나의 개발 일상 기록
[MSA] 주문 프로젝트_ItemDomain 본문
ITEM DOMAIN
Item Doamin의 경우 간략하게 Item, ItemOption, ItemOptionGroup 3개의 Entity가 연관관계를 가지고 있음을 생각해볼수 있습니다. 이 중 Item은 도메인 전체의 Aggregate Root 역할을 하게 됩니다. Item Aggregate 내부에서는 데이터가 변경될 때마다 유지돼야 하는 일관된 규을 지켜야하며 일관된 규칙은 Aggregate Root에 적용되는 모든 트랜잭션 내에서 지켜져야 합니다. 만일 규칙이 깨진다면 트랜잭션 롤백을 발생한다는 것을 의미합니다. Item Aggregate 를 만들어내는 과정에서 Factory의 사용을 검토해야 합니다. Item 도메인의 경우 객체 생성 과정에서 Item뿐만 아니라 ItemOption과 ItemOptionGroup의 정합성이 일관되게 지켜져야 합니다.
Entity 구현
Item, ItemOption, ItemOptionGroup의 필수 속성과 메서드를 정의하며 각 Entity간의 연관관계를 설정합니다. 연관관계를 설정 시 OneToMany, ManyToOne 등의 연관관계를 설정하여 객체 간의 참조 관계를 명확히 알 수 있도록 해야합니다. ManyToOne만으로 객체 관계를 설정하는 것이 단방향을 유지하고 성능 측면에서도 좋겠지만 실제 도메인 로직 구현 과정에서는 OneToMany사용에 대한 장점이 있기 때문에 적절한 타협이 필요합니다.
@OneToMany(1 : N)
Entity간의 관계를 지정할때 가장 많이 사용하는 관계가 @OneToMany일 것 입니다. @OneToMany는 데이터를 바라보는 주체가 부모 Entity이며 하나의 부모 Entity데이터와 연관이 있는 여려개의 자식 Entity데이터를 사용하겠다는 의미입니다.
OneToMany 어노테이션 사용 시 mappedBy를 설정해주어야 하는데
해당 Entity의 어떤 필드와 매칭이 되는가를 지정해주는 것입니다. 아래의 소스코드 처럼 ItemOptionList라는 리스트는 itemOptionGroup과의 1: N관계를 맺기때문에 mappedby에 itemOptionGroup 필드를 지정해주어야 합니다.

@ManyToOne (N : 1)
@ManyToOne관계는 자식 Entity객체에서 부모 Entity객체를 바라볼때 사용하는 어노테이션입니다. @ManyToOne의 FetchType의 디폴트 값은 EAGER로서 자식 Entity가 조회도미과 동시에 부모 Entity를 조회해 옵니다. 만약 부모 Entity를 바로 사용하지 않는다면 속도를 위해 FetchType을 LAZY로 설정할 수 있습니다.
@ManyToOne어노테이션 사용시 @JoinColumn어노테이션을 함께 사용하여야 하는데 @JoinColumn 어노테이션의 엘리먼트 name 의 값은 외래 키 이름을 지정하는 필드가 됩니다.

