Aug 29, 2021 - DDD pattern
*8월29일
*DDD - domain driven design
-쇼핑몰 기준, 소비자, 쇼핑몰, 주문, 제품 등으로 나눌 수 있고, 각각이 각 영역을 갖는 도메인(문제영역)이다.
-유비쿼터스 랭귀지!! 즉 소프트웨어에 관여하는 모든 부서가 같은 의미로 듣고 이해해야함. 협업!!
-소프트웨어 복잡성 해결의 키는 의존성 관리
->개발단계 뿐만 아니라 배포 후에도 지속적으로 변경이 일어남, 이러한 변경에 유연하게 대처하려면 연결관계, 즉 의존성을 제어해야함
-Presentation - Application - Domain - Infrastructure (PADI): 가장 스탠다드한 형태이며, 모든 레이어를 만들어 줘야 합니다. 도메인 로직이 복잡하거나 점점 확장해 나가야할 프로젝트라면 PADI 패턴을 사용합니다.
-application layer(필수적인 예외 처리 후, 실제 값 변경은 도메인에게)
->domain layer(핵심 로직 처리)
->infrastructure(실제 DB 연동, 메시지 송수신, 메일 발송, RestAPI 처리 등 기능 수행)
1)presentation layer
-UI layer, 세션관리, 인증관리, 유효성 검사 등 수행
router.post('/', async (req, res, next) => {
try {
const cancleOrderService = new CancleOrderService(req.appKey)
const result = await cancleOrderService.cancleOrder(req.body.orderId)
return next(null, result);
} catch(err) {
return next(err);
}
});
2)application layer
-service 단 느낌, 주문등록, 주문취소, 회원가입 등 req 온 걸 도메인영역의 도메인 모델을 활용하여 처리
->실제 도메인 로직은 도메인 영역에서 구현; 응용은 이를 사용하기 위한 서비스 모듈 구현
-단순하며, 트렌젝션 관련된 로직이 포함됨
class CancleOrderService {
...
async function cancleOrder(orderId) {
try {
const order = await findOrderById(orderId);
if (order === null) throw new Error("OrderNotFoundException");
const result = await order.cancle();
return result;
} catch (err) {
return err;
}
}
async function findOrderById(orderId) {
const orderRepository = new OrderRepository()
const order = await orderRepository.findById(orderId);
return order;
}
...
}
3)domain model layer
-주요객체 / 규칙 / 기능이 구현됨;
-객체 : entity, value, aggregate, repo, service
-규칙 : 주문 도메인 기준, 규칙1) 출고 전 배송지 변경 불가 2)주문 취소는 배송 전에만 가능
-기능 : 주문 도메인의 경우, 배송상태변경, 주문취소, 결제 등;
-쇼핑몰 기준, 주문/회원 등 ; 주문의 하위
class Order {
...
// 배송 기능
const changeShipped = () => {}
// 배송 상태 변경 기능
const changeShippingInfo = () => {}
// 주문 취소 기능
const cancle = () => {}
// 결제 기능
const completePayment = () => {}
...
}
-domain 구조
-aggregates
entities
values
services
4)infrastructure layer
-표현,응용,도메인 모두와 연계
-DB나 메시징 시스템과 같은 외부 시스템과의 연동 처리
class OrderRepository {
...
// Order ID 기반 조회 인프라 구현
const findById = () => {}
// 전체 Order ID 조회 인프라 구현
const findAll = () => {}
// 객체 저장
const save = () => {}
// 객체 삭제
const delete = () => {}
// 카운트
const count = () => {}
...
}
*예시
ex)
orderNum112547 객체 기준 , orderState와 shippingInfo라는 하위 객체들을 가지고 있음
- orderState 객체의 isShippingChangeable 메소드를 통해서 해당 오더의 결제전/상품준비중/배송중/배송완료와 같은 status를 조회할 수 있음;
-changeShippingInfo 메소드의 경우, isShippingChangeable boolean return값을 기준으로 분기처리해서 배송정보 변경 처리를 함;
ex2)
if(orderStatus == beforePayment || orderStatus == beforeShipment)
console.log('주문 취소 + 배송비 변경 가능 상태입니다.');
}
ex3)
결제완료 전에는 상품 준비 x
->결제 완료 true -> 상품 준비 activate
*entity vs value
-entity : orderNum과 같은 식별자 보유; Order라는 객체 / delivery라는 객체 모두 orderNum이라는 동일한 식별자를 보유하고 있다면, 둘은 같은 entity로 볼 수 있음;
->identifier는 일반적으로 UUID 사용
-value타입은 배송 주소, 받는 사람과 같이 개념적으로 완전한 하나의 정보를 표현하는 방식, 불변 타입으로 만드는게 맞음;
-domain에 setter는 만들지 않는다!
->특정한 창구를 통해서만 바꾸게 하는 목적 + function a, b, c를 각각 다른 사람이 만들었다고 가정 시, immutable로 처리해야 연결 고리가 깨어지지 않음
*DIP - dependency Inversion Principle
-일반적으로 고수준 모듈이 저수준 모듈에 의존하여, 저수준 모듈이 직접적인 구현 코드를 가지고 있음
-> 따라서 저수준 모듈이 동작하지 않을 시, 고수준 모듈을 독립적으로 테스트 해볼 수 없고 저수준 모듈이 일부 변경되면 고수준 모듈까지 영향받아 기능 확장이 어려운 점이 있다.
-DIP는 저수준 모듈이 고수준 모듈에 의존하도록 구조를 변경 -> 추상화된 인터페이스 사용.....
*MSA - Microservice architecture
-Monolithic architecture : 전통적인 mvc model 느낌; presentation | business | data access |; 모든게 한 덩이
->MSA 는 전체 서비스를 독립적으로 구성
*Bounded context
-주문 / 회원 / 카탈로그 / 재고 등으로 구분,, 각각이 presentation | application | domain | infra | DB를 가짐;
-각각이 하나의 서버이자 서비스; 따라서 서로간의 종속성이 최소화되므로 각각의 서버의 배포, 관리, 유지보수, 추가업뎃이 수월함
->서비스간 통신은 restAPI + eventsourcing/CQRS등의 프로토콜을 사용함
*CQRS & EventSourcing
-CQRS(읽기에 최적화) - query
-command query responsibility segregation은 조회와 삽입,수정,삭제를 분리하는 개념.
->도메인이 복잡할 수록 명령 기능과 조회 기능이 다루는 데이터 범위의 차이가 발생. 따라서 단일 모델로 처리할 수 없음.
-EventSourcing(쓰기에 최적화) - command
-이벤트 전체를 하나의 데이터로 저장하는 방식, 그로인해 쓱싹 laborOrderService collection이 필드 57개 가 넘어가게 된 것..
-즉 update, delete가 없는 오직 add만 가능한 방식임.