Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Instruções da origem · Visualização somente leitura
name
hexagonal-architecture
description
Ports and Adapters isolating application core from external concerns. Trigger: When building testable services with swappable infrastructure.
license
Apache 2.0
metadata
{"version":"1.0","type":"domain"}
Hexagonal Architecture (Ports and Adapters)
Isolates application core from external systems. Core defines interfaces (ports), external systems provide implementations (adapters). Enables easy swapping of implementations and testing via mocks.
When to Use
Application needs to be testable without real DB/email/external APIs
Need to swap infrastructure (Postgres → MySQL, SendGrid → AWS SES)
Multiple adapters for same port (REST + CLI + GraphQL)
Business logic must be isolated from framework details
Don't use for:
Simple CRUD with no testability requirements
Scripts with single external dependency
Prototypes
Critical Patterns
✅ REQUIRED: Define Ports in Application Core
Ports are interfaces owned by the application, not by infrastructure.
// ❌ WRONG: Application use case imports concrete implementationimport { PrismaClient } from'@prisma/client'; // Infrastructure in application!classRegisterUserUseCase {
private db = newPrismaClient(); // Tightly coupled
}
// ✅ CORRECT: Depend on interfaceclassRegisterUserUseCase {
constructor(privateuserRepo: IUserRepository) {} // Inject via port
}
✅ REQUIRED: Know Primary vs Secondary Ports
Primary (Driving) Ports: Exposed by core, called by adapters
→ HTTP Controller calls use case (driving adapter)
→ CLI calls use case (another driving adapter)
Secondary (Driven) Ports: Defined by core, implemented by infrastructure
→ IUserRepository ← PostgresRepository
→ IEmailService ← SendGridEmailService
✅ REQUIRED: Frontend Adapter Pattern (React)
Same principle in the browser: define the port in the feature, implement with fetch/axios, swap with mock in tests.
// Port — owned by the feature, not by the API layerinterfaceIUserApi {
getUser(id: string): Promise<User>;
updateUser(id: string, data: Partial<User>): Promise<User>;
}
// Secondary adapter — implements the port with real network callsclassRestUserApiimplementsIUserApi {
asyncgetUser(id: string) { returnfetch(`/api/users/${id}`).then(r => r.json()); }
asyncupdateUser(id: string, data: Partial<User>) { returnfetch(`/api/users/${id}`, { method: 'PATCH', body: JSON.stringify(data) }).then(r => r.json()); }
}
// Mock adapter — same port, no network (for tests and Storybook)classMockUserApiimplementsIUserApi {
asyncgetUser(id: string) { return { id, name: 'Test User', email: 'test@example.com' }; }
asyncupdateUser(id: string, data: Partial<User>) { return { id, ...data } asUser; }
}
// Driving adapter — hook consumes the port; concrete impl injected at composition rootfunctionuseUser(id: string, api: IUserApi = newRestUserApi()) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => { api.getUser(id).then(setUser); }, [id]);
return user;
}
// Tests: inject MockUserApi — no HTTP calls, no server needed
Decision Tree
Need to test without real infrastructure?
→ Define port (interface) in application layer
→ Inject mock in tests
Need to swap DB/email/API without changing business logic?
→ Implement new adapter for existing port
Core importing concrete infra class?
→ Extract interface, move concrete to infrastructure/
Multiple ways to trigger same use case (HTTP + CLI)?
→ Create separate driving adapters, both call same use case
Example
// PortinterfaceIPaymentGateway { charge(amount: number, token: string): Promise<PaymentResult>; }
// Use Case (core — no infra imports)// Result<T>: typed wrapper for success/failure — Result.ok(value) | Result.fail("error")classPlaceOrderUseCase {
constructor(privatepayment: IPaymentGateway) {}
asyncexecute(order: Order, token: string): Promise<Result<Order>> {
const result = awaitthis.payment.charge(order.total, token);
if (!result.success) returnResult.fail('Payment failed');
returnResult.ok(order);
}
}
// Adapters (infrastructure)classStripeAdapterimplementsIPaymentGateway { ... }
classPayPalAdapterimplementsIPaymentGateway { ... }
classMockAdapterimplementsIPaymentGateway { charge: jest.fn().mockResolvedValue({ success: true }) }
Edge Cases
Port granularity: Too many small ports = port explosion. Group related operations (IUserRepository with findById + save + delete, not separate interfaces).
Shared domain types: DTOs and domain entities cross layers but only move inward. Infrastructure adapters map to/from domain types.
Partial adoption: Can apply hexagonal to specific layers without full Clean Architecture. Most common: isolate DB + external APIs via ports.