Flutter 클린 아키텍쳐

우리는 매일 많은 앱을 개발합니다. 소규모 스토어와 같이 작은 프로젝트일 때도 있고, 큰 규모의 앱일 때도 있습니다. 항상 그렇듯이 적절한 도구를 선택해야 합니다. 이러한 도구에는 IDE, 언어, 프레임워크만 있는 것이 아닙니다. 다른 도구와 마찬가지로 아키텍처도 고려해야 합니다. 잘 계획된 아키텍처는 가독성이 좋고 확장 가능한 소프트웨어를 더 잘 만들 수 있기 때문에 훨씬 더 좋습니다. 또한 향후에 소프트웨어를 변경해야 할 때 더 쉽게 작업할 수 있습니다. 앱 뿐만 아니라 특정 기능에 대한 좋은 계획이 성공의 열쇠입니다. 좋은 소식은 프로젝트가 크고 이미 수년 동안 개발된 경우에도 아키텍처를 선택할 수 있다는 것입니다.

MVC

MVC는 앱의 기본 아키텍처입니다. 모든 화면은 세 가지 구성 요소로 나뉩니다.

👉 Model - 비즈니스 엔티티와 애플리케이션 모델을 배치하는 곳
👉 View - 형식이 지정된 데이터를 사용자에게 표시하는 곳
👉 Controller - 데이터를 변환하고 사용자 상호 작용을 처리하는 곳

MVP

Model View Presenter (MVP) 아키텍처는 MVC와 매우 유사하지만 모든 UI 비즈니스 로직이 프레젠터에 저장됩니다. 예를 들어 핸들 버튼 동작의 경우 프레젠터에 다음 뷰를 표시하도록 요청할 수 있습니다. 또한 프레젠터는 모델과 상호 작용하여 비즈니스 엔티티를 가져오고 파싱합니다. 즉, 프레젠터에는 뷰별로 호출되는 메서드가 포함되어 있으며 뷰에 대한 작업을 수행합니다. 이 경우 프레젠터에는 뷰에 대한 참조가 포함됩니다.

MVVM

Model View ViewModel (MVVM)은, 또 다른 유사한 아키텍처이지만 이 경우에는 뷰모델을 뷰 추상화의 다른 측면으로 취급할 수 있습니다. 뷰모델은 뷰에 연결할 수 있는 모델 데이터에 대한 래퍼를 제공합니다. 뷰모델에는 모델 및 뷰와 통신하는 명령(메서드)이 포함되어 있습니다.

예시: 버튼을 탭하는 경우 뷰가 서비스 데이터를 요청할 수 있습니다. 뷰모델은 서비스에서 데이터를 가져와 파싱하고 처리한 후 적절한 방식으로 뷰에 반환하는 역할을 담당합니다.

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개의 다른 레이어가 있습니다. 더 자세히 살펴봅시다.

.g 파일 자동 생성 터미널 명령

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)))

Serializable


Previous Post Next Post