System Design Cases
Hexagonal Architecture (Ports & Adapters)
Hexagonal Architecture (Ports & Adapters) by Alistair Cockburn (2005). Domain в центре, adapters снаружи. Driving adapters (REST/CLI/Kafka/gRPC/Cron) -> driving ports -> application services -> domain entities. Services также вызывают driven ports (OrderRepository, PaymentGateway, EmailNotifier, EventPublisher) которые реализуются driven adapters снаружи (PostgresOrderRepo, DynamoOrderRepo (alt), InMemoryOrderRepo (test), StripeAdapter, SmtpEmailAdapter, KafkaEventPublisher). Dependency inversion: hexagon определяет interfaces, infrastructure реализует. 3 scenarios: REST->hexagon->Postgres canonical happy path, swap Postgres->DynamoDB без изменений в hexagon, test domain в isolation через in-memory adapters. ADRs: hexagonal vs layered 3-tier when to apply, mapping DTO<->domain entity boilerplate trade-offs.
Hexagonal architecture: ports, adapters and semantic contracts
Cockburn’s Ports and Adapters isolates application logic from UI, database and other devices. Source dependencies point toward the application-owned ports, while runtime calls can travel outward through a driven port. The hexagon’s number of sides is not material, and replacing an adapter is not guaranteed to be a one-line DI change.
Что утверждает паттерн — и чего он не гарантирует
- Cockburn’s intent is an application runnable/testable without UI or database and connectable through adapters.
- Driving adapters invoke application ports; driven adapters implement ports owned by the application. Naming primary/secondary or inbound/outbound is secondary to the dependency boundary.
- Runtime control flow may point outward while source dependency remains inward through dependency inversion.
- Adapter replaceability is conditional on semantic contract and data migration, not guaranteed by interface syntax.
Границы и компоненты
| Компонент | Ответственность |
|---|---|
| HTTP Driving Adapter | Переводит HTTP/auth/DTO в application command. |
| CLI Driving Adapter | Вызывает тот же use-case port без HTTP assumptions. |
| Inbound Use-Case Port | Application-owned semantic command contract. |
| Application Core | Выполняет policy и оркестрацию без knowledge of devices. |
| Outbound Repository Port | Определяет required persistence semantics языком core. |
| Postgres Adapter | Реализует repository contract с transaction/concurrency mapping. |
| Outbound Payment Port | Определяет idempotency/status semantics внешнего charge. |
| Payment Provider Adapter | Переводит provider protocol и ambiguous outcomes. |
| Durable Integration Outbox | Сохраняет publish intent для retry/reconciliation. |
Сценарии
Drive one use case through two adapters
HTTP and CLI normalize input into the same application command; transport-specific authentication and rendering stay outside the core.
Проверяемый исход: The core behavior is testable without a web server, and adapters cannot silently invent different business rules.
Persist through an application-owned port
The core asks for compare-and-set persistence semantics. The Postgres adapter implements the port and maps conflicts explicitly.
Проверяемый исход: The core never receives ORM rows; concurrency and transaction behavior are part of the port contract.
Handle an ambiguous payment outcome
The core supplies a stable idempotency key. A timeout after provider acceptance is unknown, not a definite decline; adapter queries/reconciles before retrying effect.
Проверяемый исход: No duplicate charge is created merely because a socket timed out.
Swap a persistence adapter safely
A candidate adapter passes semantic contract tests, data migration/backfill and shadow comparison before routing changes. DI alone cannot preserve unsupported transactions or queries.
Проверяемый исход: Cutover is gated on capability, data and failure equivalence; rollback remains possible.
Failure, concurrency и evolution checklist
- Contract-test success, conflict, timeout, cancellation and retry semantics for every adapter.
- Do not leak transport DTO, ORM row or provider enum across a core boundary.
- Use stable idempotency identities and explicit unknown state for remote effects.
- Persist integration intent durably; an in-memory callback chain is not an outbox.
Метрики, units и допущения
- Port timeout, retry budget and idempotency retention use explicit milliseconds/seconds/days.
- Adapter capacity is measured end-to-end under the required semantic contract; interface conformance gives no throughput guarantee.
- Migration completeness uses counts/hashes/invariants by range plus observed shadow mismatch rate, not only a successful DI startup.
Числа здесь — размерностные формулы или явно помеченные учебные inputs. Паттерн архитектуры сам по себе не задаёт SLA, throughput, latency или fault tolerance.
Связанные темы
Первичные источники
- https://alistair.cockburn.us/hexagonal-architecture/
- https://alistaircockburn.com/Articles/Hexagonal-Architecture
Scope note
Hexagonal architecture is a structural pattern. It does not by itself provide transactions, durable messaging, idempotency, authorization, adapter equivalence or a zero-downtime migration.