우리는 매일 많은 앱을 개발합니다. 소규모 스토어와 같이 작은 프로젝트일 때도 있고, 큰 규모의 앱일 때도 있습니다. 항상 그렇듯이 적절한 도구를 선택해야 합니다. 이러한 도구에는 IDE, 언어, 프레임워크만 있는 것이 아닙니다. 다른 도구와 마찬가지로 아키텍처도 고려해야 합니다. 잘 계획된 아키텍처는 가독성이 좋고 확장 가능한 소프트웨어를 더 잘 만들 수 있기 때문에 훨씬 더 좋습니다. 또한 향후에 소프트웨어를 변경해야 할 때 더 쉽게 작업할 수 있습니다. 앱 뿐만 아니라 특정 기능에 대한 좋은 계획이 성공의 열쇠입니다. 좋은 소식은 프로젝트가 크고 이미 수년 동안 개발된 경우에도 아키텍처를 선택할 수 있다는 것입니다.
MVC
MVC는 앱의 기본 아키텍처입니다. 모든 화면은 세 가지 구성 요소로 나뉩니다.
👉 Model - 비즈니스 엔티티와 애플리케이션 모델을 배치하는 곳
👉 View - 형식이 지정된 데이터를 사용자에게 표시하는 곳
👉 Controller - 데이터를 변환하고 사용자 상호 작용을 처리하는 곳
MVP
Model View Presenter (MVP) 아키텍처는 MVC와 매우 유사하지만 모든 UI 비즈니스 로직이 프레젠터에 저장됩니다. 예를 들어 핸들 버튼 동작의 경우 프레젠터에 다음 뷰를 표시하도록 요청할 수 있습니다. 또한 프레젠터는 모델과 상호 작용하여 비즈니스 엔티티를 가져오고 파싱합니다. 즉, 프레젠터에는 뷰별로 호출되는 메서드가 포함되어 있으며 뷰에 대한 작업을 수행합니다. 이 경우 프레젠터에는 뷰에 대한 참조가 포함됩니다.
MVVM
Model View ViewModel (MVVM)은, 또 다른 유사한 아키텍처이지만 이 경우에는 뷰모델을 뷰 추상화의 다른 측면으로 취급할 수 있습니다. 뷰모델은 뷰에 연결할 수 있는 모델 데이터에 대한 래퍼를 제공합니다. 뷰모델에는 모델 및 뷰와 통신하는 명령(메서드)이 포함되어 있습니다.
예시: 버튼을 탭하는 경우 뷰가 서비스 데이터를 요청할 수 있습니다. 뷰모델은 서비스에서 데이터를 가져와 파싱하고 처리한 후 적절한 방식으로 뷰에 반환하는 역할을 담당합니다.
또 다른 차이점은 뷰모델은 다른 뷰와 공유할 수 있다는 것입니다. 발표자는 그렇지 않습니다. 발표자는 구체적인 뷰와 연관되어 있습니다.
클린 아키텍처를 갖춘 MVVM
MVVM과 클린 아키텍처는 앱 개발에서 일반적으로 사용되는 두 가지 아키텍처 패턴입니다. 이 두 가지 패턴을 결합하면 잘 구조화되고 유지 관리 및 테스트가 가능한 안드로이드 애플리케이션을 만들 수 있습니다.
MVVM은 앱을 모델, 뷰, 뷰모델의 세 가지 주요 구성 요소로 분리합니다.
👉 Model: 데이터 및 비즈니스 로직을 나타냄
👉 View: UI 컴포넌트를 나타냄
👉 ViewModel: 모델과 뷰 사이의 중개자 역할을 하며 뷰에 데이터를 제공하고 사용자 상호 작용을 처리함
Clean Architecture
클린 아키텍쳐는 앱 내 여러 계층의 독립성과 관심사 분리를 강조합니다.
👉 Domain Layer: 비즈니스 로직과 엔티티를 포함합니다.
👉 Data Layer: 데이터 액세스 및 저장을 관리합니다.
👉 Presentation Layer: UI 및 사용자 상호작용을 처리합니다.
.g 파일 자동 생성 터미널 명령
flutter packages pub run build_runner build
flutter pub run build_runner build --delete-conflicting-outputs
dart run build_runner watch
모든 아키텍처에는 한 가지 공통점이 있는데, 바로 컴포넌트 분리입니다.
클린 아키텍처는 Robert C. Martin이 소개한 아키텍처 패턴입니다. 이는 애플리케이션의 여러 구성 요소를 각각 잘 정의된 목적을 가진 모듈로 분리하는 애플리케이션 구조화 방법을 제공합니다. 클린 아키텍처의 기본 개념은 애플리케이션을 프레젠테이션 계층, 도메인 계층, 데이터 계층의 세 가지 주요 계층으로 분리하는 것입니다.
종속성 반전 원칙(DIP)은 상위 레벨 모듈이 하위 레벨 모듈에 종속되어서는 안 되며, 오히려 둘 다 추상화에 종속되어야 한다고 주장합니다. Flutter 클린 아키텍처는 이 원칙을 준수합니다. 이는 일반적으로 Flutter의 추상 클래스와 인터페이스를 활용하여 여러 계층에 걸쳐 컨트랙트를 지정함으로써 테스트를 쉽게 하고 구현을 유연하게 변경할 수 있도록 합니다.
여기에는 4개의 다른 레이어가 있습니다. 더 자세히 살펴봅시다.
Data Layer
애플리케이션의 데이터 계층을 나타냅니다. 가장 바깥쪽 레이어의 일부인 데이터 모듈은 데이터 검색을 담당합니다. 이는 서버 및/또는 로컬 데이터베이스에 대한 API 호출의 형태일 수 있습니다. 또한 리포지토리 구현도 포함됩니다.
👉 Repositories (Domain): 도메인 계층에 있는 리포지토리의 실제 구현입니다. 리포지토리는 서로 다른 데이터 소스의 데이터를 조정하는 역할을 담당합니다.
👉 DTO Models: 데이터 소스와 상호 작용할 수 있는 JSON 구조의 표현입니다.
👉 Data sources: 원격 및 로컬 데이터 소스로 구성됩니다. 원격 데이터 소스는 API에서 HTTP 요청을 수행합니다. 로컬 데이터 소스는 데이터를 캐시하거나 보존합니다.
👉 Mapper: 엔티티 객체를 모델에 매핑하거나 그 반대로 매핑합니다.
데이터 계층은 변환기, 데이터베이스, 리포지토리 패키지로 구성됩니다. 데이터 계층은 도메인 계층에 의존하기 때문에 위에서 설명한 대로 리포지토리 구현을 수행합니다. 리포지토리는 데이터베이스와 통신하지만 각 데이터베이스마다 별도의 모델이 필요하므로 컨버터 패키지가 필요합니다.
Model Class (mapper class) 관계 엔티티 클래스
매퍼는 API에서 가져온 데이터(이 경우 DTO)를 비즈니스 개체에 매핑하는 간단한 클래스입니다.
import 'package:freezed_annotation/freezed_annotation.dart';
part 'department_entity.freezed.dart';
@Freezed(copyWith: false, equal: false)
class DepartmentEntity with _$DepartmentEntity {
const factory DepartmentEntity(
{ final int? id,
final String? day}) = _DepartmentEntity;
const DepartmentEntity._();
}
import 'package:freezed_annotation/freezed_annotation.dart';
part 'department_mapper.freezed.dart';
part 'department_mapper.g.dart';
@Freezed(copyWith: false, equal: false)
class DepartmentMapper with _$DepartmentMapper {
const factory DepartmentMapper(
{@JsonKey(name: 'ID') final int? id,
@JsonKey(name: 'DAY')
final String? day}) = _DepartmentMapper;
const DepartmentMapper._();
factory DepartmentMapper.fromJson(Map<String, dynamic> json) =>
_$DepartmentMapperFromJson(json);
}
extension DepartmentExtension on DepartmentMapper {
DepartmentEntity toDomain() =>
DepartmentEntity(id: id, day: day);
}
response.data.map<DepartmentEntity>((data) => DepartmentMapper.fromJson(data).toDomain()).toList()
Equatable
Equatable은 개체의 동일성 여부를 쉽게 비교할 수 있도록 해주는 인기 있는 Flutter 패키지입니다. 데이터는 같지만 참조가 다른 객체에 대해 의도하지 않은 동일성 검사를 수행할 수 있는 Dart의 기본 제공 == 연산자로 작업할 때 특히 유용합니다.
Equatable은 많은 상용구 코드를 작성하지 않고도 클래스에서 등호(==) 및 해시 코드(hashCode) 메서드를 재정의할 수 있도록 도와주는 Dart 패키지입니다. 메모리 주소가 아닌 속성을 기반으로 객체의 동등성을 간결하고 우아하게 정의할 수 있는 방법을 제공합니다.
Serializable
part 'department_mapper.g.dart';
@JsonSerializable()
class DepartmentMapper extends DepartmentEntity {
@override
@JsonKey(name: JsonKeyConstant.idConsignmentJsonKey)
final int? id;
@override
@JsonKey(name: JsonKeyConstant.dayConsignmentJsonKey)
final String? day;
const DepartmentMapper({this.id, this.day})
: super(id: id, day: day);
factory DepartmentMapper.fromJson(Map<String, dynamic> json) =>
_$DepartmentMapperFromJson(json);
Map<String, dynamic> toJson() => _$DepartmentMapperToJson(this);
}
import 'package:equatable/equatable.dart';
class DepartmentEntity extends Equatable {
final int? _id;
final String? _day;
const DepartmentEntity({int? id, String? day})
: _id = id,
_day = day;
int? get id => _id;
String? get day => _day;
@override
List<Object> get props => [_id!, _day!];
@override
bool get stringify => true;
}
List<DepartmentMapper>.from(response.data
.map((i) => DepartmentMapper.fromJson(i)))