도메인


응용서비스 관련 내용을 진행하기 전에 도메인에 대해 간단하게 정리해보자. 응용서비스에서 가장많이 사용하게 되는 부분은 도메인이다.

도메인에서 담당하는 역할은 도메인 내부에 있어야한다. 특히 도메인의 데이터를 조작하는 경우에는 도메인을 사용하는 응용서비스 영역에 배치되게 되면 응용서비스 영역에서 사용할 때마다 중복코드가 발생할 간으성이 크다. (도메인 관련 validation 체크도 도메인 내부에서 진행하는 것을 추천)

 

응용서비스


응용서비스는 표현영역과 도메인 영역을 연결하는 매개체 역할을 하는데 이는 디자인 패터의 파사드(Fasade)패턴과 같다.

 

응용서비스의 크기

응용서비스는 보통 다음 두가지 방법 중 한가지 방식으로 구현한다. 

1. 한 응용서비스 클래스에 회원 도메인의 모든 기능을 구현한다.

-> 한 곳에 위치하여 중복코드가 방지된다.

-> 코드의 크기가 커지기 때문에 연관성이 적은 코드가 한 클래스 내부에 모두 위치하게 되는 문제가 발생하 수 있다.

 

2, 구분되는 기능별로 응용서비스 클래스를 따로 구현한다.

-> 코드 품질이 일정부분 유지가 가능하다.

-> 코드간에 의존성이 줄어든다.

-> 필요한 코드가 발생할 때 마다 비슷한 코드가 계속 생성될 가능성이 있다.

 

결론적으로 선택에 따라 다르지만, 구현되는 응용서비스 클래스를 별도로 만드는게 나중에 가서는 조금 더 효율적인 코딩방식이 된다. (의존성이 적어 확장이 용이)

 

응용서비스의 인터페이스와 클래스

인터페이스를 만들고 그를 구현하거나 추상 클래스를 만들어 상속해서 클래스를 구현하는게 올바른 코딩 방식인가 하는 논재가 있다. 일반적으로 내부가 비슷한 속성을 가진 객체를 같은 인터페이스로 구현하여 전달받고자 할 때 많이 사용된다. 하지만 이렇게 동일한 인터페이스가 필요한 경우는 드물고 인터페이스와 클래스를 별도로 구분하여 매번 생성하면 코드의 양이 많아지고 구현하는 클래스에 대한 간접 참조가 증가해서 전체 구조만 복잡해지는 문제가 발생할 수 있기때문에 인터페이스가 명확하기전까지는 응용서비스에 대한 인터페이스를 작성하는 것이 좋은 설계라고만 할 수 없다.

 

응용서비스 메서드의 파라미터와 리턴 값

응용서비스의 파라미터 타입에 표현 영역과 관련된 타입을 주면 안된다. 예를 들어 HttpServletRequest, HttpSession등은 응용 서비스에 파라미터로 전달되면 안된다. 만약 이렇게 서비스에 Request정보가 전달 될 경우 해당 서비스를 표현영역이 있어야만 사용이 가능하게 되어 단독으로 테스트를 하거나 확장하기가 어려워진다.

 

애거리거트 트랜잭션 관리


데이터의 일관성을 관리하기 위해서 트랜잭션을 사용한다. 일반적으로 트랜잭션을 생각하면 작업이 도중에 실패할경우 해당 트랜잭션에 엮여있는 모든 작업을 롤백한다고만 알고 있다. 하지만 다른 의미로 어떤 동작을 수행하고 있을 때 다른 스레드에서 동작을 수행하고 있는 도메인의 데이터를 수정하지 못하도록 하는 것도 트랜잭션 관리의 하나이다.

선점(Pessimistic) 잠금

선전잠금은 먼저 애그리거트를 구한 스레드가 애그리거트 사용이 끝날때 까지 다른 스레드가 해당 애그리거트를 수정하는 것을 막는 방식이다. JPA의 EntityManger의 find() 메서드를 사용하여 LockModeType.PESSIMISTIC_WRITE을 전달하면 해당 엔티티와 매팽된 테이블을 이용해서 선전잠금을 적용할 수 있다.

entityManager.find(Member.class, memberId, LockModeType.PESSIMISTIC_WRITE);

 

