[프롬프트] 클린 아키텍처 설계 프롬프트 - 최적 프롬프트 레시피

클린 아키텍처 및 헥사고날 소프트웨어 설계 가이드

[프롬프트] 클린 아키텍처 설계 프롬프트 - 최적 프롬프트 레시피

💡 한 줄 요약: "클린 아키텍처로 설계해줘"라는 단순 질의 시 발생하는 ORM 중심의 빈혈 도메인 모델(Anemic Domain Model), 계층 간 결합도 상승, 비즈니스 로직의 인프라 의존 문제를 원천 차단하고, Principal Enterprise Software Architect 및 Clean Architecture Tech Lead 관점에서 Entities, Use Cases, Interface Adapters, Frameworks & Drivers 4계층과 의존성 역전 원칙(DIP), In/Outbound Port, 헥사고날(Hexagonal) 구조를 완벽히 도출하는 최적 프롬프트 레시피입니다.


1. 페인포인트 & 작업 목표

• 실무 상황: 엔터프라이즈 백엔드 시스템 구축 시 계층형 아키텍처(Layered Architecture)를 도입했으나, 프레임워크(Spring, NestJS 등)와 ORM(JPA, TypeORM, Prisma)에 비즈니스 로직이 강하게 종속되어 비즈니스 규칙의 독립적 단위 테스트가 불가능하고 기술 스택 변경 비용이 급증하는 상황.
• 단순 프롬프트 요청 시 발생하는 4대 전형적 문제:
1. ORM 엔티티에 비즈니스 도메인이 오염되는 빈혈 도메인 모델(Anemic Model): 프롬프트 제약이 없으면 AI는 JPA/TypeORM 데코레이터가 붙은 DB 테이블 맵핑 객체에 단순 Getter/Setter만 나열하여 순수 도메인 비즈니스 규칙을 상실함.
2. 의존성 방향 역전(DIP) 실패 및 내부 계층의 외부 인프라 직접 참조: 내부 비즈니스 Use Case 계층이 외부 인프라(DB Repository 구현체, 외부 결제 SDK)를 직접 import하여 의존성 규칙을 위반함.
3. 인터페이스 분리 원칙(ISP) 및 포트 & 어댑터(Ports and Adapters) 규격 부재: Use Case 실행을 위한 Inbound Port와 영속성/외부 통신을 위한 Outbound Port 추상화 경계 없이 Controller와 Service가 긴밀하게 결합됨.
4. 단일 거대 서비스(Fat Service) 안티패턴 양산: 비즈니스 플로우마다 단일 책임 Use Case를 분리하지 않고, 수십 개 메서드를 하나의 거대 Service 클래스에 몰아넣어 유지보수성을 극도로 저하시킴.
• 해결 목표:
1. Principal Enterprise Software Architect 페르소나 주입: 순수 언어 표준(Pure Domain) 기반 엔티티 캡슐화, 단일 책임 기반 Use Case 커맨드/쿼리 분리, DIP 완전 준수 강제.
2. 헥사고날 포트 & 어댑터 인터페이스 경계 확립: Inbound Port(Controller -> Use Case) 및 Outbound Port(Use Case -> DB/외부 결제)의 엄격한 인터페이스 정의와 어댑터 분리.
3. 도메인 목적별 듀얼 레시피 체계화: 그린필드 신규 도메인 설계(레시피 B)와 1,500줄 이상 레거시 모놀리스 Fat Service의 헥사고날 쾌속 리팩토링(레시피 C)의 이원화.

2. 프롬프트 레시피 비교

❌ 레시피 A: 기본형 (단순 질의 초안)

역할 정의, 계층 제약, 의존성 역전 원칙, 순수 도메인 규칙 없이 단순 아키텍처 설계를 요청하는 초안입니다.

[영문 프롬프트 (복사용)]

Design clean architecture for: [service]

[한글 프롬프트 (1:1 매칭 복사용)]

