[DDD] Golang을 사용하여 도메인 지식을 모형으로 표현

개시하다


Go Advent Calendar 2021 6일째 보도다.
DDD에 관해서는 엘릭 이판스 선생님과 선인의 귀신처럼 알기 쉬운 책이 있다
여기 읽는 것을 추천합니다.(웃음)
  • 도메인 제어 설계 모델링/설치 설명서
  • 도메인 드라이브 설계 샘플 코드 & FAQ
  • 도메인 이름 구동 설계 입문은 아래에서 위로 알 수 있습니다!도메인 구동 설계의 기본
  • 따라서 DDD에 대해서는 자세히 설명하지 않습니다.
    Golang 모형을 사용하여 도메인 이름 지식을 소프트웨어에 삽입하는 방법
    다음은 간단한 구체적인 예를 들어 설명한다.

    DDD(Domain Drive Design)란 무엇입니까?


    그럼에도 불구하고, 나는 역구동 설계를 간단하게 설명하겠다.
    DDD Reference의 정의는 다음과 같다.
    Domain-Driven Design is an approach to the development of complex software in which we:
  • Focus on the core domain.
  • Explore models in a creative collaboration of domain practitioners and software practitioners.
  • Speak a ubiquitous language within an explicitly bounded context.
  • This three-point summary of DDD depends on the definition of the terms, which are defined in this booklet.
    출처 URL: https://www.domainlanguage.com/ddd/reference/
    Videos from DDD Europe 2016에서 표현에 미묘한 차이가 있지만 정의는 다음과 같다.
    What is Domain Driven Design?
  • Focus on the core complexity and opportunity in the domain
  • Explore models in a collaboration of domain experts and software experts
  • Write software that expresses those models explicitly
  • Speak ubiquitous language within a bounded context
  • 이걸 일본어로 번역하면...
    1. 핵심 분야의 복잡성과 기회에 주목
    2. 지역 전문가와 소프트웨어 전문가의 합작 연구 모델을 통해
    3. 이 모델들을 명확하게 표현할 수 있는 소프트웨어를 쓴다
    4. 경계가 있는 상하문에서 보편적인 언어를 사용

    ソフトウェア開発者とドメインエキスパートが同じ言葉で認識をすり合わせて、
    ドメインモデルについて探求し続け、それらのモデルをソフトウェアに落とし込むこと
    
    DDD라고 할 수 있죠.
    (에릭 에반스 본인도 "명확하게 정의하기 어렵다"고 말했기 때문에 일률적으로 말하기는 어렵다.)

    도메인 이름 모델은 무엇입니까?


    이것도 DDD Reference에서 정의한 것이니 참고하세요.
    domain
    A sphere of knowledge, influence, or activity. The subject area to which the user applies a program is the domain of the software.
    model
    A system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain.
    다음은 DeepL 선생님의 의역입니다.
    도메인 이름:
    지식, 영향력 또는 활동 범위를 가리킨다.사용자 응용 프로그램의 객체 영역은 다음과 같습니다.
    응용 프로그램의 대상 구역은 그 소프트웨어의 영역이다.
    모델 번호:
    하나의 영역의 선택 영역을 설명하고 그 영역과 관련된 문제를 해결하는 추상적인 시스템
    ドメインは業務的な関心事、モデルはソフトウェアでそのドメインの課題解決する対象로 정의된다.

    무엇이 불변의 조건입니까?


    DDD 및 도메인 이름 모델 복습 중
    다음은 업무상의 제약을 고려한 토대에서 변하지 않는 조건을 접촉합시다.
    불변조건은 モデルが有効である期間中に、常に一貫している必要のある状態을 가리킨다.
    예를 들어, TODO 목록의 응용 프로그램을 예로 들 수 있습니다.
    요건에 대한 정의에 따라 우리는 다음과 같은 보편적인 조건을 만족시킬 필요가 있음을 발견했다.
  • 작업에 이름, 날짜 및 우선 순위가 있어야 합니다
  • .
  • 작업을 만들 때 완료되지 않은 상태로 변경
  • 퀘스트 완성 후 완성 상태가 되어 상태를 회복할 수 없음
  • 퀘스트 5회 연기, 하루
  • 임무명, 우선도를 변경할 수 없음
  • 그럼 이 변하지 않는 조건을 사용해서 코드에 실제로 들어갑니다.

    [실패 모드] 역지식을 표현하지 않는 모형


    우선 역지식을 표현할 수 없는 역층에서 실시한다.
    도메인 계층
    type task struct {
    	id            string
    	taskStatus    TaskStatus
    	name          string
    	dueDate       time.Time
    	priority      PriorityStatus
    	postponeCount int64
    }
    
    func NewTask() *task {
    	return &task{}
    }
    
    const POSTPONE_MAX_COUNT = 5
    
    type TaskStatus string
    
    const (
    	TaskStatusDoing TaskStatus = "doing"
    	TaskStatusDone  TaskStatus = "done"
    )
    
    type PriorityStatus string
    
    const (
    	PriorityStatusHigh   PriorityStatus = "high"
    	PriorityStatusMiddle PriorityStatus = "middle"
    	PriorityStatusLow    PriorityStatus = "low"
    )
    
    // setterを全項目分作成しちゃう
    func (t *task) SetID(id string) {
    	t.id = id
    }
    func (t *task) SetTaskStatus(taskStatus TaskStatus) {
    	t.taskStatus = taskStatus
    }
    func (t *task) SetName(name string) {
    	t.name = name
    }
    func (t *task) SetDueDate(dueDate time.Time) {
    	t.dueDate = dueDate
    }
    func (t *task) SetPriority(priority PriorityStatus) {
    	t.priority = priority
    }
    func (t *task) SetPostponeCount(postponeCount int64) {
    	t.postponeCount = postponeCount
    }
    
    // getter
    func (t *task) GetID() string {
    	return t.id
    }
    func (t *task) GetName() string {
    	return t.name
    }
    func (t *task) GetTaskStatus() TaskStatus {
    	return t.taskStatus
    }
    func (t *task) GetDueDate() time.Time {
    	return t.dueDate
    }
    func (t *task) GetDueDate() PriorityStatus {
    	return t.priority
    }
    func (t *task) GetPostponeCount() int64 {
    	return t.postponeCount
    }
    
    // とりあえず振る舞いをもたせたメソッドを作っておく
    func (t *task) CanPostpone() bool {
    	return t.postponeCount < POSTPONE_MAX_COUNT
    }
    
    됐다!
    뇌사 같은 느낌이 든다.웃다 웃다
    그러면 다음에 응용 프로그램 층을 설치합니다.
    type TaskApplication struct {
    	ctx      context.Context
    	taskRepo repository.TaskRepository
    }
    
    func (s *TaskApplication) CreateTask(name string, dueDate time.Time, priority domain.PriorityStatus) error {
    	if name == "" || dueDate.IsZero() {
    		return errors.New("必須項目が設定されていません。")
    	}
    	task := domain.NewTask()
    	task.SetTaskStatus(domain.TaskStatusDoing
    	task.SetName(name))
    	task.SetPriority(priority)
    	task.SetDueDate(dueDate)
    	task.SetPostponeCount(0)
    	if err := s.taskRepo.Save(ctx, task); err != nil {
    		return err
    	}
    	return nil
    }
    
    func (s *TaskApplication) PostponeTask(taskID string) error {
    	task, err := s.taskRepo.GetByID(ctx, taskID)
    	if err != nil {
    		return err
    	}
    	if !task.CanPostpone() {
    		return errors.New("最大延長回数を超過しています。")
    	}
    	task.SetDueDate(task.GetDueDate().Add(24 * time.Hour))
    	task.SetPostponeCount(task.GetPostponeCount() + 1)
    	if err := s.taskRepo.Save(ctx, task); err != nil {
    		return err
    	}
    	return nil
    }
    
    어쨌든 이렇게 하면 요구를 만족시킬 수 있다.
    그럼 발매할게요.
    다만, 반년 후 새로 연결된 엔지니어는 다음과 같은 코드를 실시했다.
    애플리케이션 계층
    func (s *TaskApplication) CreateDoneTask(taskID, name string, dueDate time.Time, taskStatus domain.TaskStatus, priority domain.PriorityStatus) error {
    	task := domain.NewTask()
    	task.SetTaskStatus(domain.TaskStatusDone) // 完了自体でタスクを作成
    	task.SetPostponeCount(-1)                 // カウントにマイナスを設定
    	if err := s.taskRepo.Save(ctx, task); err != nil {
    		return err
    	}
    	return nil
    }
    
    func (s *TaskApplication) ChangeTask(taskID, name string, dueDate time.Time, taskStatus domain.TaskStatus, priority domain.PriorityStatus) error {
    	task := domain.NewTask(
    	task.SetName(name)             // 変更しては行けないタスク名を変更してしまってる
    	task.SetPriority(priority)     // 変更しては行けない優先度を変更してしまっている
    	task.SetDueDate(dueDate)       // 勝手に期日を入力値で設定、延長回数も無視してしまってる
    	task.SetTaskStatus(taskStatus) // タスクを未完了に戻せてしまう
    	if err := s.taskRepo.Save(ctx, task); err != nil {
    		return err
    	}
    	return nil
    }
    
    불변의 조건이 무엇인지 의심스러울 정도로 파괴되었다.
    역지식이 공중에서 해체된 것 같아서 눈치채지 못했다.
    우선 응용 프로그램의 규격이 표현된 것 같다
    모든 항목에 Setter/Getter가 있기 때문에 작업의 모델 자체는 아무런 표현이 없습니다.
    이런 상태를 ドメインモデル貧血症라고 부른다.
    그렇다면 어떻게 해야만 이 빈혈증을 해소하고 변하지 않는 조건을 실장에 적용할 수 있을까.
    다음은 역지식을 표현할 수 있는 모형을 살펴보자.

    [성공 모드] 역지식을 나타내는 모형


    다음은 역층의 실현이다.
    type task struct {
    	id            string
    	taskStatus    TaskStatus
    	name          string
    	dueDate       time.Time
    	priority      PriorityStatus
    	postponeCount int64
    }
    
    const POSTPONE_MAX_COUNT = 5
    
    type TaskStatus string
    
    const (
    	TaskStatusDoing TaskStatus = "doing"
    	TaskStatusDone  TaskStatus = "done"
    )
    
    type PriorityStatus string
    
    const (
    	PriorityStatusHigh   PriorityStatus = "high"
    	PriorityStatusMiddle PriorityStatus = "middle"
    	PriorityStatusLow    PriorityStatus = "low"
    )
    
    // エンティティ作成時の不変条件を表現する
    func NewTask(name string, dueDate time.Time, priority PriorityStatus) (*task, error) {
    	if name == "" || dueDate.IsZero() {
    		return nil, errors.New("必須項目が設定されていません。")
    	}
    	return &task{
    		taskStatus:    TaskStatusDoing,
    		name:          name,
    		dueDate:       dueDate,
    		priority:      priority,
    		postponeCount: 0,
    	}, nil
    }
    
    // 作成済みエンティティの状態遷移に関する不変条件を表現する
    func (t *task) Postpone() (*task, error) {
    	if !t.CanPostpone() {
    		return nil, errors.New("最大延長回数を超過しています。")
    	}
    	t.dueDate.Add(24 * time.Hour)
    	t.postponeCount++
    	return t, nil
    }
    
    func (t *task) Done() {
    	t.taskStatus = TaskStatusDone
    }
    
    // name,priorityのsetterは存在しないので、name,priorityを変更することはできない
    
    // getter
    func (t *task) GetID() string {
    	return t.id
    }
    func (t *task) GetName() string {
    	return t.name
    }
    func (t *task) GetDueDate() time.Time {
    	return t.dueDate
    }
    func (t *task) GetDueDate() PriorityStatus {
    	return t.priority
    }
    
    func (t *task) IsDoing() bool {
    	return t.taskStatus == TaskStatusDoing
    }
    func (t *task) CanPostpone() bool {
    	return t.postponeCount < POSTPONE_MAX_COUNT
    }
    
    다음은 응용층이다.
    type TaskApplication struct {
    	ctx      context.Context
    	taskRepo repository.TaskRepository
    }
    
    func (s *TaskApplication) CreateTask(name string, dueDate time.Time, priority domain.PriorityStatus) error {
    	task, err := domain.NewTask(name, dueDate, priority)
    	if err != nil {
    		return err
    	}
    	if err := s.taskRepo.Save(ctx, task); err != nil {
    		return err
    	}
    	return nil
    }
    
    func (s *TaskApplication) PostponeTask(taskID string) error {
    	task, err := s.taskRepo.GetByID(ctx, taskID)
    	if err != nil {
    		return err
    	}
    	postponedTask, err := task.Postpone()
    	if err != nil {
    		return err
    	}
    	if err := s.taskRepo.Save(ctx, postponedTask); err != nil {
    		return err
    	}
    	return nil
    }
    
    응용층의 설치가 아주 간단해졌네요.
    그리고 업무적인 관심사가 역층에 집중되어 있잖아요.
    불변의 조건을 다시 봅시다.
  • 작업에 이름, 날짜 및 우선 순위가 있어야 합니다
  • .
  • 작업을 만들 때 완료되지 않은 상태로 변경
  • 퀘스트 완성 후 완성 상태가 되어 상태를 회복할 수 없음
  • 퀘스트 5회 연기, 하루
  • 작업 이름, 우선 순위를 변경할 수 없음
  • 응!잘, 대역층의 보편적인 조건이 코드에 버려져 있다.
    이런 디자인을 통해서.
    Task 모델의 변경되지 않은 조건은 도메인 레이어의 구현만 봐도 알 수 있습니다.
    어떤 코드를 쓰든지 응용 프로그램의 변하지 않는 조건을 파괴하는 것은 실현될 수 없다.
    이게 モデルがドメインの知識を表現しているということができている죠.

    총결산


    DDD
    1. 핵심 분야의 복잡성과 기회에 주목
    2. 지역 전문가와 소프트웨어 전문가의 합작 연구 모델
    3. 이 모델들을 명확하게 표현할 수 있는 소프트웨어를 쓴다
    4. 경계가 있는 상하문에서 보편적인 언어를 사용
    역지식을 코드로 표시하려면
    다른 엔지니어가 도메인 지식을 알지 못하더라도 도메인 계층으로 변경되지 않는 조건 집합
    소프트웨어 표현 상태를 목표로 하다より複雑なモデルを用いて本質的な課題解決に焦点を当てた開発ができる꺼 아니에요?
    조금만 참고해 주시면 감사하겠습니다.
    당신의 평론과 피드백을 기대합니다.

    참고 문헌

  • little hands'lab-역 구동 디자인 전수
  • 도메인 제어 설계 모델링/설치 설명서
  • 도메인 드라이브 설계 샘플 코드 & FAQ
  • 도메인 이름 구동 설계 입문은 아래에서 위로 알 수 있습니다!도메인 구동 설계의 기본
  • 엘릭 이판스의 영역 구동 설계
  • 실천 영역 구동 설계
  • 좋은 웹페이지 즐겨찾기