hexagonal-layout
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHexagonal layout: keep a system's reason independent of its technology
Hexagonal布局:保持系统核心逻辑与技术解耦
A system holds three kinds of code, told apart by why each exists:
- The reason — why the system exists. Its business logic, domain types, and use-cases, plus the interfaces it declares for what it needs from outside. It owns those interfaces and depends on nothing outward.
- The connections — how it reaches the outside world. The implementations of those interfaces against external systems. Where all the I/O lives.
- How it's run — which connections to use, and how the program starts. Picks the concrete implementations, wires them into the reason, and is the entrypoint. It's where the connections and the reason meet.
Names vary — //, //, // are all the same three
kinds. Detect what a project uses and follow it (and any ADR that fixes the layout); the kinds are what matter,
not the labels.
coreadaptersrunappinfrarundomainadapterscmdYou'll reach for this lens in a few situations — scaffolding a new system, placing a change in an
existing one, reviewing how one is shaped, or writing down the layout decision in an ADR. The same
question drives them all: is this piece the reason, a connection, or how it's run — and what may it depend on?
一个系统包含三类代码,可根据其存在目的区分:
- 核心逻辑 — 系统存在的根本原因。包含业务逻辑、领域类型和用例,以及系统为满足外部需求而声明的接口。它拥有这些接口,且不依赖任何外部内容。
- 外部连接 — 系统与外部世界交互的方式。是针对外部系统的接口实现,所有I/O操作都位于此处。
- 运行配置 — 选择使用哪些连接,以及程序启动方式。负责选择具体实现,将其与核心逻辑绑定,是系统的入口点。这是外部连接与核心逻辑的交汇之处。
命名方式各不相同——//、//、//都对应这三类代码。要识别项目中使用的命名并遵循(以及任何确定布局的ADR);关键是代码的类型,而非标签名称。
coreadaptersrunappinfrarundomainadapterscmd在以下几种场景中可以应用此思路——搭建新系统、在现有系统中放置变更、评审系统架构形态,或者在ADR中记录布局决策。所有场景的核心问题都是一致的:这段代码属于核心逻辑、外部连接还是运行配置?它可以依赖哪些内容?
Which way dependencies point
依赖指向规则
Dependencies point toward the reason: the connections depend on it, the run code on both, and the reason on
neither. The reason declares the interfaces; the connections implement them; run wires them. The reason doesn't
reach out for a collaborator — it's handed one. (In the usual bucket names: .)
run → core ← adaptersAn outward arrow from the reason is worth a second look, and its kind tells you how much it weighs:
- A runtime dependency is loaded and run with the program — it pulls in behaviour and ties the reason to a concrete.
- A build-time dependency is about shape, not behaviour — a type, interface, constant, or enum referred to while compiling or type-checking — so it weighs less.
How much weight to give either is the project's call.
依赖指向核心逻辑:外部连接依赖核心逻辑,运行配置依赖前两者,而核心逻辑不依赖任何一方。核心逻辑声明接口;外部连接实现这些接口;运行配置负责绑定它们。核心逻辑不会主动寻找协作对象——而是由外部注入。(用常见的桶命名表示:)
run → core ← adapters如果核心逻辑存在向外的依赖,需要格外关注,依赖类型决定了其影响程度:
- 运行时依赖:会随程序加载并运行——它引入外部行为,将核心逻辑与具体实现绑定。
- 构建时依赖:仅涉及形态而非行为——比如编译或类型检查时引用的类型、接口、常量或枚举——因此影响程度较低。
如何权衡这两种依赖的影响,由项目团队决定。
What this buys
收益
Keeping the reason independent of its connections pays off three ways:
- Technology-independent — swap or defer an external choice without touching business logic.
- Testable — exercise the logic with stand-ins, no external system in the loop.
- Reusable — the same logic driven by more than one way of running it.
Hold this as loosely or firmly as the project decides; real codebases sit all along that spectrum. Read it as
"be aware of how the system is shaped," not "obey these rules."
保持核心逻辑与外部连接解耦,可带来三方面收益:
- 技术无关性:无需修改业务逻辑,即可替换或延迟选择外部技术。
- 可测试性:使用替身即可测试核心逻辑,无需依赖外部系统。
- 可复用性:同一核心逻辑可通过多种运行方式驱动。
项目团队可灵活决定遵循的严格程度;实际代码库的遵循程度各不相同。应将其理解为*“关注系统架构形态”,而非“严格遵守规则”*。
Reason, connection, or how it's run?
核心逻辑、外部连接还是运行配置?
Ask what a piece of code is for, and read its dependencies, not its surface topic:
| What it's for | Kind |
|---|---|
| A business rule, a domain concept, a use-case, or an interface the system needs | Reason |
| Reaching an external system to fulfil an interface the reason declared | Connection |
| Choosing which connections run, wiring them, and starting the process | How it's run |
When a change spans kinds, split it along the seam — the interface in the reason, its implementation in a
connection, the wiring in run — if the payoff is worth the seam for that change.
判断一段代码的类型,应关注它的用途及其依赖关系,而非表面主题:
| 用途 | 类型 |
|---|---|
| 业务规则、领域概念、用例,或系统所需的接口 | 核心逻辑 |
| 连接外部系统以实现核心逻辑声明的接口 | 外部连接 |
| 选择运行的连接、绑定它们并启动进程 | 运行配置 |
如果一项变更涉及多种类型的代码,可沿着边界拆分——核心逻辑中的接口、外部连接中的实现、运行配置中的绑定——只要拆分带来的收益大于成本。
Naming
命名规范
Top-level names should announce what the system is — its domain — not which pattern it uses. A generic
catch-all bucket becomes a junk drawer that hides intent and collects stray logic. Name buckets for intent; if
you group by kind, keep the names few. The split into the three kinds is what matters, not the labels.
顶层命名应明确系统的定位——即其领域——而非所使用的模式。通用的“万能桶”会变成隐藏意图、堆积零散逻辑的“杂物抽屉”。应根据用途命名桶;如果按类型分组,应尽量减少命名数量。关键是将代码分为三类,而非标签名称。
Smells to notice
需要注意的坏味道
Each lets a dependency point the wrong way and dulls a payoff. Notice them; fix the ones that cost a payoff
this project actually wants:
- I/O or an outside concrete in the reason — it can't run without the real system. Put it behind an interface the reason declares.
- Business logic in a connection — a second connection can't reuse it. Move the decision into the reason; leave the connection a thin translation.
- An interface declared with the connections instead of in the reason — inverts the arrow. The reason owns its interfaces, where they're needed.
- Wiring or config-reading in the reason — ties it to one deployment. Keep construction and selection in run.
- An anemic reason with the logic in run — bound to one way of running. Push use-cases into the reason; keep run thin.
How firmly to hold the line is the project's call — some teams stay aware of it, others add a check that flags
an arrow pointing the wrong way.
这些坏味道都会导致依赖指向错误,降低收益。要留意它们;修复那些会影响项目实际需求收益的问题:
- 核心逻辑中包含I/O或外部具体实现——没有真实系统就无法运行。应将其置于核心逻辑声明的接口之后。
- 外部连接中包含业务逻辑——其他外部连接无法复用该逻辑。应将决策逻辑移至核心逻辑;仅保留外部连接作为轻量转换层。
- 接口在外部连接中声明而非核心逻辑中——反转了依赖指向。核心逻辑应拥有自己所需的接口。
- 核心逻辑中包含绑定或配置读取逻辑——将其与特定部署绑定。应将构造和选择逻辑留在运行配置中。
- 核心逻辑贫血,逻辑位于运行配置中——与单一运行方式绑定。应将用例移至核心逻辑;保持运行配置轻量化。
团队可自行决定遵循的严格程度——有些团队仅保持关注,有些团队则添加检查机制来标记错误的依赖指向。
Workflow
工作流程
The situation sets where you start; the through-line is the same — name each kind and check its arrows.
- Detect or establish the layout — in an existing system, identify which bucket holds each kind, using the project's vocabulary and any ADR; don't rename. Scaffolding a new one, establish the three kinds and name the buckets for the domain (see Naming).
- Classify — is this the reason, a connection, or how it's run? If a change spans kinds, split it along the seam.
- Place it and check the arrow points toward the reason.
- Flag the smells that cost a payoff this project cares about; leave the trade-offs it made deliberately. Reviewing a whole system is mostly this step, walked across the codebase.
起始步骤取决于具体场景,但核心思路一致——明确每类代码的类型并检查依赖指向。
- 识别或确立布局——在现有系统中,使用项目的术语和ADR来确定每个桶对应的代码类型;不要重命名。搭建新系统时,确立三类代码,并根据领域命名桶(参见命名规范)。
- 分类——这段代码属于核心逻辑、外部连接还是运行配置?如果变更涉及多种类型,沿着边界拆分。
- 放置并检查依赖指向确保依赖指向核心逻辑。
- 标记影响收益的坏味道;保留团队刻意做出的权衡。评审整个系统时,主要就是遍历代码库执行此步骤。