다음 서비스에 대해 클린 아키텍처를 설계해줘: [서비스명]
💡 [프롬프트 구조 및 핵심 파라미터 해설란]
• 구조적 결함: 엔티티 순수성(POJO/Plain Type), 4계층(Entities, Use Cases, Interface Adapters, Frameworks) 명세, In/Outbound Port 분리 지침이 전혀 없습니다.
• 부작용: AI는 3티어(Controller-Service-Repository) 구조에 이름만 'Clean'을 붙여 내놓으며, DB 어노테이션이 침투한 빈혈 도메인 모델과 단일 거대 Service 클래스를 생성합니다.

⚡ 레시피 B: Principal Enterprise Software Architect 실무 종결형 클린 아키텍처 설계 프롬프트

순수 도메인 엔티티 보호, Use Case 단일 책임 원칙, In/Outbound Port 기반 의존성 역전(DIP), 패키지 디렉토리 구조 및 실제 인터페이스/구현체 코드 명세를 완벽히 도출하는 레시피입니다.

[영문 프롬프트 (복사용)]

You are a Principal Enterprise Software Architect and Clean Architecture Tech Lead. Design a production-grade, strictly decoupled Clean Architecture specification and code implementation for the target business domain below. ### Target Domain Context: - Domain Service: [e.g., E-Commerce Order & Payment Authorization Domain] - Core Business Capabilities: [e.g., Order creation, discount calculation, inventory reservation, PG payment authorization, order cancellation] - Target Tech Stack: [e.g., TypeScript / Node.js (or Java / Spring Boot)] ### Architectural Directives & Constraints: 1. 4-TIER ONION/CLEAN LAYER STRUCTURE: - Enterprise Business Rules (Pure Entities): Zero external framework/ORM dependencies. Pure domain logic and invariant validation. - Application Business Rules (Use Cases): Single-purpose Interactor/UseCase classes. Execute workflows via ports. - Interface Adapters: Inbound Adapters (REST Controllers, CLI) and Outbound Adapters (Prisma/JPA Repositories, External Payment Client). - Frameworks & Drivers: Express/Fastify/NestJS or Spring configuration, database engines. 2. STRICT DEPENDENCY INVERSION (DIP) & PORTS: - Define explicit Inbound Ports (Driving Ports: Use Case interfaces called by controllers). - Define explicit Outbound Ports (Driven Ports: Repository & Payment Gateway interfaces implemented in the adapter layer). - Outer layers depend on inner layers; inner layers NEVER import outer layers. 3. DOMAIN PURITY & DATA MAPPING: - Forbid ORM decorators inside domain entities. Require explicit Mapper classes between Domain Entity and DB Schema Model. 4. DELIVERABLE SPECIFICATION: - Provide modular package directory tree. - Provide compilable code implementations for: (1) Pure Domain Entity with invariants, (2) Inbound & Outbound Port interfaces, (3) Use Case Interactor with transactional flow, (4) Outbound Persistence Adapter.

[한글 프롬프트 (1:1 매칭 복사용)]

