[도메인 주도 개발 시작하기] 1. 도메인 모델 시작하기

Jermaine ·2023년 8월 26일
0
post-thumbnail

도메인이란?

  • 도메인이 무엇이 의미하는지 먼저 이해하고 가야한다.
    • 처음의 나로서는 그저, “비즈니스 영역의 관심사”, 혹은 “소프트웨어가 해결해야하는 문제 영역”이라고 생각이 된다.
    • 개발자 입장에서의 도메인은 구현해야할 소프트웨어의 대상을 도메인이라고 하며, 이는 곧 기술적으로 해결해야할 문제의 영역이다.
      • 몇몇의 책에서는 도메인 전문가가 존재하는 영역을 도메인이라고 정의하기도 한다.
  • 즉, 개발하고자 하는 소프트웨어를 하나의 큰 도메인은 여러 하위 도메인으로 분리할 수 있으며 각 도메인은 독립적인 기능을 제공하기도 하고, 다른 도메인과 함께 하나의 완전한 기능을 제공하기도 한다.
    • 그렇다고 해서, 특정 도메인이 요구하는 모든 기능을 직접 구현하는 것은 아니다. 때로는 외부의 기 구현된 시스템을 이용하기도 한다.
  • 여러 하위 도메인을 어떻게 구성해야할 지에 대한 고민과 그의 결과는, 상황에 따라 달라지며, 이러한 상황을 종합적으로 고려하여 적당히 잘(제일 어려운 말이지만) 구성해야한다.

도메인 전문가와 개발자 간 지식 공유

  • 도메인을 구성하고, 설계하는 과정에 있어서, 당연하게도 도메인 전문가가 전달해주는 의견과 요구사항이 중요하다.
    • 당연하게도, 금융지식이 없는 내가 여신 도메인, 수신 도메인에 대한 요구사항과 해당 도메인의 특성을 전문가만큼 아는 것은 신기한(?) 경우에 해당된다.
  • 도메인 전문가와 그의 이해 관계자가 없이 개발자가 요구사항을 분석하고, 이에 대한 구현을 진행하다고 생각해보면 끔찍하다. 대부분의 경우는 요구사항을 잘 분석하지 못하여, 코드를 수정하고, 배포를 다시해야하기 때문이다.
    • 코드 수정은 매우 귀찮고, 심한 경우에는 다시 짜는게 오히려 공수가 덜 들어갈 때도 있다.
  • 그렇기 때문에, 다시 한 번 강조하지만, 요구사항을 올바르게 이해하는 것이 중요한데, 제일 효과적인 방법은 도메인 전문가와 이해관계자와 직접 소통하는 것이다.
    • 중간에 전달자가 많아지면, 의도와 요구사항이 이상하게 전달되거나 정보가 손실 될 수 있기 마련이다.
      • 햄이 햄스터가 되는 기적을 볼 수 있다.
    • 그렇기에 개발자도 도메인에 대한 어느정도의 지식은 필요하다.
      • 공부는 끝이 없기 마련이다.. 어찌보면 행복한 일이다.
  • 즉, 우리 개발자는 도메인 전문가와 원활하게 직접 소통하고 함께 요구사항을 정립해야 도메인이 요구하는 좋은 품질의 소프트웨어를 개발할 수 있다
    • 특히, 도메인 전문가의 의도와 목적을 인지하게 될 경우, 요구사항을 그에 맞춰 함께 조율 할 수 있다(의도와 목적에 맞게 우리가 역제안을 할 수 있다).

도메인 모델

  • 도메인 모델은 특정 도메인을 개념적으로 표현한 것이다.
    • 특정 도메인에서 요구하는 기능과 그에 필요한 데이터들을 표현한 것이라고 생각하면 될 것 같다.
    • 도데인 모델을 통해 도메인 전문가를 비롯한 여러 관계자들이 동일한 이해를 공유할 수 있고, 이는 도메인 지식을 공유하는데에도 도움이 된다.
    • 도메인을 표현하는 데에는 객체 모델, 상태 다이어그램 등 여러 표현 기법이 있지만, 각 도메인에 맞게 절절한 표현 방식을 취사선택하면 된다.
  • 도메인 모델은 도메인에 대한 이해를 위해 작성하는 개념 모델일 뿐, 이를 곧바로 코드로 옮길 수는 없다.
    • 개념 모델을 통해 이해한 도메인을 코드로 옮기기 위한 구현 모델이 따로 필요하다.
    • 구현 모델과 개념 모델은 상이한 것이지만, 구현 모델과 개념 모델이 최대한 맞춰지도록 할 수 있다.
      • 처음부터 완벽한 개념 모델은 없지만, 구현을 진행하다 보면, 개발자들도 도메인에 대한 통찰이 늘어나기 마련이다. 이처럼 초기 개념모델은 도메인에 대한 전반적인 윤곽을 이해하는데 집중하는데 사용하고, 점진적으로 발전시키는 것이 좋다.
  • 각 하위 도메인이 다루는 영역은 서로 다르다. 그렇기 때문에 같은 용어라도 각 도메인마다 사용되는 용도와 목적이 달라지는 경우가 있다. 그렇기 때문에 여러 하위 도메인을 하나의 개념 모델 안에서 표현하는 경우는 최대한 지양해야한다.
    • 예를 들어, 카탈로그 도메인의 상품은 상품의 정보를 담고 있는 객체이고, 배송 도메인의 상품은 현재 고객에게 배송되는 객체를 의미하는 것과 같이, 같은 용어여도 도메인 별로 다른 의미를 가지고 있는 경우가 있다.

