Clean Architecture 정리 - 5부 20장 아키텍처: 엔티티, 유스케이스
2025. 7. 24. 10:34

용어 정리

  • 업무 규칙: 수익을 얻거나 비용을 줄이기 위한 절차나 규칙
  • 핵심 업무 규칙: 컴퓨터로 구현되었는지 여부와 상관없이 본질적인 업무 절차
  • 핵심 업무 데이터: 핵심 업무 규칙이 다루는 상태값들

엔티티 (Entity)

정의

  • 시스템 내부의 비즈니스 개념을 대표하는 객체
  • 핵심 업무 데이터와 규칙을 함께 가지며 외부와 완전히 분리됨

 

특징

  • 순수하게 업무에 대한 규칙만 포함
  • 어떤 시스템에서도 동일하게 업무를 수행할 수 있어야 함
    • 개발 언어, 데이터 저장 방식, 프레임워크, 컴퓨터 배치 방식과도 무관
  • 그 어떤 시스템의 고려사항들이 엔티티에 영향을 주면 안됨. 절대 독립적

 

예시 – 주문 수수료 계산

class Order { // 엔티티
  constructor( // 핵심 업무 데이터
    private readonly totalAmount: number,
    private readonly discountAmount: number,
    private readonly status: 'PENDING' | 'COMPLETED' | 'CANCELLED'
  ) {}

  calculateFee(): number { // 핵심 업무 규칙
    if (this.status !== 'COMPLETED') {
      throw new Error('주문이 완료되지 않았습니다.');
    }
    const baseAmount = this.totalAmount - this.discountAmount;
    return baseAmount * 0.1;
  }
}

유스케이스 (Use Case)

정의

  • 애플리케이션 특화된 자동화 규칙을 수행하는 객체
  • 하나의 시나리오를 절차적으로 정의하고, 엔티티를 호출해 업무를 완성함

 

특징

  • 자동화 애플리케이션에 특화된 업무 규칙을 설명하는 객체
  • 하나의 시나리오를 함수 단위로 구현하며, 엔티티를 호출해 로직을 완성함
  • 입력, 출력, 참조 데이터를 포함하지만, 그 데이터가 JSON인지, HTML인지 등의 표현 형식은 명시하지 않음
  • UI, 전달 방식, 표현 방식과는 무관하며, 사용자와 엔티티 사이의 상호작용만 정의함

 

요청 및 응답 모델과 유스케이스의 분리

유스케이스는 입출력 구조를 가지지만, 그 형식에는 관심 없어야 함
예: HttpRequest, SQL, HTML, JSON 등 구체적인 기술 요소와는 완전히 분리되어야 함

 

✅ 올바른 방식: DTO를 활용한 인터페이스 분리

interface CompleteOrderRequestModel {
  orderId: string;
}

interface CompleteOrderResponseModel {
  fee: number;
  message: string;
}

class CompleteOrderUseCase {
  constructor(private readonly orderRepository: OrderRepository) {}

  async execute(request: CompleteOrderRequestModel): Promise<CompleteOrderResponseModel> {
    const order = await this.orderRepository.findById(request.orderId);
    if (!order) throw new Error('주문을 찾을 수 없음');

    const fee = order.calculateFee();
    return { fee, message: '수수료 계산 완료' };
  }
}

 

컨트롤러 계층에서는 유스케이스와 I/O를 매핑만 담당

async function completeOrderController(req: HttpRequest, res: HttpResponse) {
  const requestModel = { orderId: req.params.id };
  const responseModel = await useCase.execute(requestModel);
  res.status(200).json(responseModel);
}

➡️ 유스케이스는 오직 흐름과 규칙에 집중하고, 표현 계층과는 철저히 분리되어야 함

 

 

유스케이스에 여러 시나리오가 있어도 되는가?

결론: 단일 책임 원칙을 기준으로 판단해야 하며, 업무적 목적이 다르면 분리하는 것이 좋음

❌ 나쁜 예 – 여러 업무 목표를 하나에 혼합

class OrderUseCase {
  approveOrder(...) {...}
  cancelOrder(...) {...}
  refundOrder(...) {...}
}
  • 주문 승인 / 취소 / 환불은 목적이 다름 → 유스케이스를 분리해야 함
  • 테스트 및 유지보수가 어려워짐

✅ 좋은 예 – 하나의 절차 흐름을 단계별로 구성

class CompleteOrderUseCase {
  async execute(...) {
    this.validateOrder();
    this.applyFee();
    this.notifyUser();
  }
}
  • 주문 완료라는 단일 절차 흐름 안의 구성 요소들
  • 응집력 있는 하나의 유스케이스로 구성 가능

엔티티 vs 유스케이스 비교

항목 엔티티 (Entity) 유스케이스 (Use Case)
정의 핵심 업무 규칙을 담은 비즈니스 객체 자동화 시나리오를 수행하는 함수 객체
역할 상태와 규칙을 가진 순수한 비즈니스 로직 사용자-엔티티 간 상호작용과 흐름 제어
독립성 외부 시스템과 완전 독립 표현 방식은 무관하나, 엔티티 호출은 포함
포함 요소 핵심 데이터, 규칙 입력/출력/참조 데이터, 흐름 절차
의존성 방향 유스케이스를 전혀 모름 (완전 독립) 엔티티에 의존
추상도 고수준 (일반화, 재사용 가능) 저수준 (애플리케이션 특화)

 

 

 

※ 본 글은 『Clean Architecture』(로버트 C. 마틴 저) 5부 20장을 기반으로 학습 목적으로 요약한 글입니다.

※ 이 글은 책의 내용을 요약한 것으로, 원문 없이 읽을 경우 오해의 여지가 있을 수 있습니다. 정확한 이해를 위해 원서의 정독을 권장합니다.