당신은 수석 엔터프라이즈 소프트웨어 아키텍트(Principal Enterprise Software Architect)이자 클린 아키텍처 테크 리드(Clean Architecture Tech Lead)입니다. 아래 설명된 대상 비즈니스 도메인에 대해 엄격히 결합이 분리된 프로덕션급 클린 아키텍처 명세 및 코드 구현을 설계하십시오. ### 대상 도메인 컨텍스트: - 도메인 서비스: [예: 전자상거래 주문 및 결제 승인 도메인] - 핵심 비즈니스 기능: [예: 주문 생성, 할인 정책 계산, 재고 가예약, PG 결제 승인, 주문 취소] - 대상 기술 스택: [예: TypeScript / Node.js (또는 Java / Spring Boot)] ### 아키텍처 설계 필수 지침 및 제약사항: 1. 4계층 어니언/클린 아키텍처 구조: - 엔터프라이즈 비즈니스 규칙(Pure Entities): 외부 프레임워크 및 ORM 의존성 0%. 순수 도메인 로직 및 불변식(Invariant) 검증 캡슐화. - 애플리케이션 비즈니스 규칙(Use Cases): 단일 목적 인터랙터(UseCase) 클래스 설계. 포트를 통한 오케스트레이션. - 인터페이스 어댑터(Interface Adapters): 인바운드 어댑터(REST 컨트롤러) 및 아웃바운드 어댑터(Prisma/JPA 리포지토리, 외부 PG 결제 연동). - 프레임워크 및 드라이버(Frameworks & Drivers): 웹 프레임워크 라우터 및 DB 엔진 연동. 2. 엄격한 의존성 역전 원칙(DIP) 및 포트 사양: - 명시적인 인바운드 포트(Driving Ports: 컨트롤러가 호출하는 Use Case 인터페이스) 정의. - 명시적인 아웃바운드 포트(Driven Ports: 어댑터 계층이 구현할 영속성 및 외부 결제 인터페이스) 정의. - 바깥쪽 계층이 안쪽 계층에 의존하며, 안쪽 계층은 바깥쪽 계층을 절대 참조(import)하지 않음. 3. 도메인 순수성 및 데이터 매퍼 강제: - 도메인 엔티티 내 ORM 데코레이터 선언 절대 금지. 순수 도메인 엔티티와 DB 영속성 스키마 간 데이터 변환 매퍼(Mapper) 구현. 4. 산출물 명세: - 모듈식 패키지 디렉토리 구조도 제공. - 다음 항목의 컴파일 가능한 실제 구현 코드 제공: (1) 불변식을 가진 순수 도메인 엔티티, (2) 인바운드/아웃바운드 포트 인터페이스, (3) 트랜잭션 흐름을 제어하는 Use Case 인터랙터, (4) 아웃바운드 영속성 어댑터.
💡 [프롬프트 구조 및 핵심 파라미터 해설란]
• 의존성 역전(DIP) 강제: Use Case가 DB 구현체를 참조하지 않고 Outbound Port(인터페이스)에만 의존하게 만들어, DB가 변경되어도 비즈니스 코드가 전혀 영향받지 않습니다.
• 도메인 엔티티 순수성 보호: ORM 데코레이터(@Entity, @Column)를 엔티티에서 완전히 격리하고 Mapper를 강제하여 프레임워크 침투를 차단합니다.
• 단일 목적 Use Case 명세: 하나의 클래스에 하나의 execute 메서드를 두어 복잡한 비즈니스 유스케이스를 명확하게 캡슐화합니다.

🚀 레시피 C: 레거시 모놀리스 Fat Service ➔ 헥사고날 포트 & 어댑터 쾌속 리팩토링 특화

DB, 결제 SDK, 이메일 전송, 비즈니스 로직이 1,500줄 이상 한데 엉킨 레거시 모놀리스 Service를 포트 & 어댑터 패턴으로 안전하게 분해하는 리팩토링 레시피입니다.

[영문 프롬프트 (복사용)]

You are a Staff Refactoring Architect specializing in Legacy Modernization and Hexagonal Architecture. Refactor the provided legacy fat monolithic service into a decoupled Hexagonal (Ports and Adapters) architecture. ### Target Legacy Context: - Legacy Service Code: [Paste or summarize 1,500-line legacy Spring/NestJS Service with mixed DB, HTTP, and domain logic] - Pain Points: Mixed transaction scopes, untestable external SDK calls, DB entity pollution. ### Modernization Directives: 1. SEPARATION OF CONCERNS & DECONSTRUCTION: - Extract pure domain invariants into a domain entity/aggregate root. - Split bloated methods into isolated Command Use Cases and Query Use Cases (CQRS hint). 2. PORT CONTRACT EXTRACTION: - Extract all DB interactions into Outbound Command/Query Ports. - Extract third-party payment/messaging calls into Outbound Gateway Ports. 3. STEP-BY-STEP STRANGLER FIG MIGRATION: - Provide a zero-downtime, non-breaking refactoring step plan (Strangler Fig Pattern). - Write refactored Inbound Controller, UseCase Interactor, Port Interfaces, and Adapter implementations.

[한글 프롬프트 (1:1 매칭 복사용)]