도메인 모델 패턴

  • 일반적인 어플리케이션의 아키텍쳐는 위의 그림과 같다.

    계층설명
    표현 UI(Presentation)외부의 요청을 처리하고, 정보를 노출시킨다.
    응용 Application요청한 기능을 실행하며, 도메인 계층을 조합하여 기능을 실행한다.
    도메인 Domain시스템이 제공할 도메인 규칙을 구현한다.
    인프라스트럭쳐 Infrastruture데이터베이스나 외부 메세징 시스템과의 연동을 처리한다.
  • 도메인 모델 패턴은 아키텍쳐 상의 도메인 계층을 객체지향 기법으로 구현하는 패턴을 의미한다.

    • 도메인 계층은 도메인의 핵심 규칙을 구현하는 책임을 갖는다.
  • 즉, 도메인의 데이터를 변경하는 기능이나, 도메인이 요구하는 기능은 해당 도메인 영역 내에서 구현되어야 한다는 것을 의미한다.

    • 특정 도메인의 핵심 규칙을 해당 도메인 내에서 구현한다면, 규칙의 변경 혹은 확장에 유연하고 안전하게 대처할 수 있다.

도메인 모델 도출

  • 도메인에 대한 이해 없이 개발을 진행한다는 것은 아래와 같이, 수저없이 밥을 먹는 것과 똑같다.

  • 그렇기 때문에, 우리는 기획서, 유스케이스, 사용자 스토리와 같은 요구사항과 관련자와의 대화, 합의 를 통해 도메인을 이해해야하며, 개념적으로 도메인 모델의 초안이 완성되어야 개발을 할 수 있다.
    • 방법이 어떻든간에, 도메인에 대한 초기 모델이 필요하다.
    • 관련자와의 대화와 합의가 없이 서로 다른 이해를 가지고 업무를 진행하여, 숨쉬듯 나오는 남탓의 현장을 목격한 적도 있다.
  • 도메인을 모델링할 때, 가장 기본이 되는 작업은 모델을 구성하는 핵심 구성요소, 규칙, 기능을 찾는 것이다. 이는, 요구사항을 탐색하는 과정으로 부터 시작한다.
  • 단적으로 도메인의 요구사항 탐색을 통해 도메인을 모델링을 하는 방법은 다음과 같다.
    • 요구사항으로부터 도메인이 제공해야하는 기능을 도출한다.
      • 상세한 구현까지는 아니더라도, 도메인 모델에 관련 기능을 메서드로 추가할 수 있다.
    • 요구사항으로부터 도메인이 어떤 데이터로 구성되어 있는지 파악한다.
      • 이를 도메인 모델의 멤버 변수로 선언할 수 있다.
      • 더불어, 해당 멤버 변수를 계산하는 메서드, 생성자, getter, setter를 선언할 수 있다.
    • 요구사항으로부터 도메인 모델의 제약과 규칙을 도출한다.
      • 해당 제약과 규칙을 표현할 수 있는 형태로 멤버 변수와 메서드, 생성자, getter, setter를 정의할 수 있다.
      • 또한, 이를 검증할 수 있는 검증 메서드를 선언할 수 있다.
    • 요구사항으로부터 도메인이 표현해야하는 상태를 도출한다.
      • 도메인이 가질 수 있는 상태들을 열거 타입으로 상태정보를 표현할 수 있고, 상태에 대한 변경을 수행하는 메서드를 정의할 수 있다.
      • 도메인의 상태를 변경 시 관여하는 제약과 규칙을 검증하는 메서드를 선언할 수 있다.
  • 이와 같이, 도메인 모델링은 요구사항으로부터 차근차근 점진적으로 만들어나가야 하며, 상세한 구현까지는 필수적이지 않다.
    • 도메인 모델링의 결과물을 문서화를 진행하고, 작성된 문서는 관련자와 도메인 전문가들과의 소통에서 공유되어, 논의 과정에 도구로써 활용되기도 한다.

엔티티와 밸류

  • 요구사항으로부터 도출한 도메인 모델은 크게 엔티티밸류로 구분할 수 있다.
    • 이들을 정확히구분해야 올바르 설계와 구현이 가능하다.

