https://www.youtube.com/watch?v=dJ5C4qRqAgA

우아한 형제들에서 진행한 우아한 객체지향 세미나에 가고 싶었는데 아쉽게도 가지 못했다. 발표해주시는분이 객체지향의 사실과 오해를 쓰신 조영호분이라서 더 가보고 싶었는데 아쉽다. 책에 내용이 좋아서 동영상으로라도 보고 싶었는데 유튜브에 동영상이 올라와서 보고 정리해본다.

 

개념

- 설계는 코드에 어디에 놓을건지를 정하는 것.

- 의존성 문제의 핵심은 코드 변경시 영향을 주는지이다.

- 의존성 문제는 디커플링이 되어야 한다.

 

관계설명

연관관계 (Association)

A 클래스에 B클래스로 갈수 있는 영구적인 방법이 있는 경우

A → B

class A { 
	private B b;
}

 

의존관계 (Dependency)

A ---> B (파라미터, 리턴타입, 지역변수 등등)

일시적으로 관계를 맺고 사라지는 관계

class A { 
	public B method(B b) { 
		return new B(); 
	}
}

 

상속관계 (Inheritance)

A → B

부모에 구현이 바뀌면 영향을 받을 수 있음.

public class A extends B

 

실체화 관계 (Realization)

인터페이스에 오퍼레이션 시그니처가 변경되었을때만 영향을 받음

A ---> B

class A implements B

 

효율적인 객체 설계법

1. 양향향 의존성을 피하라

  • 순환참조를 피하라. 오직 단방향 참조를 하라.
  • 다중성이 적은 방향을 선택하라.
  • 1대다 보다 다대1로 설계하라. (아래 예시)
class A { 
	private Collection<B> bs;
}
보다

class B { 
	private A a
}
로 사용하라.

 

2. 제일 좋은건 의존성을 줄여라.

  • 관계가 있다는 것은 파라미터, 또는 인스턴스 변수등에서 전달되는 사이가 있다.
  • 관계에는 방향성이 있어야 한다.
  • 협력의 방향, 의존성의 방향
  • 상속관계랑 실체화 관계의 경우 명확하고 대부분이 연관, 의존 관계가 대부분
  • 데이터 흐름의 의존적일 수 있다.
  • 영구적인지, 일시적인지 여부에 따라서 의존관계 연관관계를 지정
  • 관계는 런타임에 어떤 연관을 가지는지에 따라 달라진다.

 

3.  연관관계 정의

  • 이 객체를 알면 다른 객체를 찾아갈수 있어요가 연관관계의 정의
  • 설계를 할때 의존관계를 손으로 그리고 코드를 작성한다. => 잘못된 패키지 참조가 발생되나?
  • 도메인에 나눠서 패키지를 구성하라.
  • 레이어 구현은 패키지
  • 패키지내부에 어떤 레이어로 나눌건지 패키지 구성
  • 의존성 역전 원칙
  • 패키지 싸이클은 절대 돌면 안된다.

 

대표적인 연관성 문제 해결방안

1. 객체 참조

  • 객체 참조로 List<B> b로 데이터를 가지고 있으면 메모리에 있을때 큰 이슈는 없으나 orm을 통해 데이터를 읽을 때 문제의 소지 발생 가능 (N+1 문제 등등)
  • 객체의 연관성이 너무 얽혀있음.
  • 객체 참조로 많은걸 수정하면 롱 트랜잭션이 생겨서 트랜잭션의 경계가 모호해짐.
  • 객체의 연관성이 높아지면 트랜잭션이 포인트가 커져서 트랜잭션의 문제가 발생
  • 객체 참조는 연관성이 너무 크기 때문에 필요하다면 다 끊어야한다.

  • 모든 객체 참조가 불 필요한 건 아니다. 함께 생성되고 함꼐 삭제되는 객체는 묵어라. 다시 말해 도메인 제약 사항을 공유하는 객체들은 묶어라. (도메인 관점으로 묶어라). 가능하면 분리하라 
  • 아래 같은 경우처럼 같은일은 하는 Order, OrderLineItem, OrderOptionGroup, OrderOption의 연관관계는 유지하고 Shop은 연관관계를 끊고 shopId를 보유하고 그 Id를 이용해서 데이터를 탐색하라.

  • 같은 객체와 연관관계가 있는 객체는 같은 트랜잭션 레벨에서 관리하면 된다. (같은 객체에 정보는 한번에 쿼리로 가져온면 된다.)

 