당신은 레거시 시스템 현대화 및 헥사고날 아키텍처를 전문으로 하는 스태프 리팩토링 아키텍트(Staff Refactoring Architect)입니다. 제공된 레거시 Fat Monolithic 서비스를 결합이 분리된 헥사고날(포트 & 어댑터) 아키텍처로 안전하게 리팩토링하십시오. ### 대상 레거시 컨텍스트: - 레거시 서비스 코드: [DB, 외부 HTTP, 비즈니스 로직이 혼재된 1,500줄 규모의 레거시 Spring/NestJS Service 코드 또는 요약] - 직면한 문제점: 혼재된 트랜잭션 범위, 테스트 불가능한 외부 SDK 결합, DB 엔티티 오염. ### 현대화 리팩토링 지침: 1. 관심사 분리 및 해체: - 서비스 내부에 흩어진 순수 도메인 비즈니스 불변식을 도메인 엔티티(또는 애그리게이트 루트)로 추출하십시오. - 비대해진 메서드들을 독립적인 Command Use Case와 Query Use Case로 분리하십시오. 2. 포트 계약(Contract) 추출: - 모든 DB 상호작용을 아웃바운드 포트(OrderCommandPort, OrderQueryPort)로 추출하십시오. - 외부 PG 결제 및 알림 연동을 아웃바운드 게이트웨이 포트(PaymentGatewayPort)로 격리하십시오. 3. 스트랭글러 피그(Strangler Fig) 단계적 마이그레이션: - 기존 서비스 운영에 무중단으로 점진 적용할 수 있는 단계적 리팩토링 전략을 제시하십시오. - 리팩토링된 인바운드 컨트롤러, UseCase 인터랙터, 포트 인터페이스 및 어댑터 구현체 코드를 작성하십시오.
💡 [프롬프트 구조 및 핵심 파라미터 해설란]
• Fat Service 해체 로직: 거대한 서비스 메서드를 불변식을 품은 Entity와 오케스트레이션을 맡는 UseCase로 분해합니다.
• 외부 연동 완전 격리: 외부 결제 SDK나 알림 모듈을 Outbound Port 뒤로 숨겨 Mocking 기반의 순수 단위 테스트를 즉시 가능하게 만듭니다.
• 스트랭글러 피그 점진 전환: 기존 시스템을 전면 재작성하지 않고 엔드포인트별로 포트를 덧대어 점진 이전하는 실무 안전망을 제공합니다.

3. 실행 결과 비교: 레시피 A vs B vs C 실전 대조

❌ 레시피 A 실행 결과: [단순 질의의 한계] ORM에 오염된 빈혈 도메인 3티어 구조

레시피 A를 실행했을 때 도출되는 전형적인 안티패턴 코드입니다.

// 안티패턴: ORM 데코레이터 침투, 빈혈 도메인 모델, Controller-Service 강결합 import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity('orders') export class Order { @PrimaryGeneratedColumn('uuid') id: string; @Column() amount: number; @Column() status: string; // 단순 데이터 홀더, 상태 변경 유효성 검증 로직 전무 } @Injectable() export class OrderService { constructor( @InjectRepository(Order) private readonly orderRepo: Repository<Order>, private readonly stripeClient: StripeSDK // 외부 결제 인프라에 직접 강결합! ) {} async createAndPay(amount: number) { const charge = await this.stripeClient.charges.create({ amount }); const order = this.orderRepo.create({ amount, status: 'PAID' }); return await this.orderRepo.save(order); } }
비판:
• Order는 비즈니스 규칙이 없는 빈혈 객체이며 TypeORM에 완전히 결합되어 프레임워크 없이는 테스트조차 어렵습니다.
• OrderService가 외부 StripeSDK와 Repository<Order> 구현체를 직접 참조하여 의존성 역전이 완전히 깨져 있습니다.

⚡ 레시피 B 실행 결과: [실전 사례 1] 전자상거래 주문/결제 승인 도메인 클린 아키텍처 패키지 & 코드 명세 (순수 Domain Entity, OrderPaymentUseCase, In/Outbound Port 인터페이스, Prisma 어댑터 분리, 후속 체이닝 2종, 4단계 워크플로우)

