용어 정리
- 업무 규칙: 수익을 얻거나 비용을 줄이기 위한 절차나 규칙
- 핵심 업무 규칙: 컴퓨터로 구현되었는지 여부와 상관없이 본질적인 업무 절차
- 핵심 업무 데이터: 핵심 업무 규칙이 다루는 상태값들
엔티티 (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장을 기반으로 학습 목적으로 요약한 글입니다.
※ 이 글은 책의 내용을 요약한 것으로, 원문 없이 읽을 경우 오해의 여지가 있을 수 있습니다. 정확한 이해를 위해 원서의 정독을 권장합니다.
'아키텍처' 카테고리의 다른 글
Clean Architecture 정리 - 5부 22장 클린아키텍처 (0) | 2025.07.24 |
---|---|
Clean Architecture 정리 - 5부 21장 소리치는 아키텍처 (0) | 2025.07.24 |
Clean Architecture 정리 – 5부 19장 아키텍처: 정책과 수준 (0) | 2025.07.23 |
Clean Architecture 정리 – 5부 17장 아키텍처에서 경계 나누기 (2) | 2025.07.23 |
Clean Architecture 정리 – 5부 16장 아키텍처의 독립성 (4) | 2025.07.19 |