엔티티(Entity)

  • 엔티티는 식별자를 가지는 객체다.
    • 객체는 논리적/물리적으로 구분가능하고 식별가능한 단위를 의미한다.
    • 즉, 고유 식별자를 통해 도메인 내의 엔티티들을 구분할 수 있으며, 식별자가 같으면 같은 엔티티, 다르면 다른 엔티티가 된다.
      • JAVA에서 클래스의 equals(), hashCode() 메서드를 구현할 수 있다.
    • 엔티티 별로 고유한 식별자를 가지기 때문에, 식별자는 한번 설정되면, 엔티티 내부의 상태, 속성이 바뀌어도, 해당 엔티티가 소멸될 때 까지 유지된다.
  • 엔티티의 식별자의 생성 시점과 생성 방식은 도메인의 특징과 사용하는 기술에 따라 달리지는데, 보통 아래와 같은 방식 중 하나의 방식으로 생성이 이루어진다.
    • 특정 규칙에 따라 생성
      • 흔히, 다른 값과 현재 시간의 값을 조합하여 생성
    • UUID 혹은 Nano ID와 같은 고유 식별자 생성기를 사용
      • 다수의 개발언어에서 지원하는 UUID 생성기 혹은 Nano ID 를 사용한다.
    • 값을 직접 입력
      • 회원의 아이디 혹은 이메일과 같은 값을 이용해 식별한다. 이때, 중복에 대한 검증이 필수적으로 수반되어야한다.
    • 일련번호를 사용(시퀀스, DB의 자동 증가 컬럼)
      • 자동 증가 컬럼은 주로 데이터베이스가 제공하는 auto-increment기능을 사용하여 식별자를 생성한다.
        • 이 방식의 단점은 DB에 엔티티를 삽입해야 해당 엔티티의 식별자를 알 수 있다는 것이다.
      • 식별자를 생성해주는 시퀀스를 만든 후, 엔티티 객체를 생성할 때, 주입하는 방식으로도 생성한다.

밸류 타입(Value Type)

  • 밸류 타입는 개념적으로 완전한 하나를 표현할 때 사용한다.

    • 이게 뭔 소리인가 싶을 것이다. (사실 위의 정의만 보고 밸류 타입을 이해했으면, 아이스크림 하나만 사주십쇼 대박!)
    • 예를 들어 주문정보 엔티티에는 받는 사람의 이름, 받는 사람의 전화번호와 같이 받는 사람으로 표현된 도메인 개념이 가지고 있는 정보들이 있을 것이다.
    • 이를 하나의 밸류 타입인 Receiver 타입으로 묶어 도메인 개념을 표현한다면, 이러한 산재될 수 있는 데이터를 개념적으로 완전한 하나로 표현할 수 있다(아하! 굳).
    • 이렇게 개념적인 데이터들을 하나의 밸류 타입으로 표현하면, 해당 밸류타입을 위한 기능을 메서드로 추가할 수 있다는 장점이 있다.
    • 두 밸류 타입 객체를 비교할 때는 모든 속성이 같은지 비교하면 된다.
  • 밸류 타입은 내부의 필수적으로 여러 개의 데이터를 가지고 있어야하는 것이 아니다. 의미를 명확하게 표현하기 위해서 하나의 데이터를 밸류 타입으로 사용하는 경우도 있다.

    • 예를 들어 Interger money라고 되어 있는 데이터를 Money money로 표현한다면, 의미가 더욱 명확하고, 직관적으로 다가올 것이다.
      • 이러한 방식은 예전에 TS로 개발할 때는 매우 많이 사용했던 것 같은데, 지금 회사에서는 많이 사용하고 있는 편은 아닌 것 같다. 취향차이인가 싶기도 하고,,,
  • 또한, 밸류 타입의 객체의 내부 데이터를 변경할 때에는기존의 데이터를 변경하기보단, 변경한 데이터를 가지는 새로운 밸류 타입 객체를 생성하는 방식이 선호된다.

    • 즉, 하나의 밸류 타입 객체는 불변(Immutable)의 성격을 띄어야한다.
      • 참조 투명성 부터 안전한 코드, thread-safe한 코드를 작성하기 위해,
      • setter를 선언하지 말고, 생성자를 통해서만 필드 주입이 가능하도록 해야한다.

    엔티티 식별자는 대부분 숫자(Long, Integer), 혹은 문자열(String)이다. 하지만, 보통의 숫자, 문자열과는 다르게 특별한 의미를 지니는 경우가 많기 때문에, 식별자를 위한 밸류 타입을 사용해주면 식별자의 의미가 더욱 잘 드러나게 할 수 있다. 이를 통해 필드 이름 만으로는 해당 필드가 어떤 필드인지 모호했던 문제가 해결될 가능성이 높다.