1. 패키지 디렉토리 구조 & 순수 Domain Entity

src/order/ ├── domain/ # 1. Enterprise Rules (Order.ts: 순수 비즈니스 불변식) ├── application/ # 2. Application Rules (UseCase & In/Outbound Ports) │ ├── port/in/OrderPaymentUseCase.ts & port/out/OrderRepositoryPort.ts │ └── service/OrderPaymentService.ts ├── adapter/ # 3. Interface Adapters (Web Controller & Prisma/Stripe) └── infrastructure/ # 4. Frameworks & Drivers (prisma.ts)
// domain/entity/Order.ts (순수 도메인 엔티티: ORM 데코레이터 전무, 자체 불변식 보증) export class Order { constructor( private readonly _id: string, private readonly _customerId: string, private _amount: number, private _status: 'PENDING' | 'AUTHORIZED' | 'CANCELLED' ) { if (_amount <= 0) throw new Error('Order amount must be positive.'); } get id(): string { return this._id; } get amount(): number { return this._amount; } get status(): string { return this._status; } public authorizePayment(): void { if (this._status !== 'PENDING') throw new Error(`Cannot authorize order in status: ${this._status}`); this._status = 'AUTHORIZED'; } }

2. In/Outbound Port 인터페이스 명세

// application/port/in/OrderPaymentUseCase.ts (인바운드 포트) export interface OrderPaymentCommand { orderId: string; paymentMethodId: string; } export interface OrderPaymentUseCase { execute(command: OrderPaymentCommand): Promise<{ orderId: string; status: string }>; } // application/port/out/OrderRepositoryPort.ts & PaymentGatewayPort.ts (아웃바운드 포트) export interface OrderRepositoryPort { findById(id: string): Promise<Order | null>; save(order: Order): Promise<void>; } export interface PaymentGatewayPort { authorize(amount: number, paymentMethodId: string): Promise<{ transactionId: string }>; }

3. Use Case Interactor & Outbound Persistence Adapter

// application/service/OrderPaymentService.ts (Use Case: 포트만 의존, DIP 완성) export class OrderPaymentService implements OrderPaymentUseCase { constructor( private readonly orderRepository: OrderRepositoryPort, private readonly paymentGateway: PaymentGatewayPort ) {} async execute(command: OrderPaymentCommand): Promise<{ orderId: string; status: string }> { const order = await this.orderRepository.findById(command.orderId); if (!order) throw new Error('Order not found'); order.authorizePayment(); // 순수 도메인 상태 변경 (불변식 검증) await this.paymentGateway.authorize(order.amount, command.paymentMethodId); await this.orderRepository.save(order); return { orderId: order.id, status: order.status }; } } // adapter/out/persistence/PrismaOrderRepository.ts (아웃바운드 어댑터: Prisma 맵핑) export class PrismaOrderRepository implements OrderRepositoryPort { constructor(private readonly prisma: PrismaClient) {} async findById(id: string): Promise<Order | null> { const raw = await this.prisma.orderRecord.findUnique({ where: { id } }); return raw ? new Order(raw.id, raw.customerId, raw.amount, raw.status as any) : null; } async save(order: Order): Promise<void> { await this.prisma.orderRecord.upsert({ where: { id: order.id }, update: { amount: order.amount, status: order.status }, create: { id: order.id, customerId: 'cust-1', amount: order.amount, status: order.status } }); } }

후속 체이닝 2종

체이닝 1 (분산 트랜잭션/Outbox): "위 OrderPaymentService에서 결제 승인 완료 후 카프카 이벤트를 신뢰성 있게 발행하기 위해 OutboxPatternPort 인터페이스와 Transactional Outbox Adapter 코드를 추가해줘."
체이닝 2 (순수 도메인 단위 테스트): "외부 의존성이 Mocking된 OrderPaymentService의 Jest 단위 테스트 코드와, Order 도메인 엔티티의 불변식 위반 실패 케이스 테스트를 작성해줘."

