DDD Start
책이 참 좋네요. 다들 사서 한번 읽어보세요 .
domain driven design
‘온라인 서점’은 소프트웨어로 해결하고자 하는 문제 영역, 즉 도메인에 해당한다.
한 도메인은 다시 하위 도메인으로 나눌수 있다.
도메인 모델에는 다양한 정의가 존재하는데 기본적으로 도메인 모델은 특정 도메인을 개념적으로 표현한 것이다.
도메인에 따라 용어의 의미가 결정되므로 여러 하위 도메인을 하나의 다이어그램에 모델링 하면 안된다.
모델의 각 구성요서는 특정 도메인을 한정할때 비로소 의미가 완전해지기 때문에 각 하위 도메인마다 별도로 모델을 만들어야 한다.
도메인 계층은 도메인의 핵심 규칙을 구현한다.. 주문 도메인의 경우 ‘출고 전에 배송지를 변경할수 있다’ 는 규칙과 ‘주문 취소는 배송 전에만 할 수 있다’는 규칙을 구현한 코드가 도메인 계층에 위치하게 된다. 이런 도메인 규칙을 객체지향 기법으로 구현하는 패턴이 도메인 모델 패턴이다.
핵심 규칙을 구현한 코드는 도메인 모델에만 위치하기 때문에 규칙이 바뀌거나 확장해야 할 때 다른 코드에 영향을 덜 주고 변경 내역을 모델에 반영할수 있게 된다.
엔티티
엔티티의 가장 큰 특징은 식별자를 갖는다는 것이다. uuid사용
벨류 타입
데이터를 가지고 있다. 개념적으로 완전한 하나를 표현할때 사용한다.
도메인 모델에 set 메서드 넣지 않기
도메인 객체가 불완전한 상태로 사용되느느 것을 막으려면 생성 시점에 필요한 것을 전달해 주어야 한다. 즉 생성자를 통해 필요한 데이터를 모두 받아야 한다.
불변 밸류타입을 사용하면 자연스럽게 밸류 타입에는 set메서드를 구현하지 않는다. set메서드를 구현해야 할 특별한 이유가 없다면 불변 타입의 장점을 살릴 수 있도록 밸류타입은 불변으로 구현한다.
아키텍쳐 개요
네개의 영역
표현 (UI) 응용 도메인 인프라스터럭쳐
계층구조 아키텍쳐
상위계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않는다.
고수준 모듈과 저수준 모듈
DIP dependency injection principle
고수준의 모듈은 더 이상 저수준 모듈에 의존하지 않고 구현을 추상화한 인터페이스에 의존한다.
DIP의 핵심은 고수준 모듈이 저수준 모듈에 의존하지 않도록 하기 위함
dip를 적용할 때 하위 기능을 추상화한 인터페이스는 고수준 모듈 관점에서 도출한다.
즉 할인 금액 계산을 추상화한 인터페이스는 저수준 모듈이 아닌 고수준 모듈에 위치한다.
하위 기능을 추상화한 인터페이스는 고수준모듈에 위치하낟,.
인프라스트럭처 영역은 구현 기술을 다루는 저수준의 모듈이고 응용 영역과 도메인 영역은 고수준 모듈이다.
도메인 영역의 주요 구선 요소
- 엔티티
- 밸류
- 애그리거트
- 리포지터리
- 도메인 서비스
신입 시절에는 처음 도메인 모델을 만들때 DB 테이블의 엔티티와 도메인 모델의 엔티티를 구분하지 못해 거의 동일하게 만들곤 햇다. 실제 도메인 모델의 엔티티와 DB 관계형 모델의 엔티티는 같은것이 아니을 알게 되었다.
두 모델의 가장 큰 차이점은 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 함께 제동한다는 점이다. 예를들의 주문을 표현하는 주문과 관련된 데이터 뿐만 아니라 배송지 주소 변경읠 위한 기능을 함께 제공한다.
에그리거트는 관련 객체를 하나로 묶은 군집이다. 대표적인 예가 주문이다. 주문이라는 도메인 개념은 주문 배송지정보 주문자 주문목록 총 결제금액의 하위 모델로 구성되는데 이때 이 하위 개념을 표현한 모델을 하나로 묶어서 ‘주문’이라는 상위 개념으로 표현할수 있다.
리포지터리 도메인 객체를 지속적으로 사용하려면 DB 로컬파일과같은 물리적인 저장소에 도메인인 객체를 보관해야한다. 이를 위한 도메인 모델이 리포지터리이다. 엔티티나 밸류가 요구사항에서 도출되는 도메인 모델이라면 리포지터리는 구현을 위한 도메인 모델이다.
리포지터리는 애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다.
응용 서비스는 도메인 모델을 이용해서 기능을 구현한다. 기능 구현에 필요한 도메인객체를 리포지터리에서 가져와 실행하거나 신규 도메인 객체를 생성해서 리포지토리에 저장하낟.
인프라 스트럭쳐는 도메인 영역과 응용 영역에서 인프라스트럭쳐의 기능을 직접 사용하는 것보다 이 두영역에 정의한 인터페이스를 인프라스트럭처 영역에서 구현하는 것이 시스템을 더 유연하고 테스트하기 쉽게 만들어준다.
응용역역과 도메인 영역이 인파라스트럭처에 대한 의존을 완전히 갖지 않도록 시도하는 것은 자칫 구현을 더 복잡하고 어렵게 만들수 있다.
애그리거트
상위 수준에서 모델을 정리하면 복잡한 도메인 고델의 관계를 이해하는데 도움이 된다.
애그리거트는 관련 객체를 하나의 군으로 묶어준다. 수많은 객체를 애그리거트로 묶어서 바라보면 좀 더 상위 수준에서 도메인 모델 간의 관계를 파악할 수 있다.
에그리거트는 복잡한 도메인을 단훈한 구조로 만드어 준다. 복잡도가 낮아지는 만큼 도메인 기능을 확장하고 변경하는데 필요한 노력도 줄어든다.
에그리거크는 관련된 모델을 하나로 모은 것이기 때문에 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프사이클을 갖는다.
도메인 규칙에 따라 최초 주문 시점에 일부 객체를 만들 필요가 없는 경우도 있지만 애그리거트에 속한 속한 구성요소는 대부분함께 생성하고 함께 제거한다.
한 애그리거크에 속한 객체는 다른 애그리거트에 속하지 않는다. 애그리거트는 독립된 객체군이며 각 애그리거트는 자신을 관리할뿐 다른 애그리거트를 관리하지 않는다. 예를들어 주문 애그리거트는 배송지를 변경하거나 주문 상품 개수를 변경하는등 자기자신읠 관리하지만 주문 애그리거트에서 회원의 비밀번호를 변경하거나 상품의 가격을 변경하지는 않는다.
흔히 ‘A가 B를 갖는다.’로 설계할 수 있는 요구사항이 있다면 A와 B를 한 애그리거트로 묶어서 생각하기 쉽다. 하지만 반드시 그러는건 아니다.
다수의 애그리거트는 한개의 인티티 객체만 갖는경우가 많으며 두개 이상의 엔티티로 구성되는 애그리거트는 드물게 존재한다.
에그리거트에 속한 모든 객체가 일관된 상태를 유지하려면 애그리거트 전체 관리할 주체가 필요한데 이 책임을 지는것이 애그리거트의 루트 엔티티이다 애그리거트 루트 엔티티는 애그리거트의 대표 앤티티로 애그리거트에 속한 객에는 애그리거트 루트 엔티티에 직접 또는 간접적으로 속한다.
애그리거트 루트가 제공하는 메소드는 도메인 규칙에 따라 에그리거트에 속한 객체의 일관성이 깨지지 않도록 구현해야 한다. 예를들어 배송이 시작되기 전까지만 배송지 정보를 변경할 수 있다는 규칙이 있다면 애그리거트루트인 order의 changeshippinginfo()메소드는 이 규칙에 따라 배송 시작 여부를 확인하고 변경이 가능한 경우에만 배송지 정보를 변경해야한다.
애그리거트 루트가 아닌 다른 객체가 애그리거트에 속한 객체를 직접 변경하면 안된다.
애그리거트 루트를 통해서만 도메인 로직을 구현하게 만들려면 도메인 모델에 대해 다음 두가지를 습관적으로 적용해야한다.
- 단순히 필드를 변경하는 set메소드를 public으로 만들지 않는다.
- 벨류 타입은 불변으로 구현한다.
동일하게 한 트랜젝션에서는 한개의 애그리거트만 수정해야한다. 한 트랜젝션에서 한 애그리거트만 수정한다는것은 애그리거트에서 다른 애그리거트를 변경하지 않는다는 것을 뜻한다. 애그리거트는 서루 최대한 독립적이여야한다. 만약 부득이 하게 한 트랜젝션으로 두개 이상의 애그리거트를 수정해야한다면 애그리거트에서 다른 애그리거트를 직접 수정하지 말고 응용 서비스에서 두 애그리거트를 수정하도록 구현해야한다.
리포지터리와 애그리거트
리포지터리는 애그리거트 단위로 존재한다. 애그리거트 개념적으로 하나이므로 리포지터리는 애그리거트 전체를 저장소에 영속화 해야 한다.
ID를 이용한 애그리거트 참조
애그리거트간의 참조는 필드를 통해 쉽게 구현할 수 있다.
필드를 이용해서 다른 애그리거트를 직접 찹조하는것은 개발자에게 구현의 편리함을 제공한다. 그러나 필드를 이용한 애그리거트 참조는 다음의 문제를 야기할수 있다.
- 편한 탐색 오용
- 성능에 대한 고민
- 확장의 어려움
한 애그리거트가 관리하는 범위는 자기 자신으로 한정해야한다. lazy로딩과 eager 로딩중 택한다. 성능문제 단일 dbms에서는 가능하나 확장시 문제에 부딛힌다.
id를 이용한 참조는 db 테이블에서 외래키를 사용해서 참조하는 것과 비슷하다. 다른 애그리거트를 참조할 때 Id 참조를 사용한다는 점이다. 단 애그리거트 내의 엔티티를 참조할 때는 객체 레퍼런스로 참조한다.
참조하는 애그리거트가 필요하면 응용 서비스에서 아이디를 이용해서 로딩하면 된다. 응용 서비스에서 필요한 애그리거트를 로딩하므로 애그리거트 수준에서는 지연 로딩을 하는것과 동일한 결과를 만든다.
ID를 이용한 참조 방식을 사용하면 복잡도를 낮추는 것과 함께 한 애그리거트에서 다른 애그리거트를 수정하는 문제를 원천적으로 방지할 수 있다. 외부 애그리거트를 직접 참조하기 않기 때문에 애초에 한 애그리거트에서 다른 애그리거트의 상태를 변경할 수 없는 것이다.
애그리거트별로 다른 구현 기술을 사용하는 것도 가능해진다.(오라클,몽고디비) 등등
id 참조 방식을 사용하면서 N+1 조회와 같은 문제가 발생하지 않도록 하려면 전용 조회 쿼리를 사용하면된다 예를들어 데이터조회를 위한 별도의 DAO를 만들고 DAO의 조회 메서드에서 세타 조인을 이용해서 한번의 쿼리로 필요한 데이터를 로딩하면된다.
에그리거트가 갖고 잇는 데이터를 이용해서 다른 애그리거트를 생성해야 한다면 애그리거트에 팩토리 메소드를 구현하는 것을 고려해 보자. product의 경우 제품을 생성한 스토어의 식별자를 필요로한다. 즉 store의 데이터를 이용해서 product를 생성한다 게다가 Product를 생성할 수 있는 조건을 판단할때 스토어의 상태를 이용한다. 따라서 스토어에 프로덕트를 생성하는 팩토리 메서드를 추가하면 product를 생성할때 필요한 데이터의 일부를 직접 제공하면서 동시에 중요한 도메인 로직을 함께 구현할수 있게 된다.
리포지터리와 모델 구현
리포지터리 인터페이스는 는 domain에 잇어야함 (인프라스트럭쳐에 말고.) 구현은 인프라스트럭쳐에
가능하면 리포지터리 구현 클래스를 인프라스트럭쳐 영역에 위치시켜 인프라스트럭쳐에 대한 의존을 낮춰야한다.
인터페이스는 애그리거트 루트를 기준으로 작성한다.
응용 서비스와 표현 영역
실제 사용자가 우너하는 기능을 제공하는 것은 응용영역에 위치한 서비스이다. 사용자가 회원 가입을 요청햇다면 실제 그 요청을 위한 기능을 제공하는 주체는 응용서비스에 위이한다. 응용 서비스는 기능을 실행하는 데 필요한 입력값을 메서드 파라미터로 전달받고 실행 결과를 리턴한다. 응용 서비스의 메서드가 요구하는 파라미터와 표현 영역이 사용자로부터 전달받은 데이터는 형식이 일치하지 않기 때문에 표현영역은 응용 서비스가 요구하는 형식으로 사용자 요청을 변환한다.
응용서비스의 역할
응용 서비스는 사용자가 요청한 기능을 실행한다 응용 서비스는 사용자의 요청을 처리하기 위해 리포지터리로부터 도메인 객체를 구하고 도메인 객체를 사용한다,
응용 서비스의 주요 역할은 도메인 객체를 사용해서 사용자의 요청을 처리하는것이므로 표현(사용자)영역 입장에서 보앗을때 응용 서비스는 도메인 영역과 표현영역을 연결해주는 창구인 파사드 역할을 한다.
public Result doSomethingCreate(CreateSomeReq req){
//데이터 유효성 검사
checkValid(req);
//에그리게이트 생성
SomeAgg newAgg = createSome(req)
// 리포지토리에 에그리거트 저장
someAggRepository.save(newAgg);
//결과 리턴
return createSuccessResult(newAgg);
}
응용 서비스가 이것보다 복잡하다면 응용 서비스에서 도메인 로직의 일부를 구현하고 있을 가능성이 높다,
도메인 로직 넣지 않기
도메인 로직은 도메인 영역에 위치하고 응용 서비스는 도메인 로직을 구현하지 않는다고 했다.
도메인의 핵심 로직이기 때문에 응용서비스에서 이 로직을 구현하면 안된다.
철저하게 응용서비스가 표현 영역의 기술을 사용하지 않도록 해야한다. 이를 지키기 위한 가장 쉬운 방법이 서비스 메서드의 파라미터와 리턴 타입으로 표현 영역의 구현 기술을 사용하지 않는 것이다.
응용서비스의 역할중 하나는 도메인 영역에서 발생시킨 이벤트를 처리하는 것이다. 여기서 이벶트는 도메인에서 발생한 상태 변경을 의미하며 ‘암호 변경됨’ ,’주문취소됨’과 같은 것이 이벤트가 될수 있다. 도메인 영역은 상태가 변경되면 이를 외부에 알리기 위해 이벤트를 발생시킬수 잇다
표현 영역
- 사용자가 시스템을 사용할수 잇는 (화면)흐름을 제공하고 제어한다.
- 사용자의 요청을 알맞은 응용서비스에 전달하고 결과를 사용자에게 제공한다.
- 사용자의 세션을 관리한다.
도메인 서비스
할인 금액 규칙 계산 처럼 한 애그리거트에 넣기 애매한 도메인 개념을 구현하려명 애그리거트에 억지로 넣기 보다는 도메인 서비스를 이용해서 도메인 개념을 명시적으로 드러내면 된다. 응용 역역의 서비스가 으용ㅇ 로직을 다룬다면 도메인 서비스는 도메인 로직을 다룬다.
도메인 서비스가 도메인 영역의 애그리거트나 밸류오ㅓ 같은 다른 구성요소와 비교할때 다른점이 잇다면 상태없이 로직만 구현한다는 점이다. 도메인 서비스를 구현하는데 필요한 상태는 애그리거트나 다른 방법 으로 전달받는다.
에그리거트 객체에 도메인 서비스를 전달하는 것은 으용 서비스 책임이다.
특정 기능이 응용서비스인지 도메인 서비스인지 감을 잡기 어려울때는 해당 로직이 애그리거트의 상태를 변경하거나 애그리거트의 상태값을 계산하는지 검사해보면된다. 예를 들어 , 계좌이체 로직은 계좌 애그리거트의 상태를 변경한다. 결제 금액 로직은 주문 애그리거트의 주문 금액을 계산한다. 이 두 로직은 각각 애그리거트를 변경하고 애그리거트의 값을 계산하는 도메인 로직이다. 도메인 로직이면서 한 애그리거트에 넣기 적합하지 않으므로 이 두로직은 도메인 서비스로 구현하게 된다.
도메인 모델과 bounded context
하위 도메인마다 사용하는 용어가 다르기 때문에 올바른 도메인 모델을 개발하려면 하위 도메인마다 모델을 만들어야 한다.
모델을 특정한 컨텍스트(문맥)하에서 완전한 문맥을 갖는다. 같은 제품이라도 카달로그컨텍스트와 재고 컨텍스트에서 의미가 서로 다르다. 이렇게 구분되는 경졔를 갖는 컨텍스트를 DDD에서는 Bounded Context라고 부른다.
Bounded Context는 논리적으로 한개의 모델을 갖는다.
Bounded Context는 실제로 사용자에게 기능을 제공하는 물리적 시스템으로 도메인 모델은 이 bounded context안에서 도메인을 구현한다.
여러 하위 도메인을 하나의 bounded Context에서 개발할때 주의할 점은 하위 도메인의 모델이 뒤섞이지 않도록 하는 것이다.
비록 한개의 bounded Contextㅇ네서 여러 하위 도메인을 포함하더라도 하위 도메인 마다 구분되는 패키지를 갖도록 구현해야 하위 도메인을 위한 모델이 서로 뒤섞이지 않아서 하위 도메인 마다 bounded context를 갖는 효과를 낼수 있다.
Bounded Context는 어떤식으로든 연결이 되기 때문에 두 bounded Context는 다양한 방식으로 관계를 맺는다. 두 바운디드 컨텍스트간 관계중 가장 흔한 관계는 한쪽에서 api를 제공하고 다른한쪽에서 그 api를 호출하는 관계이다. Restapi가 대표적이다. 이런 서비스를 가르켜 공개 호스트 서비스 라고 한다.
컨텍스트맵
개별 Bounded Context에 매몰되면 전체를 보지 못할 때가 있다 나무만 보고 숲을 보지 못하는 상황을 방지하려면 전체 비지니스를 조망할수 있는 지도가 필요한데 그것이 바로 컨텍스트맵이다.
오픈호스트 서비스와 안티코럽션 계층만 표시햇는데 하위 도메인이나 조직 구조를 함께 표시하면 도메인을 포함한 전체 관계를 이해하는데 도움이 된다.
이벤트
비동기 이벤트를 사용하면 두 시스템간의 결합을 크게 낮출수 있다.
이벤트라는 용어는 ‘과거에 벌어진 어떤 것’을 뜻한다.
~할때 ~할때가 발생하면 만약 ~하면 같은 요구사항은 도메인 상태 변경과 관련된 경우가 많고 이런 요구사항을 이벤트를 이용해서 구현할수 있다.
이벤트 관련 구성 요소
이벤트 생성 주체 => 이벤트 디스패쳐(퍼블리셔) =>이벤트 핸들러 (구독자)
이벤트 객체는 앤티티 밸류 도메인 서비스와 같은 도메인 객체이다. 이벤트 핸드러는 이벤트 생성 주체가 발생한 이벤트에 반응한다. 이벤트 핸들러는 생성 주체가 발생한 이벤트를 전달받아 이벤트에 담긴 데이터를 이용해 원하는 기능을 실행한다. 예를 들면 ‘주문 취소’ 이벤트를 받은 이벤트 핸들러는 해당 주문의 주문자에게 sms로 주문을 취소 사실을 통지할수 있다.
이벤트 생성 주체와 핸드러를 이어주는것이 디스패쳐이다. ㅇ이벤트를 전달받은 디스패터는 해당 이벤트를 처리할수 있는 핸들러에 이벤트를 전파한다.
이벤트의 구성
- 이벤트의 종류 : 클래스 이름으로 이벤트 종류를 표현
- 이벤트 발생 시간
- 추가 데이터 : 주문번호 등 이벤트와 관련된 정보
이벤트는 이벤트 핸들러가 작업을 수행하는데 핅요한 최소한의 데이터를 담아야한다.
배송지 정보를 변경해서 발생시킨 shippingInfoChangedEvent가 이벤트발생과 직접 관련된 바뀐 배송지 정보를 포함하는것은 맞지만 배송지 정보 변경과 전혀 관련없는 주문상품 번호와 갯수를 담을 필요는 없다.
이벤트 용도
- 트리거 - 도메인상태가 바뀔때 다른 후처리를 해야할경우 후처리를 실행하기위한 트리거이다.
- 서로 다른 시스템간의 데이터 동기화이다. 배송지 변경하면 외부 배송 서비스에 바귄 배송지 정보를 전송해야한다. 이 경우 주문 도메인은 배송지 변경 이벤트를 발생시키고 이벤트 핸들러는 외부 배송 서비스와 배송지 정보를 동기화한다.
비동기 이벤트 처리
‘A’하면 이어서 ‘B’를 하라는 내용을 담고잇는 요구사항은 실제로 ‘A’하면 최대 언제까지 B하라인 경우가 많다. 즉 후속조치를 바로 할필요없이 일정 시간 안에만 처리하면 되는 경우가 적지 않다. 이벤트를 비동기로 구현할수 잇는 방법은 매우 다양하다.
- 로컬 핸드러를 비동기로 실행하기
- 메세지 큐를 사용하기
- 이벤트 저장소와 이벤트 포워더 사용하기
- 이벤트 저장소와 이벤트 제공 api를 사용하기
(이벤트 저장소는 데이터 저장소처럼 생각하면된다.. DB에 데이터를 저장하면 데이터 저장소는 디비 …그럼 이벤트 저장소도 디비에 저장하면 그냥 디비인듯.)
EventEntry 클래스
public class EventEntry{
private Guid id;
private String type ;
private contentType
private payload;
private long timestamp;
}
public EventEntry(String type,String contentType,String payload){
this.type = type;
this.contentType = contentType
...
this.timestamp = system.currentTimeMillis();
}
public EventEntry(String type,String contentType,String payload, long timestamp){
this.type = type;
this.contentType = contentType
...
}
get 함수들...set은없다. 생성자로 다 처리
이벤트 적용시 추가 고려사항
이벤트를 구현할 때 추가로 고려할 점이 있다.
첫번째는 이벤트 소스를 EventEntry에 추가할지 여부이다.
앞서 구현한 EventEntry는 이벤트 발생 주체가 없다. 따라서 오더가 발생한 이벤트만 조회하기 처럼 특정 주제가 발생한 이벤트만 조회하는 기능을 구현할수 없다.
eventEntry에 source 필드를 추가하고 이벤트에서 소스를 받아서 디비에 저장한다.
두번째는 포워더의 전송 실패를 얼마나 허용할것이냐에 대한 것이다. 포워더는 입1ㅔㄴ트 전송에 싥패하면 실패한 이벤트분터 다시 읽어와 전송을 시도한다. 그런데 특정 이벤트에서 계속 전송에 실패하면 어떻게 될까? 이러면 이벤트 때문에 나머지 이벤트를 전송할수 없게 된다 . 이러면 그 이벤트 때문에 나머지 이벤트를 전송할수 없게 된다. 따라서 포워드를 구현할때는 실패한 이벤트의 제 전송횟수에 제한을 두어야한다.
세번째는 이벤트 손실에 대한 것이다. 이벤트 저장소를 사용하는 방식은 이벤트 발생과 이벤트 저장을 한 트랜젝션으로 처리하기 때문에 트랜젝션에 성공하면 이벤트가 저장소에 봐관된다는것을 보장할수 있다 반명 로컬 핸드러를 이용해서 이벤트를 비동기로 처리할경우 이벤트 처리에 실패하면 이벤트를 유실하게 된다. - 결론은 이벤트 저장소를 쓰라는 이야기
네번째는 이벤트 순서에 대한것이다. 이벤트 발생 순서대로 외부 시스템에 전달해야할 경우 이벤트 저장소를 사용하는것이 좋다. 반면 메세징 시스템은 사용 기술에 따라 이벤트 발생 순서와 메세지 전달 순서가 다를수 있다.
다섯번째 고려할 점은 이벤트 재처리에 대한 것이다. 동일한 이벤트를 다시 처리해야 할때 이벤트를 어떻게 해야할지 결정해야한다. 가장 쉬운 방법은 마지막으로 처리한 이벤트의 순번을 기억해 두었다가 이미 처리한 순번의 이벤트가 도착하면 해당 이벤트를 처리하지 않고 무시하는것이다. 이 외에는 이벤트 처리를 ideemponent으로 처리하는 방법도 있다.
CQRS
상태 변경을 위한 모델과 조회를 위한 모델을 분리하는 것이다.
시스템이 제공하는 기능은 크게 두가지로 나누어 볼 수 있다. 하나는 상태를 변경하는것이다 새로운 주문을 생성하거나 배송지 정보를 변경하거나 회원의 암호를 변경하는 기능이 이에 해당한다 개발자는 현재 저장하고 잇는 데이터를 변경하는 방식으로 기능을 구현한다.
또 다른 하나는 사용자 입장에서 상태정보를 조회하는 기능이다 주문 내역 조회 게시글 보기 회원정보 보기등이 이에 해당한다. 조회ㅣ 기능은 필요한 데이터를 읽어와 ui를 통해 보여주는 방식으로 구현한다. (두개를 구분해서 모델을 만들어버리자..이거네. 똑똑하군..)
CQRS는 (command and query responsbility segregation)의 약자로 상태를 변경하는 모델 (command)와 상태를 조회(Query)를 위한 모델을 분리하는 패턴이다.
명령 모델과 조회 모델이 서로 다른 데이터 저장소를 사용할수도 잇다 명령 모델은 트랜젝션을 지원해주는 RDB로 하고 조회 모델은 성능이 좋은 메모리 기만 noSQL을 사용할수 있을것이다.
게시판도 한번 등록한 게시글을 수십에서 수천내지 수백만번 조회한다.
메모리에 캐시하는 데이터는 디비에 보관된 데이터를 그대로 저장하기 보다는 화면에 맞는 모양으로 변환한 데이터를 캐시할때 성능에 더 유리하다. 즉 조회 전용 모델을 캐시하는 것이다.
비슷하게 조회 속도를 높이기 위해 쿼리를 최적화한다는 것은 조회화면에 보여질 데이터를 빠르게 읽어 올수 있도록 쿼리를 작성한다는 것이다.
단점은 구현해야할 코드가 많다. 그러나 유지보수에는 도움이 된다.
도메인이 복잡하지 않는데 cqrs를 도입하면 두 모델을 유지하는 비용만 높아지고 얻을수 있는 이점은 엇다. 반면 트래픽이 높은 서비스인데 단일 모델을 고집하면 유지보수 비용이 오히려 높아질수 잇으므로 CQRS도임을 고려해 보자.
궁금한것 (공부할것.)
애그리거트와 애그리거트 루트의 관계
- 좋은 소프트웨어란?
- 응집도를 높이고
- 결합도를 낮추고
- 결과적으로 변경의 용이성
- 중복을 줄이자..
프로그래머로써 평가는 경력이 늘어날수록 자신만의 프레임워크(설계)를 만들수 있는지 인거 같다.
aop c#