하지만 선점잠금의 경우 예상가능하게 교착상태에 빠질 가능성이 크다. 예를 들어 스레드1이 A 도메인으 잠금하고 스레드2가 도메인 B를 잠금하고 있을 때 스레드1이 B 도메인을 잠그려고 하고 스레드2가 도메인 A를 잠그려고 시도하면 이는 서로 무한정 대기하게 되는 데드락 상태가 되어버린다. 이런경우를 대비해서 find메서드에 4번째 인자로 대기 시간을 지정할 수 있다. (단 DBMS마다 지원하는 경우가 있고 지원하지 않는 경우가 있다.)

Map<String, Object> hints = new HashMap();
hints.put(“javax.persistence.lock.timeout”, 1000);
entityManager.find(Member.class, memberId, LockModeType.PESSIMISTIC_WRITE, hits);

 

비선점 잠금

선점 방식에도 단점이 있다. 바로 같은 애거리거트를 수정하는 것이 아니라 특정 애거리거트를 사용하는 부분과 수정하는 부분이 겹치면 문제가 발생한다. 예를 들어보면 사용자의 집 주소를 수정하고 있을 때 배송서비스가 사용자의 집 주소 정보를 가져가서 배송을 한다고 생각해보자. 이 경우 배송이 시작해버리고 사용자 주소는 변경해봐야 소용이 없어진다.

이를 위해서 사용할 수 있는것이 버전 정보이다. 만약 사용자가 정보를 수정하고 있을 때 버전이 9였다. 그리고 사용자 정보를 배송서비스가 접근하면서 버전정보가 10이 되었다. 그 다음 사용자 정보를 수정을 저장하려고 할 때 현재 버전정보가 10이기 때문에 오류가 발생하게 만드는 방식이다.

이는 JPA에서 entity에 @Version 속성만 하나 지정해주면 된다. 그럼 @Transactional 애노테이션을 지정하고 작업을 진행하면 되고 만약 애그리거트의 데이터를 조작하려고 할 때 OptimisticLockingFailureException이 발생하면 이는 버전이 바뀐것이라고 판단하면 된다.

 

출처 : DDD Start 도메인 주도 설계 구현과 핵심 개념 익히기 (출판 지앤선, 저자 최범균)

'DDD' 카테고리의 다른 글

DDD. 응용서비스 코드 규칙과 트랜잭션 관리  (0) 2019.05.21
DDD. 애거리루트 정리  (0) 2019.05.16
DDD. DIP 의존 역전 원칙  (0) 2019.05.06
DDD. 도메인 주도 개발 시작  (0) 2019.05.03

애그리거트는 관련된 객체를 하나의 군으로 묶어주는 것 으로 상위수준에서 모델을 조망하는 방법 중 하나이다.

애그리거트는 비슷한 속성을 가진 객체를 묶어놓은 것을 의미한다.
예를 들어 주문 시스템에 주문 관련 애그리거트는 Order, Receiver, OrderLine.. 등이 있고 회원정보에는 Member, MemberInfo등으로 나눌 수 있다. 각 애그리거트에 연관된 객체를 담고 있으며 유사하고 동일한 라이프 사이클을 보유하고 있다.

애거리거트 루트


애그리거트에서 가장 핵심이 되는 주체 즉, 애그리거트 전체를 관리하고 책임지는 주체를 애그리거트의 루트 엔티티라고 한다. 애그리거트내에 존재하는 모든 엔티티는 루트 엔티티와 직간접적으로 연결되어있다.

애거리거트 루트의 핵심 역할은 애거리거트의 일관성을 유지하는 것이다. 그렇기 때문에 모든 애거리거트의 주요 기능은 애거리거트 루트 엔티티에 구현되어야 한다. 다시말하면 애거리거트 루트가 아닌 다른 객체가 애그리거트에 속한 객체를 직접 변경하면 안된다. 

예를 들어 상품에 대한 애그리거트가 있고 루트 엔티티로 Product가 있을 때 상품에 대한 정보를 보유한 ProductInfo의 price를 할인율 반영없이 단순히 변경하면 모델에 일관성을 깨트릴 수 있다.

ProductInfo productInfo = product.getProductInfo();
productInfo.setPrice(price);

또한 이렇게 변경하면서 중간에 할인율을 검사하도록 할 수 있지만 매번 중복된 코드가 만들어 질 수 있다.