4단계 워크플로우

1. 도메인 엔티티 불변식 캡슐화: 프레임워크 독립적인 순수 클래스에 상태 전이 규칙과 유효성 검증 집중.
2. In/Outbound 포트 규격 선언: 컨트롤러 호출점과 외부 리소스(DB, PG) 요구사항을 인터페이스로 추상화.
3. Use Case 인터랙터 조립: 포트 인터페이스만을 주입받아 비즈니스 플로우 트랜잭션을 일관되게 조율.
4. 인프라 어댑터 플러그인 연결: Prisma, 외부 SDK 등 실제 기술 구현체를 어댑터로 구현하여 주입.

🚀 레시피 C 실행 결과: [실전 사례 2] 1,500줄 레거시 스프링 Fat Service의 헥사고날 포트 & 어댑터 리팩토링 (OrderCommandPort, OrderQueryPort, ExternalPaymentAdapter 분리, 후속 체이닝, 3단계 워크플로우)

1. 레거시 해체 및 헥사고날 포트 분리 명세

// 1. 도메인 포트 분리: DB 쓰기(Command)와 외부 결제 통신을 포트로 격리 public interface OrderCommandPort { void updateOrderStatus(UUID orderId, OrderStatus status); } public interface OrderQueryPort { Optional<OrderSummary> findOrderForProcessing(UUID orderId); } public interface ExternalPaymentPort { PaymentResult requestAuthorization(UUID orderId, BigDecimal amount); } // 2. 단일 목적 UseCase로 분해된 Interactor @Service @RequiredArgsConstructor public class ApproveOrderPaymentUseCase { private final OrderQueryPort orderQueryPort; private final OrderCommandPort orderCommandPort; private final ExternalPaymentPort externalPaymentPort; @Transactional public void execute(UUID orderId) { OrderSummary order = orderQueryPort.findOrderForProcessing(orderId) .orElseThrow(() -> new EntityNotFoundException("Order not found: " + orderId)); PaymentResult result = externalPaymentPort.requestAuthorization(orderId, order.getAmount()); if (!result.isSuccess()) throw new PaymentFailedException(result.getErrorMessage()); orderCommandPort.updateOrderStatus(orderId, OrderStatus.AUTHORIZED); } }

2. 격리된 외부 결제 어댑터 구현체

// 3. adapter/out/payment/ExternalPaymentAdapter.java @Component @RequiredArgsConstructor public class ExternalPaymentAdapter implements ExternalPaymentPort { private final LegacyPgClient legacyPgClient; // 레거시 SDK를 어댑터 내부로 은닉 @Override public PaymentResult requestAuthorization(UUID orderId, BigDecimal amount) { try { PgResponse response = legacyPgClient.charge(orderId.toString(), amount.longValue()); return new PaymentResult(response.isOk(), response.getMessage()); } catch (Exception e) { return new PaymentResult(false, e.getMessage()); } } }

후속 체이닝: 레거시 병행 실행 검증(Shadow Traffic)

추가 프롬프트: "기존 1,500줄 레거시 Fat Service와 새로 분리된 ApproveOrderPaymentUseCase를 동시에 호출하여 결과값 일치 여부를 로깅하는 Spring AOP 기반 Shadow Execution Adapter 코드를 작성해줘."

3단계 워크플로우

1. 의존성 경계 인터페이스 추출: Fat Service 내부의 SQL 쿼리와 외부 API 호출부를 포트 인터페이스로 선별 격리.
2. 독립 Use Case 단위 기능 분해: 방대한 메서드를 단일 책임 비즈니스 유스케이스 인터랙터로 쪼개어 배치.
3. 어댑터 은닉 및 점진 전환: 외부 SDK와 ORM 접근 코드를 어댑터로 감싸고 스트랭글러 피그 패턴으로 라우팅 전환.

4. 클린 아키텍처 설계를 위한 4대 엔지니어링 원칙

