@alveolus/arch 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/bin.mjs +4 -2
- package/dist/bin.mjs.map +1 -1
- package/dist/{cli-CwPCGjDg.mjs → docs-DsQHpTtV.mjs} +287 -38
- package/dist/docs-DsQHpTtV.mjs.map +1 -0
- package/dist/index.d.mts +90 -36
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/docs/core/application/command-handlers.md +617 -0
- package/docs/core/application/event-publishers.md +234 -0
- package/docs/core/application/event-translators.md +329 -0
- package/docs/core/application/index.md +99 -0
- package/docs/core/application/integration-events.md +277 -0
- package/docs/core/application/outbox.md +416 -0
- package/docs/core/application/query-handlers.md +292 -0
- package/docs/core/application/unit-of-work.md +352 -0
- package/docs/core/domain/aggregates.md +822 -0
- package/docs/core/domain/domain-errors.md +251 -0
- package/docs/core/domain/domain-events.md +292 -0
- package/docs/core/domain/domain-services.md +249 -0
- package/docs/core/domain/entities.md +431 -0
- package/docs/core/domain/index.md +93 -0
- package/docs/core/domain/ports.md +284 -0
- package/docs/core/domain/repositories.md +335 -0
- package/docs/core/domain/value-objects.md +425 -0
- package/docs/core/domain/views.md +265 -0
- package/docs/core/index.md +108 -0
- package/docs/core/strategic/anti-corruption-layers.md +349 -0
- package/docs/core/strategic/index.md +83 -0
- package/docs/core/strategic/open-host-services.md +287 -0
- package/docs/core/strategic/published-language.md +265 -0
- package/docs/core/utilities/result.md +413 -0
- package/docs/guide/agents.md +68 -0
- package/docs/guide/existing-project.md +105 -0
- package/docs/guide/getting-started.md +275 -0
- package/docs/guide/learning-path.md +123 -0
- package/docs/guide/project-layout.md +324 -0
- package/docs/guide/versioning.md +42 -0
- package/docs/integrations/index.md +112 -0
- package/docs/integrations/nestjs.md +169 -0
- package/docs/rules/index.md +183 -0
- package/docs/rules/layers/no-driving-shortcut.md +119 -0
- package/docs/rules/layers/no-impure-domain.md +189 -0
- package/docs/rules/layers/no-outward-import.md +184 -0
- package/docs/rules/layers/no-portless-adapter.md +123 -0
- package/docs/rules/strategic/no-cross-context-import.md +140 -0
- package/docs/rules/strategic/no-fat-shared-kernel.md +81 -0
- package/docs/rules/strategic/no-leaky-host-service.md +107 -0
- package/docs/rules/strategic/no-unmapped-context.md +111 -0
- package/docs/rules/tactical/no-aggregate-reference.md +139 -0
- package/docs/rules/tactical/no-foreign-command-dependency.md +119 -0
- package/docs/rules/tactical/no-foreign-query-dependency.md +106 -0
- package/docs/rules/tactical/no-loose-code.md +171 -0
- package/docs/rules/tactical/no-misplaced-class.md +146 -0
- package/docs/rules/tactical/no-public-field.md +113 -0
- package/docs/rules/tactical/no-stateful-service.md +102 -0
- package/docs/rules/tactical/no-thrown-failure.md +162 -0
- package/docs/rules/tooling/no-loose-disable.md +98 -0
- package/package.json +4 -3
- package/dist/cli-CwPCGjDg.mjs.map +0 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Install @alveolus/core and @alveolus/arch, write your first aggregate in TypeScript and check your Domain-Driven Design architecture with alveolus arch check."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Getting started
|
|
6
|
+
|
|
7
|
+
Alveolus comes as two packages: `@alveolus/core`, the building blocks your code extends, and
|
|
8
|
+
`@alveolus/arch`, the command that checks your project keeps the architecture.
|
|
9
|
+
|
|
10
|
+
<dl class="al-glance">
|
|
11
|
+
<dt>Runtime</dt><dd>Node.js 24 or later, ES modules or CommonJS</dd>
|
|
12
|
+
<dt>TypeScript</dt><dd><code>"module": "node20"</code> or <code>"nodenext"</code>, no decorator</dd>
|
|
13
|
+
<dt>Packages</dt><dd><code>@alveolus/core</code> as a dependency, <code>@alveolus/arch</code> as a development dependency</dd>
|
|
14
|
+
<dt>Config</dt><dd><a href="#configure-the-checks"><code>alveolus.config.ts</code></a>, at the root of the project</dd>
|
|
15
|
+
<dt>Command</dt><dd><a href="#run-the-checks"><code>npx alveolus arch check</code></a></dd>
|
|
16
|
+
</dl>
|
|
17
|
+
|
|
18
|
+
::: warning
|
|
19
|
+
Alveolus is at `0.x`: a minor version may still rename a rule or a configuration key, and the
|
|
20
|
+
changelog says what to do. See [Versioning](./versioning.md).
|
|
21
|
+
:::
|
|
22
|
+
|
|
23
|
+
::: tip New to DDD?
|
|
24
|
+
The [learning path](./learning-path.md) gives the order in which to read the docs.
|
|
25
|
+
:::
|
|
26
|
+
|
|
27
|
+
## In four steps
|
|
28
|
+
|
|
29
|
+
<div class="al-cards al-cards-2">
|
|
30
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">1</span><a href="#install">Install</a></span>Add the building blocks to your code and the checks to your development tools.</div>
|
|
31
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">2</span><a href="#use-the-building-blocks">Extend a building block</a></span>Each class of your domain and application says what it is by extending one.</div>
|
|
32
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">3</span><a href="#configure-the-checks">Describe your contexts</a></span>Tell the checks where the code is and which folders are bounded contexts.</div>
|
|
33
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">4</span><a href="#run-the-checks">Run the checks</a></span>In your terminal while you code, and in continuous integration on every change.</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
## Prerequisites
|
|
37
|
+
|
|
38
|
+
The packages are ES modules described by an `exports` map, so TypeScript must resolve packages the
|
|
39
|
+
way Node.js does. Node.js 24 also loads them from a CommonJS application, such as a default NestJS
|
|
40
|
+
one.
|
|
41
|
+
|
|
42
|
+
```json [tsconfig.json]
|
|
43
|
+
{
|
|
44
|
+
"compilerOptions": {
|
|
45
|
+
"module": "node20"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`"nodenext"` works as well. No decorator and no `emitDecoratorMetadata` are needed: the building
|
|
51
|
+
blocks are plain classes, wired by hand or by your framework. See
|
|
52
|
+
[Integrations](../integrations/index.md).
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
`@alveolus/core` ends up in your domain: it is a dependency. `@alveolus/arch` only checks the
|
|
57
|
+
code: it is a development dependency.
|
|
58
|
+
|
|
59
|
+
::: code-group
|
|
60
|
+
|
|
61
|
+
```sh [pnpm]
|
|
62
|
+
pnpm add @alveolus/core
|
|
63
|
+
pnpm add -D @alveolus/arch
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```sh [npm]
|
|
67
|
+
npm install @alveolus/core
|
|
68
|
+
npm install -D @alveolus/arch
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```sh [yarn]
|
|
72
|
+
yarn add @alveolus/core
|
|
73
|
+
yarn add -D @alveolus/arch
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```sh [bun]
|
|
77
|
+
bun add @alveolus/core
|
|
78
|
+
bun add -d @alveolus/arch
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
:::
|
|
82
|
+
|
|
83
|
+
`@alveolus/core` has no runtime dependency.
|
|
84
|
+
|
|
85
|
+
## Use the building blocks
|
|
86
|
+
|
|
87
|
+
Every building block is an abstract class. Import the one you need from `@alveolus/core` and
|
|
88
|
+
extend it: the class then says what it is, to the reader and to the checks.
|
|
89
|
+
|
|
90
|
+
```ts [src/ordering/domain/value-objects/order-id.identifier.ts]
|
|
91
|
+
// [!code word:Identifier]
|
|
92
|
+
import { Identifier } from "@alveolus/core";
|
|
93
|
+
|
|
94
|
+
export class OrderId extends Identifier<string, "OrderId"> {}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Each building block also has its own entry point, such as `@alveolus/core/aggregates` or
|
|
98
|
+
`@alveolus/core/result`. Both forms give the same classes.
|
|
99
|
+
|
|
100
|
+
::: tip Where to start
|
|
101
|
+
Start from a use case: the [aggregate](../core/domain/aggregates.md) that keeps its rules, then the
|
|
102
|
+
[command handler](../core/application/command-handlers.md) that calls it. The
|
|
103
|
+
[building blocks](../core/index.md) overview shows how they fit together.
|
|
104
|
+
:::
|
|
105
|
+
|
|
106
|
+
## Configure the checks
|
|
107
|
+
|
|
108
|
+
Create `alveolus.config.ts` at the root of the project. It says where the source code is, which
|
|
109
|
+
folders are bounded contexts and which subdomain each one implements; everything else has a
|
|
110
|
+
default.
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
npx alveolus init
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
writes a starting one, with the [instructions for a coding agent](./agents.md); it never
|
|
117
|
+
overwrites a file. Or write it yourself:
|
|
118
|
+
|
|
119
|
+
```ts [alveolus.config.ts]
|
|
120
|
+
import { defineConfig } from "@alveolus/arch";
|
|
121
|
+
|
|
122
|
+
export default defineConfig({
|
|
123
|
+
boundedContexts: { catalog: "catalog", notifications: "notifications", ordering: "ordering" },
|
|
124
|
+
root: "src",
|
|
125
|
+
subdomains: { core: ["catalog", "ordering"], generic: ["notifications"] },
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
A [core](./project-layout.md#core-supporting-generic) context is checked by every rule. A
|
|
130
|
+
supporting or generic one is checked only at its boundary: it may be written any way you like, as
|
|
131
|
+
long as it reaches the other contexts through their open host services.
|
|
132
|
+
|
|
133
|
+
### Options
|
|
134
|
+
|
|
135
|
+
| Option | Default | What it does |
|
|
136
|
+
| --- | --- | --- |
|
|
137
|
+
| `root` | required | The source folder. |
|
|
138
|
+
| `tsconfig` | `"tsconfig.json"` | The TypeScript configuration the sources are read with, relative to the project folder. |
|
|
139
|
+
| `boundedContexts` | required | Each bounded context and its folder, relative to `root`. `"modules/ordering"` works. |
|
|
140
|
+
| `sharedKernel` | `"shared-kernel"` | The folder shared by every bounded context, relative to `root`. |
|
|
141
|
+
| `subdomains` | required | The [subdomain](./project-layout.md#core-supporting-generic) each bounded context implements: `{ core: ["ordering"], supporting: ["billing"], generic: ["notifications"] }`. Every context is listed once. |
|
|
142
|
+
| `contextMap` | none | For each bounded context, the ones it consumes: `{ payments: ["ledger"] }`. Checked for cycles; without it, only cycles are reported. |
|
|
143
|
+
| `compositionRoot` | `"*.module.ts"` | The file, at the root of a bounded context, that wires it. |
|
|
144
|
+
| `domainDependencies` | `{}` | npm packages the domain may import, besides `@alveolus/core`. |
|
|
145
|
+
| `applicationDependencies` | `{}` | npm packages the application may import, besides `@alveolus/core` and `domainDependencies`. |
|
|
146
|
+
| `ignore` | test files | More files to leave out, as globs from the project folder. |
|
|
147
|
+
| `layout.extraFolders` | `{}` | Folders of your own under `domain/` or `application/`, besides those of the building blocks: `{ domain: ["specifications"] }`. |
|
|
148
|
+
| `rules` | every rule `"error"` | The level of a rule: `"error"` fails the check, `"warn"` and `"info"` only report, `"off"` silences it: `{ "tactical/no-public-field": "warn" }`. |
|
|
149
|
+
|
|
150
|
+
`domainDependencies` and `applicationDependencies` take `true` for every name of a package, or the
|
|
151
|
+
list of names allowed:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
domainDependencies: { "decimal.js": true, "date-fns": ["addDays"] },
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Tests and their companions are always left out, whatever `ignore` says: `*.spec.ts`, `*.test.ts`,
|
|
158
|
+
`*.e2e-spec.ts`, `*.fixture.ts`, `*.stories.ts`, `__tests__/` and `__mocks__/`.
|
|
159
|
+
|
|
160
|
+
## Run the checks
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
npx alveolus arch check
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
On a project that keeps the rules, the command prints `No violation` and exits with code 0.
|
|
167
|
+
Otherwise it lists each violation, with what is allowed instead, and exits with code 1:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
src/ordering/domain/aggregates/order.aggregate.ts
|
|
171
|
+
1 error layers/no-impure-domain: The domain imports @nestjs/common: add it
|
|
172
|
+
to domainDependencies if the domain really needs it.
|
|
173
|
+
|
|
174
|
+
1 violation
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Each violation names its rule: the [rules](../rules/index.md) explain what each one checks and why.
|
|
178
|
+
The same pages are installed with the package, for the terminal and for a
|
|
179
|
+
[coding agent](./agents.md):
|
|
180
|
+
|
|
181
|
+
```sh
|
|
182
|
+
npx alveolus explain layers/no-impure-domain
|
|
183
|
+
npx alveolus explain aggregates
|
|
184
|
+
npx alveolus explain
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
A rule by its id, a building block or a guide by its name, and without argument the list of
|
|
188
|
+
topics.
|
|
189
|
+
|
|
190
|
+
### Options
|
|
191
|
+
|
|
192
|
+
| Option | What it does |
|
|
193
|
+
| --- | --- |
|
|
194
|
+
| `--project <dir>` | The project folder. Defaults to the current folder. |
|
|
195
|
+
| `--config <file>` | The configuration file. Defaults to `alveolus.config.ts`. |
|
|
196
|
+
| `--tsconfig <file>` | The TypeScript configuration the sources are read with. Defaults to `tsconfig.json`, or to the `tsconfig` of the configuration file. |
|
|
197
|
+
| `--format json` | Prints the violations as JSON, for tools and agents. |
|
|
198
|
+
| `--format sarif` | Prints SARIF 2.1.0, for GitHub code scanning and the other analysers: upload it with `github/codeql-action/upload-sarif`. |
|
|
199
|
+
|
|
200
|
+
The exit code says what happened: `0` when no error is reported (warnings and infos never fail
|
|
201
|
+
the check), `1` when at least one error is, `2` when the configuration or the analysis itself
|
|
202
|
+
failed, with the reason on stderr. The summary counts the files analysed: `No violation in 142
|
|
203
|
+
files` on `0 files` would hide a wrong `root`.
|
|
204
|
+
|
|
205
|
+
### Run it in continuous integration
|
|
206
|
+
|
|
207
|
+
So that no change lands without the check, add it to your scripts and run it with your other
|
|
208
|
+
checks:
|
|
209
|
+
|
|
210
|
+
```json [package.json]
|
|
211
|
+
{
|
|
212
|
+
"scripts": {
|
|
213
|
+
"lint:arch": "alveolus arch check"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
::: tip For coding agents
|
|
219
|
+
Give your agent the command and `--format json`: each violation says what is allowed instead, so
|
|
220
|
+
the agent can fix its own code before you review it.
|
|
221
|
+
:::
|
|
222
|
+
|
|
223
|
+
## Adopt it on an existing project
|
|
224
|
+
|
|
225
|
+
The short version; the [guide for an existing project](./existing-project.md) has the whole path.
|
|
226
|
+
|
|
227
|
+
An existing project rarely keeps every rule from the start. A baseline lets you turn the checks on
|
|
228
|
+
today, and fix the past over time:
|
|
229
|
+
|
|
230
|
+
<div class="al-cards al-cards-2">
|
|
231
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">1</span>Record the current violations</span>Run <code>npx alveolus arch baseline</code>: it writes them to <code>alveolus.baseline.json</code>.</div>
|
|
232
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">2</span>Commit the file</span>From then on, <code>check</code> fails only on violations that are not in the baseline, and says how many it ignored.</div>
|
|
233
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">3</span>Fix them over time</span>Each fix removes a violation from what the baseline covers. New code keeps every rule.</div>
|
|
234
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">4</span>Record it again</span>Run <code>baseline</code> after a round of fixes: the file only shrinks. It refuses to grow, unless you pass <code>--allow-growth</code>.</div>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
### How a violation is recognised
|
|
238
|
+
|
|
239
|
+
Each entry of the baseline keeps the rule, the file, the symbol and a fingerprint of the reported
|
|
240
|
+
line: a short hash of its text, blind to indentation and spacing.
|
|
241
|
+
|
|
242
|
+
<div class="al-cards al-cards-2">
|
|
243
|
+
<div class="al-card"><span class="al-card-title">Still baselined</span>The code around it moves, the file is reformatted: the line keeps its text, so its fingerprint.</div>
|
|
244
|
+
<div class="al-card"><span class="al-card-title">Reported again</span>The line itself changes. Touching a baselined line is the moment to fix it.</div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
When a baselined violation is fixed, `check` says so (`4 fixed` in the summary, and a note on
|
|
248
|
+
stderr) without failing: run `baseline` again to drop the entries.
|
|
249
|
+
|
|
250
|
+
A new violation never hides behind a fixed one: a second `throw` in the same file has another
|
|
251
|
+
line, so another fingerprint. A baseline written before fingerprints existed matches nothing:
|
|
252
|
+
`check` says so, and `baseline` writes it again.
|
|
253
|
+
|
|
254
|
+
## Turn a violation off
|
|
255
|
+
|
|
256
|
+
A violation can be right to keep for a while. Turn it off where it stands, with the rule and a
|
|
257
|
+
reason, and the reviewer sees both:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
// alveolus-disable-next-line layers/no-impure-domain: legacy pool, removed with ORD-412
|
|
261
|
+
import { Pool } from "pg";
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
The summary counts the disabled violations, `--format json` lists them with their reason, and a
|
|
265
|
+
comment that names no rule, gives no reason or disables nothing is reported by
|
|
266
|
+
[`tooling/no-loose-disable`](../rules/tooling/no-loose-disable.md). For a whole file, use
|
|
267
|
+
`ignore`; for a whole rule, `rules`; for the past, the baseline.
|
|
268
|
+
|
|
269
|
+
## See also
|
|
270
|
+
|
|
271
|
+
- [Learning path](./learning-path.md), the order in which to read the docs when you are new to DDD
|
|
272
|
+
- [Project layout](./project-layout.md), the folders and layers the checks expect
|
|
273
|
+
- [Building blocks](../core/index.md), the classes your code extends
|
|
274
|
+
- [Rules](../rules/index.md), what `alveolus arch check` verifies
|
|
275
|
+
- [Integrations](../integrations/index.md), to wire Alveolus into NestJS or another framework
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "New to Domain-Driven Design? A reading order through the Alveolus docs, from bounded contexts to aggregates, handlers and architecture checks, in TypeScript."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Learning path
|
|
6
|
+
|
|
7
|
+
New to Domain-Driven Design? Read the pages in this order: each step builds on the one before, and
|
|
8
|
+
each page explains one idea with the same `Order` example.
|
|
9
|
+
|
|
10
|
+
::: info An introduction, not a course
|
|
11
|
+
These pages explain each idea as far as you need it to use Alveolus. They are not a complete course
|
|
12
|
+
on Domain-Driven Design. For the whole picture, read the books:
|
|
13
|
+
|
|
14
|
+
- *Domain-Driven Design: Tackling Complexity in the Heart of Software*, Eric Evans, 2003
|
|
15
|
+
- *Implementing Domain-Driven Design*, Vaughn Vernon, 2013
|
|
16
|
+
- *Domain-Driven Design Distilled*, Vaughn Vernon, 2016
|
|
17
|
+
- *Learning Domain-Driven Design*, Vlad Khononov, 2021
|
|
18
|
+
:::
|
|
19
|
+
|
|
20
|
+
<dl class="al-glance">
|
|
21
|
+
<dt>For</dt><dd>Developers who know TypeScript and have never used Domain-Driven Design</dd>
|
|
22
|
+
<dt>You need</dt><dd>TypeScript and classes, nothing about DDD</dd>
|
|
23
|
+
<dt>Order</dt><dd>The one of <em>Domain-Driven Design Distilled</em> (Vaughn Vernon, 2016): split the system first, then model each part</dd>
|
|
24
|
+
</dl>
|
|
25
|
+
|
|
26
|
+
::: tip Already know DDD?
|
|
27
|
+
Go straight to [Getting started](./getting-started.md) and come back to a page when you need it.
|
|
28
|
+
:::
|
|
29
|
+
|
|
30
|
+
## Why DDD
|
|
31
|
+
|
|
32
|
+
Software gets hard to change when its code no longer says what the business means. DDD puts the
|
|
33
|
+
business at the center of the code, in its own words.
|
|
34
|
+
|
|
35
|
+
<div class="al-cards al-cards-2">
|
|
36
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">1</span><a href="../core/">Building blocks</a></span>What Alveolus gives you: the patterns of DDD as classes your code extends.</div>
|
|
37
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">2</span><a href="../core/domain/">The domain</a></span>Why the model of the business lives apart from frameworks and databases.</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
## Split the system
|
|
41
|
+
|
|
42
|
+
Before writing a class, decide where its words apply. A "product" in the catalog and a "product" in
|
|
43
|
+
ordering are not the same thing: each part of the system gets its own model.
|
|
44
|
+
|
|
45
|
+
<div class="al-cards al-cards-2">
|
|
46
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">3</span><a href="../core/strategic/">Strategic design</a></span>What a bounded context is, and why contexts never share their models.</div>
|
|
47
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">4</span><a href="./project-layout">Project layout</a></span>Where every class goes: one folder per context, with its domain, application and adapters inside, and who may import what.</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
## Model the rules
|
|
51
|
+
|
|
52
|
+
Inside a context, the domain holds the business rules. Start from the smallest pieces and build up
|
|
53
|
+
to the aggregate, the object that keeps the rules of an order.
|
|
54
|
+
|
|
55
|
+
<div class="al-cards al-cards-2">
|
|
56
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">5</span><a href="../core/domain/value-objects">Value objects</a></span>Values such as an amount or an <code>OrderId</code>, checked once and never changed.</div>
|
|
57
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">6</span><a href="../core/domain/entities">Entities</a></span>Objects that keep their identity while they change, such as an order line.</div>
|
|
58
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">7</span><a href="../core/domain/aggregates">Aggregates</a></span>The <code>Order</code> and its lines, changed as one unit through the root that keeps the rules.</div>
|
|
59
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">8</span><a href="../core/domain/domain-errors">Domain errors</a></span>Expected failures, such as an empty order, named in the business's words.</div>
|
|
60
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">9</span><a href="../core/utilities/result">Result</a></span>How a method returns a domain error instead of throwing it.</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
## Record what happened
|
|
64
|
+
|
|
65
|
+
When the rules accept a change, the aggregate records it as a fact other code can react to.
|
|
66
|
+
|
|
67
|
+
<div class="al-cards al-cards-2">
|
|
68
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">10</span><a href="../core/domain/domain-events">Domain events</a></span>Facts named in the past tense, such as <code>OrderPlaced</code>.</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
## Reach the outside world
|
|
72
|
+
|
|
73
|
+
The domain still needs things it does not own: a rule spread over several objects, a price from
|
|
74
|
+
elsewhere, a place to store orders. It asks for them in its own words.
|
|
75
|
+
|
|
76
|
+
<div class="al-cards al-cards-2">
|
|
77
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">11</span><a href="../core/domain/domain-services">Domain services</a></span>Rules that no single object owns.</div>
|
|
78
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">12</span><a href="../core/domain/ports">Ports</a></span>What the domain needs from outside, as abstract classes.</div>
|
|
79
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">13</span><a href="../core/domain/repositories">Repositories</a></span>The ports that load and save aggregates and views.</div>
|
|
80
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">14</span><a href="../core/domain/views">Views</a></span>The read-only shape a query returns.</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
## Run a use case
|
|
84
|
+
|
|
85
|
+
The application layer turns a request, such as "place this order", into a call to the domain, and
|
|
86
|
+
saves the result.
|
|
87
|
+
|
|
88
|
+
<div class="al-cards al-cards-2">
|
|
89
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">15</span><a href="../core/application/">The application</a></span>How a request flows from the outside to the domain and back.</div>
|
|
90
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">16</span><a href="../core/application/command-handlers">Command handlers</a></span>Load an aggregate, call one method, save it.</div>
|
|
91
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">17</span><a href="../core/application/query-handlers">Query handlers</a></span>Read a view, without loading an aggregate.</div>
|
|
92
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">18</span><a href="../core/application/unit-of-work">Unit of Work</a></span>All the writes of a use case kept together, or none.</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
## Talk to other contexts
|
|
96
|
+
|
|
97
|
+
Contexts never import each other. They tell each other what happened in plain JSON, and each one
|
|
98
|
+
translates what it reads into its own model.
|
|
99
|
+
|
|
100
|
+
<div class="al-cards al-cards-2">
|
|
101
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">19</span><a href="../core/application/event-translators">Event translators</a></span>Turn domain events into messages for other contexts.</div>
|
|
102
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">20</span><a href="../core/application/integration-events">Integration events</a></span>Versioned JSON messages other contexts receive.</div>
|
|
103
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">21</span><a href="../core/application/event-publishers">Event publishers</a></span>The port that sends them to a broker.</div>
|
|
104
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">22</span><a href="../core/application/outbox">Outbox</a></span>How no message is lost when the change is saved.</div>
|
|
105
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">23</span><a href="../core/strategic/published-language">Published Language</a></span>The JSON contract between contexts.</div>
|
|
106
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">24</span><a href="../core/strategic/open-host-services">Open host services</a></span>The one entry point other contexts may call.</div>
|
|
107
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">25</span><a href="../core/strategic/anti-corruption-layers">Anti-corruption layers</a></span>Translate another context into your own words.</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
## Keep it on track
|
|
111
|
+
|
|
112
|
+
Each idea above becomes a rule the checks verify, so the code keeps it whoever writes it.
|
|
113
|
+
|
|
114
|
+
<div class="al-cards al-cards-2">
|
|
115
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">26</span><a href="../rules/">Rules</a></span>What <code>alveolus arch check</code> reports, and why.</div>
|
|
116
|
+
<div class="al-card"><span class="al-card-title"><span class="al-card-step">27</span><a href="./getting-started">Getting started</a></span>Install Alveolus and run the checks on your project.</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
## See also
|
|
120
|
+
|
|
121
|
+
- [Getting started](./getting-started.md), to install Alveolus and run the checks
|
|
122
|
+
- [Project layout](./project-layout.md), the folders and layers of a project
|
|
123
|
+
- [Building blocks](../core/index.md), every class `@alveolus/core` gives
|