DDD로 개발할 때 알아야 할 4가지 기본사항

20016 단어 TypeScriptDDDtech
DDD로 개발하려면 입문서도 배우고 이해하려고 해도 코드를 쓰려면 실장한 인상을 주기 어려워 손이 멈춘다.적어도 나는 이렇다.
이 글에서 저는 먼저 DDD의 모델링 부분을 한쪽에 놓고 코드를 실현하는 토대에서 비교적 좋은 것을 알고 소개하고자 합니다.이것들은 모두 기본적인 내용이기 때문에 DDD를 장악한 사람은 새로운 발견이 없을 수도 있다.
또한 예로 사용되는 언어는 Type Script입니다.

DDD로 설치해봤는데 뭐가 좋을까


주제에 들어가기 전에 먼저 DDD에서 실현되는 동력을 높이기 위해 실제 DDD에서 실현되는 몇 가지 장점을 열거했다.
  • 논리를 쓰는 데 고민하지 않는 곳·팀워크 통일 가능
  • 예를 들어 MVC 등 준비되지 않은 계층의 구조를 사용해 실현하면 논리를 어디에 쓰느냐에 대한 고민이 생긴다.결과적으로 팀이 개발하면 특히 사람에 따라 논리를 쓰는 곳이 영락해 통일감이 부족한 코드가 된다.다른 한편, DDD로 이루어지면 우선 역층으로 쓸 수 없나요?이런 사고는 논리의 분산을 방지하는 작용을 할 수 있다.
  • 쓰기 쉬운 영역 층 테스트
  • 역층의 테스트는 쓰기가 너무 좋아서 점점 테스트를 쓰고 싶어진다.역층의 코드는 어느 곳에도 의존하지 않기 때문에 사전에 준비할 필요가 거의 없고 테스트하고 싶은 일에 집중할 수 있다.
  • 용례는 이해하기 쉽다
  • 용례에 논리를 기입하면 전망이 나빠져 전체적으로 무엇을 하고 있는지 모르겠다.DDD에서 용례는 주로 역 대상 방법을 호출하는 간단한 처리이기 때문에 코드만 보면 무엇을 하는지 똑똑히 알 수 있다.
  • API의 끝점은 ORM 솔리드에 의해 좌우되지 않습니다.
  • 예비역 대상의 실현에 있어 ORM의 실체를 중심으로 DB의 데이터만 회수하고 반환, 등록하는 API가 되기 쉽다.물론 단순한 요건이라면 그럴 수도 있지만 조건이 복잡해지면 호출자의 처리가 필요한 것보다 더 복잡해진다.DDD에서는 ORM의 실체 외에 상업적 의미의 집합을 대상으로 설계되기 때문에 호출자 처리가 필요 이상으로 복잡한 현상이 발생하기 어렵다.
  • 대체로 안 좋아요?눌러도 써.
  • 높은 학습 비용
  • 데이터의 재배열 처리 코드량 증가
  • 업데이트 시 처리 성능 저하
  • 기본적으로 DDD는 성능보다 데이터의 통합성을 중시한다.특히 업데이트 시 데이터를 한 번에 업데이트하는 것이 아니라 먼저 select 후 업데이트하면 발매되는 SQL 자체가 늘어난다.
  • 파악해야 할 4가지 기본 사항


    1. 도메인 레이어에서 ORM 솔리드와 다른 솔리드 준비


    도메인 객체는 기본적으로 솔리드로 표시됩니다.실체를 듣자마자 ORM 실체가 생각날지도 모르지만 전혀 별개의 일이다.ORM의 실체는 특정한 프로그램 라이브러리, 예를 들어 TypeORM에 의존하지만 역층의 실체는 평면 대상이기 때문에 어떤 것도 의존할 수 없다.ORM의 실체에 논리를 쓰지 말고 역층을 위한 다른 실체를 준비하는 것을 포기하세요.
    다음은 간단한 예이다.
  • 도메인 레이어 솔리드
    domain/entity/User.ts
    export default class User {
      id: number;
      familyName: string;
      givenName: string;
    
      constructor(id: number, familyName: string, givenName: string) {
        this.id = id;
        this.familyName = familyName;
        this.givenName = givenName;
      }
    }
    
  • ORM 솔리드
    infra/entity/User.ts
    import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
    
    @Entity()
    export default class User {
      @PrimaryGeneratedColumn()
      id: number;
    
      @Column({ type: "varchar", default: "" })
      familyName: string;
    
      @Column({ type: "varchar", default: "" })
      givenName: string;
    
      constructor(id: number, familyName: string, givenName: string) {
        this.id = id;
        this.familyName = familyName;
        this.givenName = givenName;
      }
    }
    
  • 2. 창고 획득 방법으로 필드 대상으로 바꾸고 값을 되돌려줍니다


    저장소에서 시스템을 가져오는 방법(find 등)은 DB에서 얻은 값을 직접 되돌려주는 것이 아니라 도메인 대상으로 변환해서 되돌려주는 것이다.따라서 창고에서 값을 받는 용례는 역 대상을 즉시 호출할 수 있습니다.다음은 예이다.
    infra/repository/User.ts
    public async findById(id: number): Promise<UserEntity | undefined> {
      const user = await this.repository.findOne({ id }); // this.repositoryはTypeORMのリポジトリ
      if (user == null) return;
    
      return this.makeEntity(user);
    }
    
    // DBから取得した値をドメインオブジェクトに詰め替える処理
    makeEntity(infraUser: User): UserEntity {
      const user = new UserEntity(
        infraUser.id,
        infraUser.familyName,
        infraUser.givenName
      );
      return user;
    }
    

    3. 대상과 창고는 일대일의 관계가 아니다


    2의 설명을 보면 기본적으로 역 대상과 창고가 1대1로 연결되어 있다고 오해할 수 있지만 그렇지 않다.창고와 1대1의 연관성은 집약적인 개념이다.집합이란 교과서에서'통합성이 필요한 대상의 집합'이다.
    상세한 설명은 생략하고 예를 들어 설명하다.
    다음과 같은 경우 Article(글) 엔티티·Section(장) 엔티티는 개별적으로 존재할 수 없으며 그룹화를 전제로 해야 하는 경우 동일한 집합으로 그 집합 단위로 창고를 만들 수 있다.

  • 전체 경로 Artical 엔티티
    domain/entity/Article.ts
    import Section from "./Section";
    
    export default class Article {
      id: number | null;
      title: string;
      sections: Section[];
    
      constructor(id: number | null, title: string, sections: Section[]) {
        this.id = id;
        this.title = title;
        this.sections = sections;
      }
    }
    

  • 집계 경로와 연관된 Section 엔티티
    domain/entity/Section.ts
    export default class Section {
      id: number | null;
      title: string;
    
      constructor(id: number | null, title: string) {
        this.id = id;
        this.title = title;
      }
    }
    

  • 통합 루트 웨어하우스
    infra/repository/Article.ts
      public async save(article: ArticleEntity): Promise<void> {
        // Article(記事)とそれに紐づくSection(章)をセットで保存する
        const result = await this.repository.save(article);
        return;
      }
    
  • 4. 도메인 객체의 생성 방법과 DB의 값을 재구성하는 방법으로 분리


    도메인 객체를 생성하는 방법에서 초기 값 등의 규칙을 설정하면 저장소에서 인스턴스를 DB 값으로 어셈블할 수 없습니다.
    이 경우 도메인 객체의 생성 방법과 DB의 값에서 재구성하는 방법을 분리합니다.
    다음은 예이다.
    이 예에서, 필드 대상이 생성될 때create 방법을 호출하고, DB에서 재구성할 때 구조기라고 부른다.
    domain/entity/User.ts
    export default class User {
      id?: number;
      familyName: string;
      givenName: string;
      status: number;
    
      constructor(
        id: number | undefined,
        familyName: string,
        givenName: string,
        status: number
      ) {
        this.id = id;
        this.familyName = familyName;
        this.givenName = givenName;
        this.status = status;
      }
    
      create(familyName: string, givenName: string): User {
        return new User(
          undefined, // 初期生成時にはidは決まらない
          familyName,
          givenName,
          1 // 初期生成時のstatusは必ず1
        );
      }
    }
    


    최종적으로 디렉터리는 다음과 같다.
    .
    ├── domain
    │   ├── entity
    │   ├── repository
    ├── infra
    │   ├── entity
    │   └── repository
    ├── presentation
    │   ├── middleware
    │   ├── route
    ├── tests
    │   ├── factory
    │   ├── feature
    │   └── unit
    └── use-case
    

    좋은 웹페이지 즐겨찾기