hexagonal-arch — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited hexagonal-arch (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The pattern, distilled: the domain core knows nothing about the world. Everything that touches the outside (HTTP, DB, queue, cron, third-party API) is an adapter. Adapters depend on the domain; the domain never depends on adapters. Pairs with ddd-architect — DDD answers "what's in the domain"; hexagonal answers "where does the domain live and what touches it".
Three layers, named by their role rather than their location:
UserRepo, EmailSender, PaymentGateway). The names are domain-meaningful, not technology-meaningful (UserRepo, not PostgresClient).PostgresUserRepo, SmtpEmailSender, StripePaymentGateway. Also one per testing strategy: InMemoryUserRepo, FakeEmailSender.The diagram is conventionally a hexagon (the "hex" in hexagonal) — the shape doesn't matter; the direction of arrows does.
This is the single non-negotiable rule.
HTTP handler → application service → domain service → aggregate
↑
uses port: UserRepo (interface)
↑
implements:
PostgresUserRepo (adapter)import "net/http", no from sqlalchemy ..., no from fastapi ... in domain files. Static check: open any file in the domain package and grep its imports.PostgresUserRepo imports User (the aggregate) and UserRepo (the port). The domain doesn't know PostgresUserRepo exists.If you can't draw an arrow from any file in your codebase inward without crossing the rule, the architecture is hexagonal.
Idioms differ; the rule is the same.
UserRepo; the adapter package (postgres) implements it. Adapter imports domain; domain has no idea about adapter. Idiomatic and clean.typing.Protocol (structural typing) in the domain module per python-architect §3. Same direction; no inheritance required. Adapters happen to satisfy the Protocol.This is the inverted direction from what frameworks usually suggest ("define interfaces in their own interfaces/ package"). The domain owns its contracts.
| Primary (driving) | Secondary (driven) | |
|---|---|---|
| Direction | World → application | Application → world |
| Examples | HTTP handlers, gRPC handlers, CLI commands, message-queue consumers, scheduled-job runners | DB repositories, HTTP clients to third parties, email senders, queue publishers, cache, file system |
| Initiates the call | Yes — receives external trigger | No — domain code calls them |
| Implements what | The application service or use-case entry point | A port the domain defined |
Both kinds of adapter are equally outside the domain. They differ only in who calls whom.
InMemoryUserRepo for tests; PostgresUserRepo in production. Tests don't need a real DB unless they're specifically integration-testing the adapter itself.This is where hexagonal pays back. The cost (defining ports, separating layers) is paid up front; the savings (fast, focused tests; swap-out for new technology) compound forever.
UserRepo.FindWithJoin(...) returning a SQL row, or EmailSender.SendWithMimeType(...) exposing email library types. Fix: rename the method, change the return type to domain types, add a translation step inside the adapter.PostgresUserRepo.PromoteToPremium(...) decides what "premium" means. Fix: the rule lives in the domain (User.PromoteToPremium()); the adapter just persists the new state.User.save() calling the DB. Fix: User is the data + behavior; UserRepo.save(user) does the persistence. The aggregate doesn't know how it gets persisted.domain/, application/, infrastructure/, interfaces/ doesn't make a codebase hexagonal. The dependency direction does. A flat folder structure can be hexagonal if the imports flow correctly; a deeply layered structure can be a mess if they don't.UserRepo interface with one implementation is ceremony.UserRepo and PostgresUserRepo and never have a second implementation (no fake, no test double — because you'll use a real test DB), the port is pointless. Use the concrete repo directly.The rule from improve-codebase-architecture: two adapters justify the seam; one adapter is hypothetical. The InMemoryUserRepo you use in tests usually counts as the second.
Go and Python examples — showing the domain core, port interface, application service, and adapter implementation — live in RECIPES.md. The imports flow inward in both: adapter → domain, never the reverse.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.