DDD 10분 읽기

프론트엔드에서 도메인 모델링이 필요한 이유

-----

❓ 도메인 모델링이란 무엇인가?


**도메인 모델링(Domain Modeling)**은 특정 비즈니스 영역(도메인)의 복잡성을 이해하고, 그 복잡성을 소프트웨어 시스템 내에서 효과적으로 표현하기 위한 과정입니다. 쉽게 말해, 현실 세계의 비즈니스 규칙과 개념을 소프트웨어 코드로 번역하는 작업이라고 할 수 있습니다.

이는 단순히 데이터베이스 테이블을 설계하거나 UI 화면을 그리는 것을 넘어, “무엇을” 다루고 “어떻게” 동작하는지에 대한 본질적인 질문에 답하는 과정입니다.

  • 도메인(Domain): 소프트웨어가 해결하고자 하는 문제 영역입니다. 예를 들어, 온라인 쇼핑몰, 병원 예약 시스템, 은행 거래 시스템 등이 각각의 도메인입니다.
  • 모델(Model): 도메인의 핵심 개념, 데이터, 그리고 그 데이터가 상호작용하는 방식을 추상화하여 표현한 것입니다. 이는 코드, 다이어그램, 텍스트 등 다양한 형태로 나타날 수 있습니다.

📌 도메인 모델링의 목표

도메인 모델링의 주요 목표는 다음과 같습니다:

  • 도메인 지식 캡슐화: 비즈니스 규칙과 로직을 코드 내에 명확하게 담아냅니다.

  • 공통의 언어 구축: 개발자와 비즈니스 전문가가 같은 용어를 사용하고 같은 개념을 이해하도록 돕습니다 (유비쿼터스 언어).

  • 복잡성 관리: 복잡한 시스템을 이해하기 쉬운 작은 단위로 분해하고 관계를 정의합니다.

  • 유연성 및 확장성: 비즈니스 요구사항 변경에 유연하게 대응하고 새로운 기능을 쉽게 추가할 수 있는 기반을 마련합니다.


❗️ 프론트엔드에서 도메인 모델링의 필요성

프론트엔드에서 도메인 모델링은 단순히 서버 데이터를 매핑하는 것을 넘어, 클라이언트 애플리케이션의 내부적인 비즈니스 로직과 상태 관리의 핵심이 됩니다.

유비쿼터스 언어(Ubiquitous Language)의 구현

도메인 주도 설계(DDD)의 핵심 개념 중 하나인 유비쿼터스 언어는 개발팀과 비즈니스 팀이 사용하는 공통의 언어를 의미합니다.

프론트엔드 도메인 모델은 이 유비쿼터스 언어를 코드로 구현하는 중요한 장소입니다. 예를 들어, 비즈니스 팀이 “고객이 주문을 확정하면 주문 상태가 ‘결제 대기’에서 ‘결제 완료’로 변경되고, 재고가 차감된다”고 말하면, 프론트엔드 Order 도메인 모델에는 confirmPayment()와 같은 메서드가 있고, 이 메서드 내부에서 status를 변경하고 stock을 업데이트하는 로직이 포함될 수 있습니다. 이렇게 하면 코드와 비즈니스 요구사항 간의 괴리가 줄어들어 의사소통 오류를 줄이고 개발 효율성을 높일 수 있습니다.

UI 상태 관리의 복잡성 해소

최신 프론트엔드 애플리케이션은 UI 상태가 매우 복잡해지는 경향이 있습니다. 단순히 서버에서 받은 데이터를 보여주는 것을 넘어, 사용자 인터랙션에 따라 데이터가 실시간으로 변경되고, 그에 따른 UI 변화가 발생합니다.

도메인 모델은 이러한 **UI 상태의 근원(Source of Truth)**이 될 수 있습니다. 예를 들어, 장바구니에 담긴 상품의 총 가격을 계산하거나, 특정 필드의 유효성을 검사하는 등의 로직을 UI 컴포넌트 내부에 직접 구현하는 대신, Cart 도메인 모델이나 Product 도메인 모델에 캡슐화하여 관리할 수 있습니다. 이는 UI 컴포넌트를 ‘바보’처럼 만들고, 오직 데이터 렌더링에만 집중하게 하여 UI 로직과 비즈니스 로직을 명확히 분리합니다.

애플리케이션의 확장성 및 유지보수성 극대화

잘 모델링된 도메인 엔티티는 재사용 가능한 블록이 됩니다. 새로운 기능이 추가되거나 기존 기능이 변경될 때, 도메인 모델의 변경은 해당 도메인에 국한되고, 다른 부분에 미치는 영향을 최소화할 수 있습니다.

예를 들어, 상품의 할인 정책이 변경되더라도, Product 도메인 엔티티 내부의 calculatePrice() 메서드만 수정하면, 이 엔티티를 사용하는 모든 UI 컴포넌트나 다른 로직은 자동으로 업데이트된 가격을 반영하게 됩니다. 이는 코드 중복을 줄이고 애플리케이션 전체의 일관성을 유지하는 데 기여합니다.

테스트 용이성 및 견고성

도메인 모델은 UI나 네트워크 계층과 독립적으로 존재하기 때문에, 단위 테스트(Unit Test) 작성이 매우 용이합니다.