ProductInfo productInfo = product.getProductInfo();
price = priceCalculator(price);
productInfo.setPrice(price);

 

트랜잭션의 범위


트랜잭션의 크기는 작을 수록 좋다. 하나의 트랜잭션에서 두 개 이상의 애거리거트(주문, 사용자정보등등)를 수정하게 되면 충돌이 발생할 가능성이 크다.

예를 들어 주문을 하면서 입력한 배송지를 사용자의 기본 배송지로 설정하는 경우에는 주문정보와 사용자 정보를 동시에 수정하는 경우가 발생 할 수 있다.

이런 경우에는 Order라는 애거리거트 루트에서 값을 두개 모두 수정하지 말고 OrderService라는 곳에서 값을 수정하는것이 옳다. 간단하게 보면 다음과 같이 OrderService에서 수정하는 것이 옳다.

public class OrderService {
  public void order(ShipInfo ship, isNewShippingAddr) {
    if(isnewShippingAddr) custermer.setAddr(ship.getAddr());
  }
}

 

애거리거트 필드 참조


애거리거트에서 다른 애거리거트를 필드로써 참조할 수 있다. 아래 예처럼 학교라는 School 클래스에 Student라는 애거리거트를 필드로써 참조 할 수있다.

public class School {
    private Student student;
}

이럴경우 ORM을 통해서 데이터를 함께 가져올 수 있는데 이럴 경우 단점이 있다.

1. 편한 탐색 오용
- 편하게 필드로써 애그리거트에서 다른 애그리거트를 접근할 수 있으므로 다른 애그리거트내에서 다른 애그리거트의 값을 수정할 수 있는 문제를 야기할 수 있다.
2. 성능에대한 문제
- 이는 사용에 따라서 lazy와 eager 둘 중의 하나로 선택해서 진행할 수 있다.
3. 확장이 어려움
- 만약 확장해서 student를 다른 도메인으로 빼고 싶을경우에 연관성이 깊어지다보면 분리하기 어려워서 확장이 여려워진다.

이에 대한 해결방법으로 School 내부에 Student라는 애그리거트를 포함하지 말고 student의 id인 studentId만을 포함하고 있는 것이다. 이럴경우 School에서 Student를 수정할 문제도 방지할 수 있고 lazy, eager를 고민할 필요도 dbms 확장도 자유롭게 할 수 있다.

 

애거리거트에 연관을 ID로 했을 시 상황


만약 School 내부에 Student가 List형태로 있다고 가정했을 때, 모든 학생정보를 가지고 오기 위해서 학생들 정보를 하나씩 다 가져와야한다.

이는 결국 School과 내부에 있는 학생들 수인 N개를 합쳐서 N + 1번 조회하게 된다고 해서 N+1문제라고 한다. 이를 속도가 엄청 느려지기 때문에 해결하기 위해서 조인을 사용해야 한다. 이런문제는 JPA에서 쿼리를 직접적으로 실행할 수 있는 기능등을 사용해서 해결하거나 이부분만 mybatis를 사용하는 등의 방법으로 해결할 수 있다.

 

출처 : DDD Start 도메인 주도 설계 구현과 핵심 개념 익히기 (출판 지앤선, 저자 최범균)

'DDD' 카테고리의 다른 글

DDD. 응용서비스 코드 규칙과 트랜잭션 관리  (0) 2019.05.21
DDD. 애거리루트 정리  (0) 2019.05.16
DDD. DIP 의존 역전 원칙  (0) 2019.05.06
DDD. 도메인 주도 개발 시작  (0) 2019.05.03

서비스가 특정 시스템에 의존성을 가지게 되면 서비스 자체만으로 테스트 수행이 어렵고 종속되는 시스템에 따라 서비스의 코드가 지속적으로 변경될 여지가 있다.

이를 해결하기 위해서 DIP개념을 사용할 수 있다.

DIP

제품의 할인율을 구하는 서비스가 있다고 가정해보자. 이 서비스는 의미 있는 단일 기능을 제공하는 고수준 모듈이다. 그리고 이 고수준 모듈의 기능 구현을 위해서 현재 가격과 할인 %등을 구하는 여러 하위 기능이 필요하다. 이때 이 기능들은 하위 기능을 실제로 구현한 저수준 모듈이라고 한다. 

