What is hexagonal-architecture?
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.
affaan-m/ECC
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.
npx skills add https://github.com/affaan-m/ECC --skill "skills/hexagonal-architecture"Quick start
Install it or open the source, trigger it with a clear task, then follow the source workflow.
npx skills add https://github.com/affaan-m/ECC --skill "skills/hexagonal-architecture"Use hexagonal-architecture to help me with: [describe your task]. Before you begin, tell me what input you need, the steps you will follow, and the expected output.
5 key workflow steps, examples, and cautions are distilled below.
Continue to the workflowDirect answers
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.
It is relevant to workflows involving Testing, Engineering, Design.
SkillSignal detected this source-specific command: npx skills add https://github.com/affaan-m/ECC --skill "skills/hexagonal-architecture". Inspect the repository and command before running it.
The upstream source does not declare a dedicated Agent platform.
No obvious permission action was detected by the static rules. This is not proof that the Skill is safe.
This page combines upstream documentation with deterministic repository, quality, and static-risk signals. It is not described as a manual test or security review.
SkillSignal brief
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.
Useful in these contexts
Core capabilities
Distilled from the source
About 5 min · 12 sections
Building new features where long-term maintainability and testability matter.
Refactoring layered or framework-heavy code where domain logic is mixed with I/O concerns.
Supporting multiple interfaces for the same use case (HTTP, CLI, queue workers, cron jobs).
Replacing infrastructure (database, external APIs, message bus) without rewriting business rules.
Step 1: Model a use case boundary
Step 2: Define outbound ports first
Step 3: Implement the use case with pure orchestration
Step 4: Build adapters at the edge
Step 5: Wire everything in a composition root
Domain entities importing ORM models, web framework types, or SDK clients.
Use cases reading directly from req, res, or queue metadata.
Returning database rows directly from use cases without domain/application mapping.
Letting adapters call each other directly instead of flowing through use-case ports.
Quality breakdown
Based on traceable docs and repository signals; stars are not treated as quality.
Compare before choosing
These links are selected from shared tasks, functions, stacks, platforms, and same-name variants. Compare the source owner, documentation, permissions, and maintenance signals.
When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program
Use when the user says "review the design", "check the UI", or wants a comprehensive UI/UX review. Uses a 7-phase methodology covering interaction, responsiveness, accessibility, and more.
Use when shaping a Playwright suite — locator strategy, Page Object boundaries, fixture composition, flake-prevention architecture, CI-vs-local split — even on 'design our E2E tests'.
Use when writing Playwright E2E tests — browser automation, visual regression testing, Page Objects, fixtures, and reliable test patterns.
Design AI evals that catch regressions before users do: rubrics, test cases, failure modes, acceptance gates, and AI-SPEC artifacts.
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.