1. 의존성 규칙(Dependency Rule) 준수:
• 소스 코드 의존성은 바깥쪽 원에서 안쪽 원(고수준 정책) 방향으로만 향해야 합니다. 엔티티와 Use Case는 UI, DB, 프레임워크의 존재를 전혀 몰라야 합니다.
2. POJO 기반 도메인 순수성(Pure Domain Invariants):
• 도메인 엔티티는 프레임워크 종속적인 어노테이션이나 라이브러리를 일절 배제한 순수 언어 객체여야 합니다. 이를 통해 비즈니스 룰을 독립적으로 검증할 수 있습니다.
3. 명시적 In/Outbound Port 분리:
• Use Case를 구동하는 주도형 포트(Driving/Inbound Port)와 외부 시스템을 제어하는 주도당하는 포트(Driven/Outbound Port)를 엄격히 분리하여 결합도를 낮춥니다.
4. 영속성 분리 매퍼(Persistence Mapping) 필수화:
• 데이터베이스 스키마와 도메인 모델 간의 1:1 일치 가정을 버리고, Mapper를 통해 영속성 스키마의 변경이 핵심 도메인 로직을 침범하지 못하도록 차단합니다.

5. 실전 투입용 플레이스홀더 템플릿

[역할 정의] 당신은 엔터프라이즈 시스템 설계를 총괄하는 Principal Enterprise Software Architect입니다. [대상 비즈니스 도메인] - 서비스 도메인: [예: B2B SaaS 구독 및 월간 정산 결제 시스템] - 핵심 엔티티: [예: Subscription, Invoice, BillingProfile, PaymentTransaction] - 적용 기술 스택: [예: TypeScript / NestJS / Prisma 또는 Java / Spring Boot 3] [클린 아키텍처 설계 요구사항] 1. 계층 분리: Entities(순수 비즈니스 룰), UseCases(포트 제어), Interface Adapters(Controller, Repo), Frameworks 4계층 패키지 구조를 정의할 것. 2. 의존성 제약: 도메인 엔티티 내 ORM 어노테이션 사용을 금지하고, UseCase가 참조할 In/Outbound Port 인터페이스를 명시할 것. 3. 구현 코드: (1) 순수 도메인 엔티티, (2) Inbound Port 및 Outbound Port, (3) UseCase 서비스, (4) 영속성 어댑터와 데이터 변환 매퍼 코드를 작성할 것. 4. 검증 방안: 외부 의존성을 Mocking하여 UseCase를 100% 격리 검증하는 단위 테스트 코드를 포함할 것.

6. 프롬프트 활용 팁 & 주의사항

1. 인바운드 포트와 DTO 인터페이스 불변성 보장:
• 컨트롤러의 요청 본문(RequestBody) 객체를 Use Case 내부로 직접 전달하지 마십시오. Use Case 전용 Command/Query DTO를 정의하여 웹 프레임워크의 요청 파싱 방식이 도메인 인터페이스를 오염시키는 것을 차단하십시오.
2. 도메인 이벤트(Domain Events) 발행 시점 격리:
• 도메인 내부에서 이벤트를 즉시 외부 메시지 브로커(Kafka, RabbitMQ)로 발송하지 마십시오. 도메인 엔티티는 registerEvent() 형태로 내부 메모리에 적재하고, Use Case 트랜잭션이 성공적으로 커밋된 후 Outbox Adapter를 통해 비동기 발행되도록 설계하십시오.
3. 과도한 엔지니어링(Over-Engineering) 경계:
• 단순 조회(CRUD)나 변경 가능성이 전무한 데이터 조회 기능의 경우, 4계층을 엄격히 거치기보다 읽기 전용 쿼리 포트(Query Port)를 통해 직접 DTO로 프로젝션하는 실용적 CQRS 패턴을 적용하여 코드 중복을 최소화하십시오.
4. 프롬프트 질의 시 의존성 방향 다이어그램 동시 요청:
• 프롬프트에 Mermaid classDiagram 또는 계층 간 의존성 흐름도를 함께 요청하면 AI가 잘못된 역방향 import를 생성하는 오류를 크게 줄일 수 있습니다.

댓글 쓰기

다음 이전