고수준 모듈이 저수준 모듈 여러개의 의존성을 가지게 된다면 테스트와 여러 기능 수정 때마다 변경이 생긴다. 그럼 이를 해결하기위해서는 저수준 모듈이 고수준 모듈을 의존하게 만들어야 하는데 이를 위해서는 추상화한 인터페이스를 이용해서 구현해야한다.

예를 들어서 여러 이벤트마다 할인된 가격을 계산해주는 기능이 포함된 인터페이스 PriceCalculatorI를 정의한다.

package chapter2;

/** * ddd 
* * *@author *wedul 
* *@since *2019-05-06 
**/
public interface PriceCalculatorI {
    int calculaterPrice(int price);
}

그리고 특가 할인 계산하는 SpecialPriceCalculator와 이를 이용하여 가격을 계산하는 고수준 모듈을 구한다.

package chapter2;

/**
 * ddd
 *
 * @author wedul
 * @since 2019-05-06
 **/
public class SpecialPriceCalculator implements PriceCalculatorI {

    @Override
    public int calculaterPrice(int price) {
        return (int) (price * 0.1);
    }
}
package chapter2;

/**
 * ddd
 *
 * @author wedul
 * @since 2019-05-06
 **/
public class ProductPrice {

    private PriceCalculatorI priceCalculator;

    public ProductPrice(PriceCalculatorI priceCalculatorI) {
        this.priceCalculator = priceCalculator;
    }

    public int calculatorProductPrice(int price) {
        return priceCalculator.calculaterPrice(price);
    }
}

이렇게 되면 상품 가격을 구하는 calculatorProductPrice 고수준 모듈은 더이상 이벤트에 따라 저수준 모듈을 변경해야하는 이슈가 없어진다. 대신 PriceCalculatorI는 고수준 모듈에 속하는 인터페이스이기 때문에 이를 구현한 SpecialPriceCalculator의 경우 저수준 모듈이다.

SpecialPriceCalculator와 같은 저수준 모듈들이 PriceCalculatorI와 같은 고수준 모듈을 의존하게 되므로 DIP가 성립된다. DIP는 Dependency Inversion Principle, 의존 역전 원칙을 의미한다.

 

출처 : DDD Start 도메인 주도 설계 구현과 핵심 개념 익히기 (출판 지앤선, 저자 최범균)

'DDD' 카테고리의 다른 글

DDD. 응용서비스 코드 규칙과 트랜잭션 관리  (0) 2019.05.21
DDD. 애거리루트 정리  (0) 2019.05.16
DDD. DIP 의존 역전 원칙  (0) 2019.05.06
DDD. 도메인 주도 개발 시작  (0) 2019.05.03

도메인 모델


도메인 모델은 특정 도메인을 개념적으로 표현하는 것
도메인을 이해하려면 도메인이 제공하는 기능과 도메인의 주요 데이터 구성을 파악해야 한다.
도메인을 표한하는 방법은 Order, Ship, Pay와 같이 객체로 구별하는 방식과 상태에 따르게 방식이 진행되도록 설계하는 상태 다이어그램을 통해 모델링을 구현할 수 있다.
도메인 모델은 기본적으로 도메인 자체를 이해하기 위한 모델이다.

도메인 모델


일반적인 애플리케이션 아키텍쳐는 4단계 계층으로 구성된다.

 

Layer 설명
UI 사용자에게 보여주는 정보
Application 사용자가 요청한 기능이 실행됨
도메인 시스템이 제공할 도메인의 규칙을 구현
Infrastructure 데이터베이스나 메시징 시스템과 같은 외부 시스템과의 연동을 처리

 

도출한 모델에는 크게 Entity와 Value로 구분할 수 있다.

Entity


- 고유한 식별자를 가진다.
- 주문 도메인에서 각 주문은 주문번호를 가지고 이 주문번호는 각 주문마다 서로 다르다. 이 주문번호가 식별자이다.
- 식별자 생성시에는 특정 규칙에 따라 생성하거나 UUID를 사용하거나 직접 값 입력 또는 일련번호를 사용한다. 이 규칙은 모두 다르다.

public class Order {
	private int orderId;
}

 

Value