습관적인 setter 추가 멈춰…!

  • 도메인 모델에 setter를 습관처럼 추가하는 것은 바람직하지 못하다.
    • 대부분의 프로그래밍 입문 예제 코드에서는 대부분 무지성 습관적으로 setter를 객체에 추가한다.
    • 물론 해당 예제 코드에 대해서는 객체 멤버에 접근하기 위한 방법으로서 추가하고 있기는 하지만, 지양해야하는 습관이다.
  • 이유는 setter는 도메인의 핵심 개념이나 의도를 코드에서 알아보기 어렵게 한다.
    • setter는 어떤 객체의 상태를 “설정한다.”라는 의미가 강하지 “변경한다”라는 의미는 약하기 때문이다.
    • 즉, 도메인 로직을 표현하고 구현해야하는 입장에서, setter와 같이 단순히 특정 상태값만을 설정하는 것은 새로이 설정된 상태값에 따른 다른 부수적인 도메인 로직을 함께 구현하기 어렵기 때문이다.
    • 메서드는 도메인이 요구하는 기능을 구현하는데 초점을 두어야하지, 도메인의 내부의 데이터를 변경하는데에 초점을 두어서는 안된다는 것이 요지인 것 같다.
  • 또 다른 이유는 setter는 도메인 객체를 생성할 때, 온전하지 않은 상태가 될 수 있다는 점이다.
    • 만약 생성자에서 초기화 하지 않은 도메인 객체의 필드가 있다면, 별도로 setter를 호출하여 필요한 값을 설정해주어야한다.
    • 즉, 도메인 객체가 불완전한 상태로 사용되는 것을 막기 위해서는 생성자를 통해 필요한 값들을 모두 전달해주고, 객체 내부에서 해당 필드를 설정할 경우에는 private setter를 통해 진행하면 된다.
  • DTOgetter/setter
    • DTO(Data Transfer Object)는 표현계층과 도메인계층이 서로 데이터를 주고 받을 때 사용하는 구조체다.
    • 예전에는 private 필드를 변경해야하는 경우가 있어서 getter, setter가 필요했다.
    • 요즘 개발되는 프레임워크에서는 직접 private 필드에 값을 할당해주는 기능이 많이 제공되기 때문에, setter를 별도로 만들지 않아도되어 불변 객체로 사용할 수 있다.

도메인 용어와 유비쿼터스 언어

  • 개발자는 도메인에서 사용하는 용어(도메인 용어)에 대해서 이해가 있어야하고, 이를 코드에 반영해야한다.
    • 관련자와의 소통에 있어서 굳이 도메인 용어와 코드 사이의 해석 과정이 줄어들고, 가독성을 높이는 작업이기 때문에, 개발 유지보수 비용도 감축시킬 수 있다.
    • 또한, 개발자가 의미를 변환하는 과정에서 발생하는 버그 또한, 매우 감소하게 된다.
    • 만약 도메인 용어를 코드에 반영하지 않는다면, 도메인이 표현하고자 하는 바를 코드로 구현하기 어렵고, 의미 또한 매우 모호해지기 때문이다.

  • 이 책을 공부하고 다음에 공부해야하는 책(도메인 주도 설계)의 저자인 에릭 에반스는 이를 강조하기 위해 유비쿼터스 언어 라는 용어를 사용했다.
    • 모든 관계자(전문가, 개발자, 기획자 등)가 도메인과 관련된 공통의 언어를 만들고 이를 통일시켜야하며, 이를 대화, 문서, 도메인모델, 코드, 테스트 등에서 모든 같은 용어를 공유해야한다는 것이다.
    • 용어의 모호함에서 기인하는 소통 문제를 줄이고, 개발자의 도메인-코드 사이의 불필요한 해석 문제를 해소할 수 있기 때문이다.

  • 사실 이러한 도메인 용어는 시작 시점에 딱! 결정되는 것이 아니라, 점점 도메인에 대한 이해가 높아지면서 더욱 적합한 용어가 탐색이 되고, 다시 공통의 언어로 반영되는 과정을 반복하게 된다.
    • 당연히 산출물은 최신 상태를 유지해야한다.
  • 한국인 개발자는 특히나,, 도메인 용어를 영어로 해석하고 필드, 클래스, 메서드 이름을 정의하는데, 이 과정에서 도메인과 어울리는 명명(네이밍)을 찾기 위해 노력을 많이 쓴다. 좋은 현상이다.
    • 사실 전 회사에서도 그렇고, 지금 회사에서도 네이밍으로 인한 시간을 많이 쓰는데, 이는 협업하는 개발자들과의 끝나지 않은 회의의 주범이기도 한데, 당연히 필요하고 중요한 과정이니 다들 열심히 참여한다.
profile
저메인 주도 개발

0개의 댓글