여기서 @JoinColumn(name = "item_option_group_id")로 하게 되면 item_options 테이블의 item_option_groups pk를 저장하는 컬럼명이 item_option_group_id가 되는 겁니다.
Factory와 Repository
Factory의 필요성
복잡한 객체를 생성하는 일은 도메인 계층의 책임이지만, 그것이 모델을 표현하는 객체에 속하는 것은 아닙니다. 예를 들어 자동차를 조립하는 것과 자동차를 운전하는 것은 다른영역인 것처럼 결코 동시에 일어날 수 없는 일입니다. 그렇다고 객체의 생성을 클라이언트에 두면 Aggregate의 캡슐화를 위반하고 클라이언트의 설계가 지저분해지게 됩니다. 복잡한 객체와 Aggregate의 인스턴스 생성을 책임지는 별도의 객체를 선언하여 운영하는 것이 필요합니다.
Factory의 특징
자신의 책임과 역할이 다른 객체를 생성하는 것인 프로그램 요소를 Factory라고 합니다. Factory는 해당 Factory에서 만들어내는 객체와 매우 강하게 결합돼 있으므로, 자신이 생성하는 객체와 가장 가까이 있어야 합니다. Factory는 Aggregate에서 유지되어야 할 불변식 로직을 Factory내에 둬서 Aggregate내에 들어 있는 복잡한 요소를 줄일 수도 있습니다. 특정 개체나 Aggregate를 생성하는 일이 복잡해지거나 내부 구조를 너무 많이 드러내는 경우 Factory를 사용하면 좋습니다.
Repository의 필요성
객체를 이용해 뭔가를 하려면 해당 객체에 대한 참조를 가지고 있어야 합니다. 클라이언트는 이미 존재하는 도메인 객체의 참조를 획득할 수 있는 실용적인 수단을 필요로 합니다. 해당 객체의 속성을 활용하여 검색하는 식으로 객체의 참조를 획득할 수 있어야 하며, 이 때 마음대로 데이터베이스에 질의를 수행하면 도메인 객체와 Aggregate의 캡슐화를 어기는 질의를 수행할 수도 있게 됩니다. Repository는 이와 같은 로직을 캡슐화하여 구현에 있어서 모델에 집중할 수 있게 해주는 개념적 틀에 해당됩니다.
Repository의 특징
클라이언트는 지정된 기준에 근거해 객체를 선택하는 질의 메서드를 이용해 Repository에서 객체를 요청하게 됩니다. 데이터베이스 관련 기술은 다양하고 복잡한데 이를 Repository 인터페이스로 도출하면 클라이언트는 단순하고 의도가 드러나는 구현이 가능하고 실제 복잡한 구현은 인프라스트럭쳐에 맡길 수 있습니다. 즉, 객체를 추가하거나 제거하고 조회하는 기술을 캡슐화하여 제공하는 것이 Repository의 특징입니다. Factory가 새로운 객체를 만들어 내는데 반해 Repository는 기존 객체를 찾아냅니다.
* DDD에서의 Aggregate
필요성
모델 내에서 복잡한 연관관계를 맺는 객체를 대상으로 일관된 규칙을 보장하기는 쉽지않습니다. 개별 객체만이 아닌 서로 밀접한 관계에 있는 객체 집합에도 변경에 대한 일관된 규칙(=불변식)이 유지되어야 하기 때문입니다. 이를 위해 Aggregate를 구성하고 Aggregate에 적용되는 불변식은 각 트랜잭션이 완료될 때 이행되도록 해야합니다.
특징
- Aggregate는 데이터 변경의 단위로 다루는 연관 객체의 묶음을 말합니다. 각 Aggregate에는 루트(Root)와 경계(Boundary)가 있는데 경계는 Aggregate에 무엇이 포홤되고 포함되지 않는지를 정의하며 루트는 단 하나만 존재하며, Aggregate에 포함된 특정 Entity를 가리킵니다.
- 각 루트 Entity는 전역 식별성을 지니고, 경계 안의 Entity는 지역 식별성을 지닙니다.
- Aggregate경계 밖에서는 루트 Entity를 제외한 Aggregate내부의 구성요소를 참조할 수 없습니다.
- Aggregate경계 안의 어떤 객체를 변경하더라도 전체 Aggregate의 불변식은 모두 지켜져야 합니다.
즉, Aggregate는 생명주기의 전 단계에서 불변식이 유지되어야 할 범위를 표시해줍니다.
* MapStruct
MapStruct는 Entity와 DTO간에 변환될 때 자동으로 매핑시켜 변환되도록 도와주는 라이브러리입니다. 매핑해줄 클래스에는 Setter가 있어야 하고 매핑이 되는 클래스에느 Getter가 있어야 사용이 가능합니다. MapStruct 사용은 복잡한 애플리케이션을 여러 개의 계층으로 나누어 개발하는 것은 각 계층의 관심 측면만을 전문적으로 다룬다는 점에서 의의가 있습니다. 각 Layer마다 사용하고 전달되는 객체는 적절한 컨버팅 로직을 통해서 격리되고 변환되어야 합니다. 특정 Layer에서 사용되는 객체가 다른 Layer로 전달되는 과정에서 적절한 컨버팅이 없다면 Layer간의 의존 관계가 깨질 수도 있습니다.
ModelMapper보다 최근에 MapStruct를 더 많이 활용하는 것으로 알고있습니다. 두 라이브러리의 차이점은 ModelMapper는 리플랙션 기반으로 동작하여 실제 매핑 로직을 쉽게 파악하기 어려우나 MapStruct는 코드 생성 방식으로 동작하기 때문에 생성된 코드를 통해 매핑로직을 쉽게 파악할 수 있다는 장점을 가지고 있으며 MapStruct는 컴파일 타임에 매핑 오류를 인지하고 설정에 따라 빌드 시 에러를 던질 수도 잇습니다. 또한, 성능 측면에서도 MapStruct가 훨씬 좋다고 알려져 있습니다.
MapStruct 사용
MapStruct를 사용하기 위해서는 build.gradle에 의존성을 추가해야합니다.

생성한 Mapper Interface에 @Mapper 어노테이션을 선언을 하며 속성들을 설정할 수 있습니다.
- componentModel = "spring"
해당 속성은 클래스를 생성할 때 spring을 통해 의존성 주입을 하겠다는 의미를 가집니다.
- injectionStrategy = InjectionStrategy.CONSTRUCTOR
의존성 주입을 사용할 때 필드 주입과 생성자 주입을 설정할 수 있는 속성입니다. 필드 주입은 FIELD를 사용합니다.
- unmappedTargetPolicy = ReportingPolicy.ERROR
Target의 필드가 매핑되지 않을 때 정책입니다. ERROR로 설정하면 매핑 시 Target.aField에 값이 매핑되지 않는다면 컴파일 오류를 발생시킵니다.

매핑 선언
- Source와 Target의 attribute가 완전히 동일한 경우
Mapper Interface 내부에 Source와 Target만 선언하면 됩니다.

- Source와 Target의 Type은 동일하나 attribute가 약간 다른 경우
별도의 매핑 선언이 필요한 attribute만 정의하면 됩니다.

- 매핑 로직에서 java expression 사용이 필요한 경우
java키워드를 선언한 후 해당 로직을 작성하면 됩니다.

- 매핑 대상 객체에 Collection 요소가 포함된 경우
Collection요소에 대한 매핑 로직을 추가로 선언해야 합니다.

'MSA > MSA 구축' 카테고리의 다른 글
| [MSA] 선물하기 프로젝트_Flow설계 및 검토 (0) | 2021.09.17 |
|---|---|
| [MSA] 주문 프로젝트_OrderDomain (0) | 2021.09.16 |
| [MSA] 주문 프로젝트_Partner Domain2 (0) | 2021.09.13 |
| [MSA] 주문 프로젝트_Partner Domain1 (0) | 2021.09.09 |
| [MSA] MSA 프로젝트 구조 및 설계 (0) | 2021.09.08 |