From ecc
TypeScript, Java, Kotlin, Go 서비스 전반에서 명확한 도메인 경계, 의존성 역전 및 테스트 가능한 유스케이스 오케스트레이션을 갖춘 포트 및 어댑터(Ports & Adapters) 시스템을 설계, 구현 및 리팩터링합니다.
npx claudepluginhub sam42-lab/everything-claude-code-krThis skill uses the workspace's default tool permissions.
헥사고날 아키텍처(포트 및 어댑터)는 비즈니스 로직을 프레임워크, 전송 방식, 지속성 세부 사항으로부터 독립적으로 유지합니다. 핵심 애플리케이션은 추상적인 포트에 의존하고, 어댑터는 가장자리에서 이러한 포트를 구현합니다.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
헥사고날 아키텍처(포트 및 어댑터)는 비즈니스 로직을 프레임워크, 전송 방식, 지속성 세부 사항으로부터 독립적으로 유지합니다. 핵심 애플리케이션은 추상적인 포트에 의존하고, 어댑터는 가장자리에서 이러한 포트를 구현합니다.
요청에 경계 설정, 도메인 중심 설계, 밀접하게 결합된 서비스 리팩터링 또는 특정 라이브러리로부터 애플리케이션 로직 분리가 포함된 경우 이 스킬을 사용하십시오.
아웃바운드 포트 인터페이스는 일반적으로 애플리케이션 계층에 위치하며(추상화가 진정으로 도메인 수준인 경우에만 도메인에 위치), 인프라 어댑터는 이를 구현합니다.
의존성 방향은 항상 내부를 향합니다:
명확한 입력 및 출력 DTO를 사용하여 단일 유스케이스를 정의합니다. 전송 세부 사항(Express req, GraphQL context, 작업 페이로드 래퍼)을 이 경계 외부에 둡니다.
모든 부수 효과를 포트로 식별합니다:
UserRepositoryPort)BillingGatewayPort)LoggerPort, ClockPort)포트는 기술이 아니라 기능을 모델링해야 합니다.
유스케이스 클래스/함수는 생성자/인수를 통해 포트를 받습니다. 애플리케이션 수준의 불변성을 검증하고, 도메인 규칙을 조정하며, 평범한 데이터 구조를 반환합니다.
어댑터를 인스턴스화한 다음 유스케이스에 주입합니다. 숨겨진 서비스 로케이터 동작을 피하기 위해 이 배선을 중앙 집중화합니다.
flowchart LR
Client["클라이언트 (HTTP/CLI/Worker)"] --> InboundAdapter["인바운드 어댑터"]
InboundAdapter -->|"호출"| UseCase["유스케이스 (애플리케이션 계층)"]
UseCase -->|"사용"| OutboundPort["아웃바운드 포트 (인터페이스)"]
OutboundAdapter["아웃바운드 어댑터"] -->|"구현"| OutboundPort
OutboundAdapter --> ExternalSystem["DB/API/큐"]
UseCase --> DomainModel["도메인 모델"]
명확한 경계가 있는 기능 우선 조직을 사용하십시오:
src/
features/
orders/
domain/
Order.ts
OrderPolicy.ts
application/
ports/
inbound/
CreateOrder.ts
outbound/
OrderRepositoryPort.ts
PaymentGatewayPort.ts
use-cases/
CreateOrderUseCase.ts
adapters/
inbound/
http/
createOrderRoute.ts
outbound/
postgres/
PostgresOrderRepository.ts
stripe/
StripePaymentGateway.ts
composition/
ordersContainer.ts
export interface OrderRepositoryPort {
save(order: Order): Promise<void>;
findById(orderId: string): Promise<Order | null>;
}
export interface PaymentGatewayPort {
authorize(input: { orderId: string; amountCents: number }): Promise<{ authorizationId: string }>;
}
type CreateOrderInput = {
orderId: string;
amountCents: number;
};
type CreateOrderOutput = {
orderId: string;
authorizationId: string;
};
export class CreateOrderUseCase {
constructor(
private readonly orderRepository: OrderRepositoryPort,
private readonly paymentGateway: PaymentGatewayPort
) {}
async execute(input: CreateOrderInput): Promise<CreateOrderOutput> {
const order = Order.create({ id: input.orderId, amountCents: input.amountCents });
const auth = await this.paymentGateway.authorize({
orderId: order.id,
amountCents: order.amountCents,
});
// markAuthorized는 새로운 Order 인스턴스를 반환하며, 기존 인스턴스를 직접 수정하지 않습니다.
const authorizedOrder = order.markAuthorized(auth.authorizationId);
await this.orderRepository.save(authorizedOrder);
return {
orderId: order.id,
authorizationId: auth.authorizationId,
};
}
}
export class PostgresOrderRepository implements OrderRepositoryPort {
constructor(private readonly db: SqlClient) {}
async save(order: Order): Promise<void> {
await this.db.query(
"insert into orders (id, amount_cents, status, authorization_id) values ($1, $2, $3, $4)",
[order.id, order.amountCents, order.status, order.authorizationId]
);
}
async findById(orderId: string): Promise<Order | null> {
const row = await this.db.oneOrNone("select * from orders where id = $1", [orderId]);
return row ? Order.rehydrate(row) : null;
}
}
export const buildCreateOrderUseCase = (deps: { db: SqlClient; stripe: StripeClient }) => {
const orderRepository = new PostgresOrderRepository(deps.db);
const paymentGateway = new StripePaymentGateway(deps.stripe);
return new CreateOrderUseCase(orderRepository, paymentGateway);
};
에코시스템 전반에서 동일한 경계 규칙을 사용하십시오. 구문과 연결 스타일만 변경됩니다.
application/ports/* (인터페이스/타입으로 정의).adapters/inbound/*, adapters/outbound/*.domain, application.port.in, application.port.out, application.usecase, adapter.in, adapter.out.application.port.*에 위치한 인터페이스.@Service는 선택 사항이며 필수는 아님).domain, application.port, application.usecase, adapter).internal/<feature>/domain, application, ports, adapters/inbound, adapters/outbound.New... 생성자가 있는 구조체.cmd/<app>/main.go (또는 전용 배선 패키지)에서 연결하고 생성자를 명시적으로 유지하십시오.req, res 또는 큐 메타데이터를 직접 읽는 경우.