복잡한 비즈니스 로직이 도메인 모델 내부에 캡슐화되어 있다면, UI 렌더링 없이도 해당 로직의 정확성을 검증할 수 있습니다. 이는 테스트 커버리지를 높이고, 버그를 조기에 발견하며, 코드 변경에 대한 자신감을 부여합니다.


이번에는 프론트엔드에서 도서관 관리 시스템을 구축하는 상황을 가정하여 도메인 모델링의 중요성을 설명해 보겠습니다.

서버에서 받은 BookDto는 다음과 같은 형태의 JSON 데이터일 수 있습니다:

{
  "bookId": "B001",
  "title": "클린 코드",
  "authorName": "로버트 C. 마틴",
  "publicationYear": 2008,
  "isbn13": "978-8992934035",
  "totalCopies": 5,
  "availableCopies": 2,
  "loanPeriodDays": 14
}

이것은 단순히 데이터베이스의 레코드를 네트워크를 통해 전달하기 위한 “데이터 전송용 봉투”입니다. 이 데이터만으로는 “이 책이 대출 가능한가?”, “반납일이 언제인가?”, “연장 가능한가?”와 같은 프론트엔드 비즈니스 로직을 바로 처리하기 어렵습니다.

프론트엔드 도메인 엔티티 (고객용 도서 카드)

프론트엔드에서 정의하는 Book 도메인 엔티티는 다음과 같은 형태를 가질 수 있습니다:

class Book {
  id: string;
  title: string;
  author: string;
  publishedYear: number;
  isbn: string;
  totalCopies: number;
  availableCopies: number;
  loanPeriod: number; // 일 단위

  constructor(dto: BookDto) {
    this.id = dto.bookId;
    this.title = dto.title;
    this.author = dto.authorName;
    this.publishedYear = dto.publicationYear;
    this.isbn = dto.isbn13;
    this.totalCopies = dto.totalCopies;
    this.availableCopies = dto.availableCopies;
    this.loanPeriod = dto.loanPeriodDays;
  }

  // **클라이언트 고유의 비즈니스 로직 (메서드)**
  isAvailable(): boolean {
    return this.availableCopies > 0;
  }

  getAvailabilityStatus(): string {
    return this.isAvailable() ? '대출 가능' : '대출 중';
  }

  getLoanDueDate(loanDate: Date): Date {
    const dueDate = new Date(loanDate);
    dueDate.setDate(dueDate.getDate() + this.loanPeriod);
    return dueDate;
  }

  canExtendLoan(currentLoan: Loan): boolean {
    // 대출 기간, 연장 횟수 등 복잡한 로직 포함
    // 예: 현재 대출이 만료되지 않았고, 연장 횟수가 2회 미만인 경우
    return currentLoan.isNotExpired() && currentLoan.extensionCount < 2;
  }

  // UI 표시를 위한 포맷팅 로직 -> 도메인 로직과 UI 로직의 애매한 경계
  getDisplayTitle(): string {
    return `${this.title} (${this.publishedYear})`;
  }
}

Book 도메인 엔티티는 단순히 데이터를 담는 것을 넘어, “이 책은 대출 가능한가?”, “이 책의 대출 기한은 언제까지인가?”, “이 책의 대출을 연장할 수 있는가?” 와 같은 질문에 스스로 답할 수 있는 **행위(메서드)**를 포함합니다.

UI 컴포넌트는 book.isAvailable()을 호출하여 대출 가능 여부에 따라 버튼을 활성화/비활성화하고, book.getDisplayTitle()을 호출하여 사용자에게 친숙한 형태로 제목을 보여줍니다.


이점

  • UI와 비즈니스 로직 분리: UI 컴포넌트는 Book 엔티티가 제공하는 메서드를 호출하기만 하면 됩니다. 대출 가능 여부를 판단하는 복잡한 로직이 UI 컴포넌트 내부에 중복되거나 섞이지 않습니다.

  • 재사용성: isAvailable() 로직은 도서 목록, 상세 페이지, 검색 결과 등 여러 곳에서 재사용될 수 있습니다.

  • 유연성: 만약 대출 가능 여부 판단 로직(예: 예약된 책은 대출 불가)이 변경되더라도, isAvailable() 메서드 내부만 수정하면 됩니다. UI는 전혀 변경할 필요가 없습니다.

  • 테스트 용이성: isAvailable(), getLoanDueDate() 등의 메서드는 UI 렌더링 없이 순수하게 단위 테스트가 가능하여, 비즈니스 로직의 정확성을 쉽게 검증할 수 있습니다.


📄 결론

프론트엔드에서 도메인 모델링은 단순히 데이터를 화면에 뿌리는 것을 넘어, 애플리케이션의 핵심 비즈니스 로직을 체계적으로 관리하고, UI와 비즈니스 로직을 분리하며, 변화에 유연하게 대응하고, 테스트하기 쉬운 견고한 시스템을 구축하는 데 필수적인 전략입니다. 이는 복잡한 프론트엔드 애플리케이션을 성공적으로 개발하고 장기적으로 유지보수하기 위한 강력한 기반이 됩니다.