Part 1 · 1 chapters · ~8 min
Hexagonal and Clean Architecture in Real Code
Ports and adapters, the dependency rule, a domain free of frameworks, application services as use cases, driving and driven adapters, testing with fakes, and when the indirection is not worth it.
3
A use case with ports
code
// domain/payout.ts: no imports from express, pg or SDKs
export class Payout {
private constructor(readonly id: string, readonly accountId: string, readonly amount: Money, private state: PayoutState) {}
static request(id: string, acct: Account, amount: Money): Payout {
if (amount.gt(acct.available)) throw new InsufficientFunds(acct.id, amount);
return new Payout(id, acct.id, amount, 'reserved');
}
}
// application/ports.ts
export interface PayoutRepo { save(p: Payout, tx: Tx): Promise<void>; }
export interface AccountRepo { lockById(id: string, tx: Tx): Promise<Account>; }
// application/create-payout.ts
export class CreatePayout {
constructor(private uow: UnitOfWork, private accounts: AccountRepo, private payouts: PayoutRepo, private outbox: Outbox) {}
run(cmd: { id: string; accountId: string; amount: Money }) {
return this.uow.run(async tx => {
const acct = await this.accounts.lockById(cmd.accountId, tx);
const p = Payout.request(cmd.id, acct, cmd.amount);
await this.payouts.save(p, tx);
await this.outbox.add({ type: 'payout.requested', id: p.id }, tx); // same transaction
return p;
});
}
}
// test: new CreatePayout(fakeUow, inMemoryAccounts, inMemoryPayouts, inMemoryOutbox): runs in microsecondsPORTS AND ADAPTERS
the domain in the middle, technology at the edges
swipe the figure sideways, or tap expand for full screen
1/5
domain
The domain holds entities and rules (a payout cannot exceed the available balance) with no imports from HTTP, SQL or SDKs.
pure rules, no frameworksfast unit tests