node, express, nestjs

Aug 29, 2021 - DDD pattern

jongviet 2021. 8. 29. 14:58

*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만 가능한 방식임.