Designs, implements, and refactors hexagonal (ports & adapters) architecture with domain isolation, dependency inversion, and testable use cases in TypeScript, Java, Kotlin, Go services.
From everything-claude-codenpx claudepluginhub usernametron/claude-code-arsenalThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Hexagonal architecture (Ports and Adapters) keeps business logic independent from frameworks, transport, and persistence details. The core app depends on abstract ports, and adapters implement those ports at the edges.
Use this skill when the request involves boundaries, domain-centric design, refactoring tightly coupled services, or decoupling application logic from specific libraries.
Outbound port interfaces usually live in the application layer (or in domain only when the abstraction is truly domain-level), while infrastructure adapters implement them.
Dependency direction is always inward:
Define a single use case with a clear input and output DTO. Keep transport details (Express req, GraphQL context, job payload wrappers) outside this boundary.
Identify every side effect as a port:
UserRepositoryPort)BillingGatewayPort)LoggerPort, ClockPort)Ports should model capabilities, not technologies.
Use case class/function receives ports via constructor/arguments. It validates application-level invariants, coordinates domain rules, and returns plain data structures.
Instantiate adapters, then inject them into use cases. Keep this wiring centralized to avoid hidden service-locator behavior.
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"]
Use feature-first organization with explicit boundaries:
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 returns a new Order instance; it does not mutate in place.
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);
};
Use the same boundary rules across ecosystems; only syntax and wiring style change.
application/ports/* as interfaces/types.adapters/inbound/*, adapters/outbound/*.domain, application.port.in, application.port.out, application.usecase, adapter.in, adapter.out.application.port.*.@Service is optional, not required).domain, application.port, application.usecase, adapter).internal/<feature>/domain, application, ports, adapters/inbound, adapters/outbound.New... constructors.cmd/<app>/main.go (or dedicated wiring package), keep constructors explicit.req, res, or queue metadata.