2. 객체지향보다 절차지향이 더 좋을때도 있다.

만약 연관관계가 있어서 사용하다가 연관관계를 끊으면서 문제가 생기면 그 부분을 별도의 로직으로 분리한다. 예를 들어 Order에서 validation을 밖으로 빼어 OrderValidatiuon을 만들면 Order에서 Shop, Menu등에 대한 연관관계가 필요없어진다. 그리고 Order에 별도의 validation 코드가 있다보면 응집도가 높은 코드들이 아닌 코드들이 같이 있기 때문에 통일성이 부족해진다. 꼭 객체안에 validation을 체크하는 로직이 같이 있을 필요는 없다.

 

3. 문제상황

의존성을 없애기 위해서 Shop객체가 사라지고 shopId가 생기면서 배달완료되고 배달료에 부여하는 코드에서 컴파일 오류가 발생된다.

이 부분을 validation처리 처럼 절차지향으로 변경하거나 도메인 이벤트 퍼블리싱으로 해결할 수 있다.

 

1) 절차지향 방식 사용

OrderDeliveredService를 사용해서 orderId를 받아서 Shop에 비용을 부과함으로써 Order에서 Shop의 연관성을 제거할 수 있다. 

하지만 DorderDeliveredService에 Order, Shop, Delivery를 사용하기 때문에 의존성이 다시 생긴다.

그래서 Interface를 이용해서 의존성을 해결해주면 된다. 

 

2) Domain Event를 이용한 의존성 제거

스프링에서 제공하는 Domain Event를 이용하여 특정 이벤트가 발생했을때 기능을 발생시키도록 하면 의존성없이 해결할 수 있다. 간단하게 AbstractAggregateRoot를 상속받아서 registerEvent를 통해 이벤트를 등록하면 핸들러가 처리한다. 실제로 배달의 민족의 대부분의 서비스가 다음과 같은 방식으로 이루어져있다고 한다.

이벤트가 발생하면 handler에서 이벤트를 받아서 비동기로 데이터를 처리하면 끝.!

 

직접 듣는게 아니라 녹화된 방송으로 들어서 정리하고 이해하면서 들을 수 있어서 더 좋았다. 다음에는 실제로 참석해서 들어보고 싶다. 개발하면서 지켜가면서 개발을 해봐야겠다.

도메인


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

도메인에서 담당하는 역할은 도메인 내부에 있어야한다. 특히 도메인의 데이터를 조작하는 경우에는 도메인을 사용하는 응용서비스 영역에 배치되게 되면 응용서비스 영역에서 사용할 때마다 중복코드가 발생할 간으성이 크다. (도메인 관련 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 도메인 주도 설계 구현과 핵심 개념 익히기 (출판 지앤선, 저자 최범균)

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

애그리거트는 비슷한 속성을 가진 객체를 묶어놓은 것을 의미한다.
예를 들어 주문 시스템에 주문 관련 애그리거트는 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 도메인 주도 설계 구현과 핵심 개념 익히기 (출판 지앤선, 저자 최범균)

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

이를 해결하기 위해서 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 도메인 주도 설계 구현과 핵심 개념 익히기 (출판 지앤선, 저자 최범균)

도메인 모델


도메인 모델은 특정 도메인을 개념적으로 표현하는 것
도메인을 이해하려면 도메인이 제공하는 기능과 도메인의 주요 데이터 구성을 파악해야 한다.
도메인을 표한하는 방법은 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 도메인 주도 설계 구현과 핵심 개념 익히기 (출판 지앤선, 저자 최범균)

 

+ Recent posts