개념적으로 완전한 하나를 표현할 때 사용한다. 예를 들어, Value라는 클래스 내부에 valance와 totalUsedValue라는 필드가 있을 때 이 둘은 누가봐도 돈이라는 하나의 개념을 따른다. 이럴 때는 이 두 개를 Money라는 객체를 만들어서 그 자체로의 의미를 완전한 하나로써 표현할 수 있다. 이로써 이 들은 일반적인 integer 타입이 아닌 money라는 하나의 개념적인 의미가 생긴 것으로 코드를 이해하는데 도움이 된다.

public class Money {
    private int value;
}

또한 이런 value 타입은 그 자체로써 자체적인 기능을 추가할 수 있다. 세금이 포함된 금액을 확인하고 싶을 때 도메인에서 별도의 작업대신 value 객체에 기능을 추가하여 진행 할 수 있다.

public int getVatValue() {
    return this.value + (int) (this.value * 0.1);
}

만약 Value가 절대 변경되어서는 안되는 경우에는 내부에 값을 변경할 수 있는 메소드를 만들지 않아서 immutable하게 선언하면 된다.

 

도메인 모델에 set 메서드 넣지 않기.


기본적으로 객체를 만들 때 무의식적으로 getter와 setter를 만든다. 습관이다. 아니면 lombok을 통해서 그냥 기본적으로 만들때도 있다. 하지만 setter를 만들게 되는경우 바뀌지 말아야할 값들이 변경되어 문제가 되는 경우가 많다.

예를 들어보면 아래 새로운 계좌를 만든다고 가정해보자.

 public void createAccount(int valance, String accountNumber) {
    Account account = new Account();

    // 잔액 설정
    account.setValue(valance);

    // 계좌 설정
    account.setAccountNumber(accountNumber);
}

계좌 번호를 입력받는 accountNumber가 null인지 확인도 하지 않고 값을 넣게 되면 필수로 들어가야하는 계좌번호 값에 오류가 발생되어 시스템에 문제가 발생한다. 지금은 계좌번호 하나지만 다른 여러 값들을 이런식으로 setter를 통해서 집어넣게 된다면? 일일히 null 체크하기도 번거럽다. 그래서 setter 사용을 못하게 하고 생성자를 통해서 객체를 만들고 이때 잘못된 값으로 데이터를 만들려고 할 때 오류를 뱉어내게 하는 방법을 택하는 것 도 좋다.

// 계좌 생성
public Account createAccount(int valance, String accountNumber) throws Exception {
    return new Account(valance, accountNumber);
}

// Money 객체 생성자와 데이터 validation 체크
public Account(int value, String accountNumber) throws Exception {
        setValue(value);
        setAccountNumber(accountNumber);
    }

    private void setValue(int value) {
        this.value = value;
    }

    private void setAccountNumber(String accountNumber) throws Exception {
        if(StringUtils.isBlank(accountNumber)) throw new Exception("계좌번호가 없습니다.");
        this.accountNumber = accountNumber;
    }
}

이렇게 하면 외부에서 값을 임의적으로 바꾸지도 못하고 잘못된 값이 들어오는 것을 체크하기에도 좋다.
될 수 있으면 set은 외부에서 사용못하도록 불변된 습성을 가지는게 좋다.

 

도메인 용어 선정


개발을 진행하다보면 타입을 ENUM에 정의를 하는데 이 때 단순하게 public OrderState { STEP1, STEP2 }로 의미없이 지정하는 경우가 있을 수 있다. 하지만 이렇게 하면 나중에 유지보수나 개발에 어려움이 있기에 public OrderState { SHIPPED, DELIVERING }과 같이 의미있는 말을 적어야 한다.


알맞은 용어를 선택하는 것이 좋은 코드를 작성하는 것 이상으로 중요하다.

 

출처 : DDD Start 도메인 주도 설계 구현과 핵심 개념 익히기 (출판 지앤선, 저자 최범균)

 

'DDD' 카테고리의 다른 글

DDD. 응용서비스 코드 규칙과 트랜잭션 관리  (0) 2019.05.21
DDD. 애거리루트 정리  (0) 2019.05.16
DDD. DIP 의존 역전 원칙  (0) 2019.05.06
DDD. 도메인 주도 개발 시작  (0) 2019.05.03

+ Recent posts