2편은 Domain Model에 관한 것이다.
원글은 ..
https://codewithandrea.com/articles/flutter-app-architecture-domain-model/
Domain-Driven Design(DDD)에서 중요한 것은 model 이다.
좋은 도메인 모델을 갖고 있는 지의 여부는 프로젝트의 성공과 실패를 결정하는 것과도 같다..
플러터 아키텍처를 다시 살펴보자
전에서 데이터 계층(Data layer)은
외부 API 및 데이터 소스와 통신하는 역할을 하며 repository를 포함하고 있다고 말했다.
그 바로 위에 있는 모델 계층과 애플리케이션 계층은 모델들과 비즈니스 로직을 갖고 있으므로
매우 중요한 역할을 한다.
일단,
도메인 모델이란 무엇일까?
도메인 모델은 행동과 데이터를 모두 통합하는 도메인의 개념적 모델이다.
무슨 말인지 모르겠다면.. 그냥 도메인 자체를 이해하기 위한 개념 모델이라고 생각하자
· 데이터(Data) : 엔티티 집합과 그들의 관계로 이루어져 있음
· 행동(Behavior) : 비즈니스 로직을 변환한 것으로 엔티티들을 조작하는 역할을 함
아래는 엔티티 관계 예시이다
다음과 같이 작성된 다이어그램을 통해
Model을 작성해 보도록 하자..
typedef ProductID = String;
class Product {
Product({
required this.id,
required this.imageUrl,
required this.title,
required this.price,
required this.availableQuantity,
});
final ProductID id;
final String imageUrl;
final String title;
final double price;
final int availableQuantity;
// serialization code
factory Product.fromMap(Map<String, dynamic> map, ProductID id) {
...
}
Map<String, dynamic> toMap() {
...
}
}
model 안에는 UI를 그리기 위해 필요한 속성들은 필수적으로 가지고 있어야 한다.
아래 그림과 같이 품목에 대해 정보를 제공해주는 요소들을 말이다..
이렇게 simple data classes가 있는 반면,
(위 product 모델은 리파지토리 및 서비스 혹은 다른 객체에 접근할 수 없는 그저 간단한 모델 클래스이다.)
비즈니스 로직을 갖고 있는 모델 클래스도 존재한다.
◆ 비즈니스 로직을 가진 Model class
예를 들어서, 쇼핑 앱에서 피자와 여러 채소들을 구매하고자 장바구니에 담았다고 가정해보자.
장바구니에 담긴 항목들은 사용자의 선택에 따라 금액, 수량, 품목들을 갱신해야 한다.
이때, 장바구니를 갱신하는 코드를 상품 정보를 갖고있는 클래스 안에서 만들기 보단
해당 클래스를 플러터 확장 매서드인 extension을 사용하여 나타낸다.
상품 ID와 수량을 key-value 쌍의 Map 형태로 가지고 있는 Cart 클래스를 먼저 보자
class Cart {
const Cart([this.items = const {}]);
/// - key: product ID
/// - value: quantity
final Map<ProductID, int> items;
factory Cart.fromMap(Map<String, dynamic> map) { ... }
Map<String, dynamic> toMap() { ... }
}
cart에서 상품을 추가하거나 제거할 수도 있다.
해당 기능을 cart 클래스를 extension 해서 만들어 보자
extension MutableCart on Cart {
//카드 갱신
Cart addItem({required ProductID productId, required int quantity}) {
final copy = Map<ProductID, int>.from(items);
//기존의 것을 복사해서 새로 cart를 update 함
copy[productId] = quantity + (copy[productId] ?? 0);
return Cart(copy);
}
//카드에서 삭제
Cart removeItemById(ProductID productId) {
final copy = Map<ProductID, int>.from(items);
copy.remove(productId);
return Cart(copy);
}
}
이렇게 하면 카트 안의 값들은 변경되지만 최종적으로 immutable한 새로운 카트 객체를 얻게 된다.
→ 많은 상태관리가 immutable한 객체에 의존하고 있기 때문에,
모델의 상태를 변경하기 위해서는 새로운 immutable한 객체를 복사한 뒤, 그 안에서 변경해야 한다.
★ 핵심
모델/엔티티에 대해서 immutable data class로 만드는 것이 중요하다
모델의 단위테스트에 대해서는 나중에 따로
테스트 part에 모아서 정리한 뒤 올리도록 하겠당..
'Flutter' 카테고리의 다른 글
[Flutter/플러터] Riverpod와 StateNotifier에 대해 (0) | 2022.09.28 |
---|---|
[Flutter/플러터] Riverpod 장점 및 사용법 (1) | 2022.09.28 |
[Flutter/플러터] Riverpod를 통해 알아보는 플러터 아키텍처 - (1) Repository pattern (0) | 2022.09.27 |
[Flutter/플러터] Freezed 플러그인 사용하기 - (1) (0) | 2022.09.27 |
[Flutter/플러터] getx를 사용하여 like 버튼(하트 버튼) 만들기 (0) | 2022.09.23 |