| name | hexagonal-architecture |
| description | Design, implement, and refactor Ports & Adapters systems with clear domain boundaries, dependency inversion, and testable use-case orchestration across TypeScript, Java, Kotlin, and Go services. |
| origin | ECC |
Hexagonal Architecture
ãããµãŽãã«ã¢ãŒããã¯ãã£ïŒPorts and AdaptersïŒã¯ãããžãã¹ããžãã¯ããã¬ãŒã ã¯ãŒã¯ããã©ã³ã¹ããŒããæ°žç¶åã®è©³çްããç¬ç«ãããŸããã³ã¢ã¢ããªã±ãŒã·ã§ã³ã¯æœè±¡çãªããŒãã«äŸåããã¢ããã¿ãŒããšããžéšåã§ãããã®ããŒããå®è£
ããŸãã
䜿çšã¿ã€ãã³ã°
- é·æçãªä¿å®æ§ãšãã¹ã¿ããªãã£ãéèŠãªæ°æ©èœãæ§ç¯ããå Žåã
- ãã¡ã€ã³ããžãã¯ã I/O é¢é£ã®åŠçãšæ··åšããŠããã¬ã€ã€ãŒãæ§é ããã¬ãŒã ã¯ãŒã¯äŸåã®åŒ·ãã³ãŒãããªãã¡ã¯ã¿ãªã³ã°ããå Žåã
- åäžã®ãŠãŒã¹ã±ãŒã¹ã«å¯ŸããŠè€æ°ã®ã€ã³ã¿ãŒãã§ãŒã¹ããµããŒãããå ŽåïŒHTTPãCLIããã¥ãŒã¯ãŒã«ãŒãcronãžã§ãïŒã
- ããžãã¹ã«ãŒã«ãæžãæããã«ã€ã³ãã©ïŒããŒã¿ããŒã¹ãå€éšAPIãã¡ãã»ãŒãžãã¹ïŒã眮ãæããå Žåã
ãªã¯ãšã¹ããå¢çããã¡ã€ã³äžå¿ã®èšèšãå¯çµåãµãŒãã¹ã®ãªãã¡ã¯ã¿ãªã³ã°ããŸãã¯ç¹å®ã®ã©ã€ãã©ãªããã®ã¢ããªã±ãŒã·ã§ã³ããžãã¯ã®åé¢ã«é¢ããå Žåã«ãã®ã¹ãã«ã䜿çšããŠãã ããã
ã³ã¢ã³ã³ã»ãã
- ãã¡ã€ã³ã¢ãã«: ããžãã¹ã«ãŒã«ãšãšã³ãã£ãã£/å€ãªããžã§ã¯ãããã¬ãŒã ã¯ãŒã¯ã®ã€ã³ããŒããªãã
- ãŠãŒã¹ã±ãŒã¹ïŒã¢ããªã±ãŒã·ã§ã³å±€ïŒ: ãã¡ã€ã³ã®æ¯ãèããšã¯ãŒã¯ãããŒã¹ãããããªãŒã±ã¹ãã¬ãŒã·ã§ã³ããŸãã
- ã€ã³ããŠã³ãããŒã: ã¢ããªã±ãŒã·ã§ã³ãäœãè¡ããããèšè¿°ããã³ã³ãã©ã¯ãïŒã³ãã³ã/ã¯ãšãª/ãŠãŒã¹ã±ãŒã¹ã€ã³ã¿ãŒãã§ãŒã¹ïŒã
- ã¢ãŠãããŠã³ãããŒã: ã¢ããªã±ãŒã·ã§ã³ãå¿
èŠãšããäŸåé¢ä¿ã®ã³ã³ãã©ã¯ãïŒãªããžããªãã²ãŒããŠã§ã€ãã€ãã³ããããªãã·ã£ãŒãã¯ããã¯ãUUID ãªã©ïŒã
- ã¢ããã¿ãŒ: ããŒãã®ã€ã³ãã©ããã³ããªããªãŒå®è£
ïŒHTTPã³ã³ãããŒã©ãŒãDBãªããžããªããã¥ãŒã³ã³ã·ã¥ãŒããŒãSDKã©ãããŒïŒã
- ã³ã³ããžã·ã§ã³ã«ãŒã: å
·äœçãªã¢ããã¿ãŒããŠãŒã¹ã±ãŒã¹ã«ãã€ã³ãããåäžã®é
ç·å Žæã
ã¢ãŠãããŠã³ãããŒãã€ã³ã¿ãŒãã§ãŒã¹ã¯éåžžã¢ããªã±ãŒã·ã§ã³å±€ã«é
眮ãããŸãïŒæœè±¡åãçã«ãã¡ã€ã³ã¬ãã«ã®å Žåã®ã¿ãã¡ã€ã³å±€ã«é
眮ïŒãäžæ¹ãã€ã³ãã©ã¢ããã¿ãŒãããããå®è£
ããŸãã
äŸåé¢ä¿ã®æ¹åã¯åžžã«å
åãã§ã:
- ã¢ããã¿ãŒ -> ã¢ããªã±ãŒã·ã§ã³/ãã¡ã€ã³
- ã¢ããªã±ãŒã·ã§ã³ -> ããŒãã€ã³ã¿ãŒãã§ãŒã¹ïŒã€ã³ããŠã³ã/ã¢ãŠãããŠã³ãã³ã³ãã©ã¯ãïŒ
- ãã¡ã€ã³ -> ãã¡ã€ã³ã®ã¿ã®æœè±¡åïŒãã¬ãŒã ã¯ãŒã¯ãã€ã³ãã©ã®äŸåé¢ä¿ãªãïŒ
- ãã¡ã€ã³ -> å€éšãžã®äŸåãªã
ä»çµã¿
ã¹ããã 1: ãŠãŒã¹ã±ãŒã¹å¢çãã¢ãã«åãã
æç¢ºãªå
¥åºå DTO ãæã€åäžã®ãŠãŒã¹ã±ãŒã¹ãå®çŸ©ããŸãããã©ã³ã¹ããŒãã®è©³çްïŒExpress ã® reqãGraphQL ã® contextããžã§ããã€ããŒãã©ãããŒïŒã¯ãã®å¢çã®å€ã«ä¿ã¡ãŸãã
ã¹ããã 2: ãŸãã¢ãŠãããŠã³ãããŒããå®çŸ©ãã
ãã¹ãŠã®å¯äœçšãããŒããšããŠç¹å®ããŸã:
- æ°žç¶åïŒ
UserRepositoryPortïŒ
- å€éšåŒã³åºãïŒ
BillingGatewayPortïŒ
- 暪æçé¢å¿äºïŒ
LoggerPortãClockPortïŒ
ããŒãã¯ãã¯ãããžãŒã§ã¯ãªããã±ã€ãããªãã£ãã¢ãã«åãã¹ãã§ãã
ã¹ããã 3: çŽç²ãªãªãŒã±ã¹ãã¬ãŒã·ã§ã³ãšããŠãŠãŒã¹ã±ãŒã¹ãå®è£
ãã
ãŠãŒã¹ã±ãŒã¹ã®ã¯ã©ã¹/颿°ã¯ã³ã³ã¹ãã©ã¯ã¿/åŒæ°ãä»ããŠããŒããåãåããŸããã¢ããªã±ãŒã·ã§ã³ã¬ãã«ã®äžå€æ¡ä»¶ãæ€èšŒãããã¡ã€ã³ã«ãŒã«ã調æŽãããã¬ãŒã³ãªããŒã¿æ§é ãè¿ããŸãã
ã¹ããã 4: ãšããžã«ã¢ããã¿ãŒãæ§ç¯ãã
- ã€ã³ããŠã³ãã¢ããã¿ãŒã¯ãããã³ã«å
¥åããŠãŒã¹ã±ãŒã¹å
¥åã«å€æããŸãã
- ã¢ãŠãããŠã³ãã¢ããã¿ãŒã¯ã¢ããªã±ãŒã·ã§ã³ã®ã³ã³ãã©ã¯ããå
·äœç㪠API/ORM/ã¯ãšãªãã«ããŒã«ãããã³ã°ããŸãã
- ãããã³ã°ã¯ãŠãŒã¹ã±ãŒã¹å
éšã§ã¯ãªãã¢ããã¿ãŒå
ã«çããŸãã
ã¹ããã 5: ã³ã³ããžã·ã§ã³ã«ãŒãã§ãã¹ãŠãé
ç·ãã
ã¢ããã¿ãŒãã€ã³ã¹ã¿ã³ã¹åãããŠãŒã¹ã±ãŒã¹ã«æ³šå
¥ããŸãããã®é
ç·ãéäžåããŠãé ãããµãŒãã¹ãã±ãŒã¿ãŒã®æ¯ãèããé¿ããŸãã
ã¹ããã 6: å¢çããšã«ãã¹ããã
- ãã§ã€ã¯ããŒãã䜿ã£ãŠãŠãŒã¹ã±ãŒã¹ããŠããããã¹ãããŸãã
- å®éã®ã€ã³ãã©äŸåé¢ä¿ã䜿ã£ãŠã¢ããã¿ãŒãçµ±åãã¹ãããŸãã
- ã€ã³ããŠã³ãã¢ããã¿ãŒãéããŠãŠãŒã¶ãŒåããããŒã E2E ãã¹ãããŸãã
ã¢ãŒããã¯ãã£å³
flowchart LR
Client["Client (HTTP/CLI/Worker)"] --> InboundAdapter["Inbound Adapter"]
InboundAdapter -->|"calls"| UseCase["UseCase (Application Layer)"]
UseCase -->|"uses"| OutboundPort["OutboundPort (Interface)"]
OutboundAdapter["Outbound Adapter"] -->|"implements"| OutboundPort
OutboundAdapter --> ExternalSystem["DB/API/Queue"]
UseCase --> DomainModel["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
TypeScript ã®äŸ
ããŒãå®çŸ©
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,
});
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);
};
å€èšèªãããã³ã°
ãšã³ã·ã¹ãã éã§åãå¢çã«ãŒã«ã䜿çšããŸããå€ããã®ã¯æ§æãšé
ç·ã¹ã¿ã€ã«ã®ã¿ã§ãã
- TypeScript/JavaScript
- ããŒã:
application/ports/* ãšããŠã€ã³ã¿ãŒãã§ãŒã¹/åãå®çŸ©ã
- ãŠãŒã¹ã±ãŒã¹: ã³ã³ã¹ãã©ã¯ã¿/åŒæ°æ³šå
¥ã«ããã¯ã©ã¹/颿°ã
- ã¢ããã¿ãŒ:
adapters/inbound/*ãadapters/outbound/*ã
- ã³ã³ããžã·ã§ã³: æç€ºçãªãã¡ã¯ããª/ã³ã³ããã¢ãžã¥ãŒã«ïŒé ããã°ããŒãã«ãªãïŒã
- Java
- ããã±ãŒãž:
domainãapplication.port.inãapplication.port.outãapplication.usecaseãadapter.inãadapter.outã
- ããŒã:
application.port.* å
ã®ã€ã³ã¿ãŒãã§ãŒã¹ã
- ãŠãŒã¹ã±ãŒã¹: ãã¬ãŒã³ã¯ã©ã¹ïŒSpring ã®
@Service ã¯ãªãã·ã§ã³ãå¿
é ã§ã¯ãªãïŒã
- ã³ã³ããžã·ã§ã³: Spring èšå®ãŸãã¯æåé
ç·ã¯ã©ã¹ãé
ç·ããã¡ã€ã³/ãŠãŒã¹ã±ãŒã¹ã¯ã©ã¹ã®å€ã«ä¿ã€ã
- Kotlin
- ã¢ãžã¥ãŒã«/ããã±ãŒãžã¯ Java ã®åå²ããã©ãŒãªã³ã°ïŒ
domainãapplication.portãapplication.usecaseãadapterïŒã
- ããŒã: Kotlin ã€ã³ã¿ãŒãã§ãŒã¹ã
- ãŠãŒã¹ã±ãŒã¹: ã³ã³ã¹ãã©ã¯ã¿æ³šå
¥ã«ããã¯ã©ã¹ïŒKoin/Dagger/Spring/æåïŒã
- ã³ã³ããžã·ã§ã³: ã¢ãžã¥ãŒã«å®çŸ©ãŸãã¯å°çšã®ã³ã³ããžã·ã§ã³é¢æ°ããµãŒãã¹ãã±ãŒã¿ãŒãã¿ãŒã³ãé¿ããã
- Go
- ããã±ãŒãž:
internal/<feature>/domainãapplicationãportsãadapters/inboundãadapters/outboundã
- ããŒã: æ¶è²»åŽã®ã¢ããªã±ãŒã·ã§ã³ããã±ãŒãžãææããå°ããªã€ã³ã¿ãŒãã§ãŒã¹ã
- ãŠãŒã¹ã±ãŒã¹: ã€ã³ã¿ãŒãã§ãŒã¹ãã£ãŒã«ããæã€æ§é äœãšæç€ºçãª
New... ã³ã³ã¹ãã©ã¯ã¿ã
- ã³ã³ããžã·ã§ã³:
cmd/<app>/main.goïŒãŸãã¯å°çšã®é
ç·ããã±ãŒãžïŒã§é
ç·ããã³ã³ã¹ãã©ã¯ã¿ã¯æç€ºçã«ä¿ã€ã
é¿ããã¹ãã¢ã³ããã¿ãŒã³
- ãã¡ã€ã³ãšã³ãã£ãã£ã ORM ã¢ãã«ãWeb ãã¬ãŒã ã¯ãŒã¯åããŸã㯠SDK ã¯ã©ã€ã¢ã³ããã€ã³ããŒãããããšã
- ãŠãŒã¹ã±ãŒã¹ã
reqãresããŸãã¯ãã¥ãŒã¡ã¿ããŒã¿ããçŽæ¥èªã¿åãããšã
- ãã¡ã€ã³/ã¢ããªã±ãŒã·ã§ã³ãããã³ã°ãªãã«ãŠãŒã¹ã±ãŒã¹ããããŒã¿ããŒã¹è¡ãçŽæ¥è¿ãããšã
- ãŠãŒã¹ã±ãŒã¹ããŒããçµç±ããã«ã¢ããã¿ãŒå士ãçŽæ¥åŒã³åºãåãããšã
- é ããã°ããŒãã«ã·ã³ã°ã«ãã³ãæã€å€æ°ã®ãã¡ã€ã«ã«äŸåé¢ä¿ã®é
ç·ã忣ãããããšã
ç§»è¡ãã¬ã€ããã¯
- 倿Žã®çã¿ãé »ç¹ã«çºçãã1ã€ã®åçŽã¹ã©ã€ã¹ïŒåäžã®ãšã³ããã€ã³ã/ãžã§ãïŒãéžæããŸãã
- æç€ºçãªå
¥åºååãæã€ãŠãŒã¹ã±ãŒã¹å¢çãæœåºããŸãã
- æ¢åã®ã€ã³ãã©åŒã³åºãã®åšãã«ã¢ãŠãããŠã³ãããŒããå°å
¥ããŸãã
- ã³ã³ãããŒã©ãŒ/ãµãŒãã¹ãããŠãŒã¹ã±ãŒã¹ã«ãªãŒã±ã¹ãã¬ãŒã·ã§ã³ããžãã¯ãç§»åããŸãã
- æ§ã¢ããã¿ãŒãç¶æããŸãããæ°ãããŠãŒã¹ã±ãŒã¹ã«åŠçãå§è²ãããŸãã
- æ°ããå¢çã®åšãã«ãã¹ãã远å ããŸãïŒãŠããã + ã¢ããã¿ãŒçµ±åïŒã
- ã¹ã©ã€ã¹ããšã«ç¹°ãè¿ããŸããå
šé¢çãªæžãçŽãã¯é¿ããŠãã ããã
æ¢åã·ã¹ãã ã®ãªãã¡ã¯ã¿ãªã³ã°
- ã¹ãã©ã³ã°ã©ãŒã¢ãããŒã: çŸè¡ã®ãšã³ããã€ã³ããç¶æããäžåºŠã«1ã€ã®ãŠãŒã¹ã±ãŒã¹ãæ°ããããŒã/ã¢ããã¿ãŒãéããŠã«ãŒãã£ã³ã°ããŸãã
- ããã°ãã³æžãæãã¯è¡ããªã: ãã£ãŒãã£ãŒã¹ã©ã€ã¹ããšã«ç§»è¡ããç¹æ§ãã¹ãã§æ¯ãèããä¿æããŸãã
- ãŸããã¡ãµãŒã: å
éšã眮ãæããåã«ãã¬ã¬ã·ãŒãµãŒãã¹ãã¢ãŠãããŠã³ãããŒãã®èåŸã«ã©ããããŸãã
- ã³ã³ããžã·ã§ã³ããªãŒãº: é
ç·ãæ©æã«éäžåããæ°ããäŸåé¢ä¿ããã¡ã€ã³/ãŠãŒã¹ã±ãŒã¹å±€ã«æŒããªãããã«ããŸãã
- ã¹ã©ã€ã¹éžæã«ãŒã«: 倿Žé »åºŠãé«ãã圱é¿ç¯å²ãå°ãããããŒãåªå
ããŸãã
- ããŒã«ããã¯ãã¹: æ¬çªç°å¢ã§ã®æ¯ãèãã確èªããããŸã§ãç§»è¡ãããã¹ã©ã€ã¹ããšã«å¯éçãªãã°ã«ãŸãã¯ã«ãŒãã¹ã€ãããç¶æããŸãã
ãã¹ãã¬ã€ãã³ã¹ïŒåããããµãŽãã«å¢çïŒ
- ãã¡ã€ã³ãã¹ã: ãšã³ãã£ãã£/å€ãªããžã§ã¯ããçŽç²ãªããžãã¹ã«ãŒã«ãšããŠãã¹ãããŸãïŒã¢ãã¯ãªãããã¬ãŒã ã¯ãŒã¯ã»ããã¢ãããªãïŒã
- ãŠãŒã¹ã±ãŒã¹ãŠããããã¹ã: ã¢ãŠãããŠã³ãããŒãã®ãã§ã€ã¯/ã¹ã¿ãã䜿ã£ãŠãªãŒã±ã¹ãã¬ãŒã·ã§ã³ããã¹ãããããžãã¹ææãšããŒãã®ã€ã³ã¿ã©ã¯ã·ã§ã³ãã¢ãµãŒãããŸãã
- ã¢ãŠãããŠã³ãã¢ããã¿ãŒã³ã³ãã©ã¯ããã¹ã: ããŒãã¬ãã«ã§å
±æã³ã³ãã©ã¯ãã¹ã€ãŒããå®çŸ©ããåã¢ããã¿ãŒå®è£
ã«å¯ŸããŠå®è¡ããŸãã
- ã€ã³ããŠã³ãã¢ããã¿ãŒãã¹ã: ãããã³ã«ãããã³ã°ïŒHTTP/CLI/ãã¥ãŒãã€ããŒããããŠãŒã¹ã±ãŒã¹å
¥åãžã®å€æãããã³åºå/ãšã©ãŒã®ãããã³ã«ãžã®éãããã³ã°ïŒãæ€èšŒããŸãã
- ã¢ããã¿ãŒçµ±åãã¹ã: ã·ãªã¢ã©ã€ãŒãŒã·ã§ã³ãã¹ããŒã/ã¯ãšãªã®æ¯ãèãããªãã©ã€ãã¿ã€ã ã¢ãŠãã®ããã«å®éã®ã€ã³ãã©ïŒDB/API/ãã¥ãŒïŒã«å¯ŸããŠå®è¡ããŸãã
- ãšã³ãããŒãšã³ããã¹ã: ã€ã³ããŠã³ãã¢ããã¿ãŒ -> ãŠãŒã¹ã±ãŒã¹ -> ã¢ãŠãããŠã³ãã¢ããã¿ãŒãéããŠéèŠãªãŠãŒã¶ãŒãžã£ãŒããŒãã«ããŒããŸãã
- ãªãã¡ã¯ã¿ãªã³ã°ã®å®å
šæ§: æœåºåã«ç¹æ§ãã¹ãã远å ããæ°ããå¢çã®æ¯ãèããå®å®ããŠç䟡ã«ãªããŸã§ããããç¶æããŸãã
ãã¹ããã©ã¯ãã£ã¹ãã§ãã¯ãªã¹ã
- ãã¡ã€ã³ãšãŠãŒã¹ã±ãŒã¹å±€ã¯å
éšåãšããŒãã®ã¿ãã€ã³ããŒãããŸãã
- ãã¹ãŠã®å€éšäŸåé¢ä¿ã¯ã¢ãŠãããŠã³ãããŒãã§è¡šçŸãããŸãã
- ããªããŒã·ã§ã³ã¯å¢çã§è¡ããŸãïŒã€ã³ããŠã³ãã¢ããã¿ãŒ + ãŠãŒã¹ã±ãŒã¹ã®äžå€æ¡ä»¶ïŒã
- ã€ãã¥ãŒã¿ãã«ãªå€æã䜿çšããŸãïŒå
±æç¶æ
ã倿Žãã代ããã«æ°ããå€/ãšã³ãã£ãã£ãè¿ãïŒã
- ãšã©ãŒã¯å¢çãè·šãã§å€æãããŸãïŒã€ã³ãã©ãšã©ãŒ -> ã¢ããªã±ãŒã·ã§ã³/ãã¡ã€ã³ãšã©ãŒïŒã
- ã³ã³ããžã·ã§ã³ã«ãŒãã¯æç€ºçã§ç£æ»ãããããã®ã«ããŸãã
- ãŠãŒã¹ã±ãŒã¹ã¯ããŒãã®ã·ã³ãã«ãªã€ã³ã¡ã¢ãªãã§ã€ã¯ã§ãã¹ãå¯èœã§ãã
- ãªãã¡ã¯ã¿ãªã³ã°ã¯æ¯ãèããä¿æãããã¹ãã䌎ã1ã€ã®åçŽã¹ã©ã€ã¹ããéå§ããŸãã
- èšèª/ãã¬ãŒã ã¯ãŒã¯åºæã®äºé
ã¯ã¢ããã¿ãŒã«çãããã¡ã€ã³ã«ãŒã«ã«ã¯å
¥ããŸããã