@alveolus/arch 0.2.0 → 0.4.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.
Files changed (60) hide show
  1. package/README.md +11 -0
  2. package/dist/bin.mjs +4 -2
  3. package/dist/bin.mjs.map +1 -1
  4. package/dist/{cli-P5PwH9OE.mjs → docs-DcFgskuN.mjs} +214 -43
  5. package/dist/docs-DcFgskuN.mjs.map +1 -0
  6. package/dist/index.d.mts +53 -12
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +2 -2
  9. package/docs/core/application/command-handlers.md +617 -0
  10. package/docs/core/application/event-publishers.md +234 -0
  11. package/docs/core/application/event-translators.md +329 -0
  12. package/docs/core/application/index.md +99 -0
  13. package/docs/core/application/integration-events.md +277 -0
  14. package/docs/core/application/outbox.md +416 -0
  15. package/docs/core/application/query-handlers.md +292 -0
  16. package/docs/core/application/unit-of-work.md +352 -0
  17. package/docs/core/domain/aggregates.md +822 -0
  18. package/docs/core/domain/domain-errors.md +251 -0
  19. package/docs/core/domain/domain-events.md +292 -0
  20. package/docs/core/domain/domain-services.md +249 -0
  21. package/docs/core/domain/entities.md +431 -0
  22. package/docs/core/domain/index.md +93 -0
  23. package/docs/core/domain/ports.md +284 -0
  24. package/docs/core/domain/repositories.md +335 -0
  25. package/docs/core/domain/value-objects.md +425 -0
  26. package/docs/core/domain/views.md +265 -0
  27. package/docs/core/index.md +108 -0
  28. package/docs/core/strategic/anti-corruption-layers.md +349 -0
  29. package/docs/core/strategic/index.md +83 -0
  30. package/docs/core/strategic/open-host-services.md +287 -0
  31. package/docs/core/strategic/published-language.md +265 -0
  32. package/docs/core/utilities/result.md +413 -0
  33. package/docs/guide/agents.md +68 -0
  34. package/docs/guide/existing-project.md +108 -0
  35. package/docs/guide/getting-started.md +286 -0
  36. package/docs/guide/learning-path.md +123 -0
  37. package/docs/guide/project-layout.md +324 -0
  38. package/docs/guide/versioning.md +42 -0
  39. package/docs/integrations/index.md +112 -0
  40. package/docs/integrations/nestjs.md +169 -0
  41. package/docs/rules/index.md +185 -0
  42. package/docs/rules/layers/no-driving-shortcut.md +119 -0
  43. package/docs/rules/layers/no-impure-domain.md +191 -0
  44. package/docs/rules/layers/no-outward-import.md +186 -0
  45. package/docs/rules/layers/no-portless-adapter.md +123 -0
  46. package/docs/rules/strategic/no-cross-context-import.md +140 -0
  47. package/docs/rules/strategic/no-fat-shared-kernel.md +81 -0
  48. package/docs/rules/strategic/no-leaky-host-service.md +107 -0
  49. package/docs/rules/strategic/no-unmapped-context.md +114 -0
  50. package/docs/rules/tactical/no-aggregate-reference.md +139 -0
  51. package/docs/rules/tactical/no-foreign-command-dependency.md +119 -0
  52. package/docs/rules/tactical/no-foreign-query-dependency.md +201 -0
  53. package/docs/rules/tactical/no-loose-code.md +171 -0
  54. package/docs/rules/tactical/no-misplaced-class.md +146 -0
  55. package/docs/rules/tactical/no-public-field.md +113 -0
  56. package/docs/rules/tactical/no-stateful-service.md +102 -0
  57. package/docs/rules/tactical/no-thrown-failure.md +162 -0
  58. package/docs/rules/tooling/no-loose-disable.md +98 -0
  59. package/package.json +4 -3
  60. package/dist/cli-P5PwH9OE.mjs.map +0 -1
@@ -0,0 +1,349 @@
1
+ ---
2
+ description: "Anti-corruption layers in Domain-Driven Design with TypeScript: translate another bounded context into your own language so its model never leaks in."
3
+ ---
4
+
5
+ # Anti-corruption layers
6
+
7
+ An anti-corruption layer is the adapter that reads another bounded context and translates it into
8
+ the language of yours, so that the foreign model never enters your domain or your application.
9
+
10
+ <dl class="al-glance">
11
+ <dt>Layer</dt><dd>Driven, or driving for a consumer</dd>
12
+ <dt>File</dt><dd><code>src/ordering/driven/catalog/adapters/catalog-price-list.adapter.ts</code></dd>
13
+ <dt>Implements</dt><dd><a href="#api"><code>AntiCorruptionLayer</code></a>, and extends your <a href="/core/domain/ports">port</a></dd>
14
+ <dt>Called by</dt><dd><a href="/core/application/command-handlers">Command handlers</a>, through the port</dd>
15
+ <dt>Checked by</dt><dd><a href="/rules/strategic/no-cross-context-import"><code>strategic/no-cross-context-import</code></a>, <a href="/rules/layers/no-portless-adapter"><code>layers/no-portless-adapter</code></a>, <a href="/rules/tactical/no-misplaced-class"><code>tactical/no-misplaced-class</code></a></dd>
16
+ </dl>
17
+
18
+ ## Why
19
+
20
+ Before adding a line to an order, ordering checks that the product has a price in the catalog. If
21
+ the handler calls `CatalogApi` directly, the catalog's representations spread into the use case,
22
+ then into the domain. Every change in the catalog becomes a change in ordering, and the order
23
+ starts speaking the catalog's language.
24
+
25
+ ::: tip The fix
26
+ One adapter, and only one, touches the catalog. Ordering asks for what it needs in its own words,
27
+ through a [port](../domain/ports.md); the anti-corruption layer calls the catalog, reads its JSON
28
+ and answers with ordering's objects.
29
+ :::
30
+
31
+ ## How it works
32
+
33
+ <div class="al-cards">
34
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">1</span>The domain states its need</span>The <code>PriceList</code> port asks for the price of a <code>ProductId</code>, as <code>Money</code>. It never mentions the catalog.</div>
35
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">2</span>The adapter calls the other context</span><code>CatalogPriceList</code> calls the <a href="/core/strategic/open-host-services">open host service</a> and reads its <a href="/core/strategic/published-language">published language</a>.</div>
36
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">3</span>It answers with your objects</span>It turns the JSON into <code>Money</code>. Representations come in; they never go out.</div>
37
+ </div>
38
+
39
+ ## Where it fits
40
+
41
+ The anti-corruption layer is the ordering side of the meeting point. The handler only sees the
42
+ port; the adapter is the one place that knows the catalog exists.
43
+
44
+ <div class="al-diagram">
45
+ <svg viewBox="0 0 720 260" role="img" aria-label="In ordering, AddLineHandler asks the PriceList port for a price. CatalogPriceList, the anti-corruption layer, extends the port and reads the JSON answered by CatalogApi, the open host service of catalog, which calls GetProductHandler.">
46
+ <defs>
47
+ <marker id="acl-flow-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
48
+ <path class="arrow" d="M 0 0 L 10 5 L 0 10 z" />
49
+ </marker>
50
+ </defs>
51
+ <rect class="box" x="8" y="8" width="272" height="244" rx="14" />
52
+ <text class="note" x="24" y="30">src/ordering/</text>
53
+ <rect class="box" x="24" y="44" width="240" height="48" rx="8" />
54
+ <text class="label" x="144" y="64" text-anchor="middle">AddLineHandler</text>
55
+ <text class="note" x="144" y="82" text-anchor="middle">asks for a price</text>
56
+ <rect class="box" x="24" y="116" width="240" height="48" rx="8" />
57
+ <text class="label" x="144" y="136" text-anchor="middle">PriceList</text>
58
+ <text class="note" x="144" y="154" text-anchor="middle">port · in your words</text>
59
+ <rect class="boundary" x="24" y="188" width="240" height="48" rx="8" />
60
+ <text class="label" x="144" y="208" text-anchor="middle">CatalogPriceList</text>
61
+ <text class="note" x="144" y="226" text-anchor="middle">anti-corruption layer</text>
62
+ <path class="link" d="M 144 92 L 144 114" marker-end="url(#acl-flow-arrow)" />
63
+ <path class="link" d="M 144 188 L 144 166" marker-end="url(#acl-flow-arrow)" />
64
+ <text class="note" x="154" y="181">extends</text>
65
+ <rect class="box" x="300" y="188" width="156" height="48" rx="8" />
66
+ <text class="label" x="378" y="208" text-anchor="middle">JSON</text>
67
+ <text class="note" x="378" y="226" text-anchor="middle">the contract</text>
68
+ <path class="link" d="M 300 212 L 266 212" marker-end="url(#acl-flow-arrow)" />
69
+ <path class="link" d="M 492 212 L 458 212" marker-end="url(#acl-flow-arrow)" />
70
+ <rect class="box" x="476" y="8" width="236" height="244" rx="14" />
71
+ <text class="note" x="492" y="30">src/catalog/</text>
72
+ <rect class="box" x="492" y="44" width="204" height="48" rx="8" />
73
+ <text class="label" x="594" y="64" text-anchor="middle">GetProductHandler</text>
74
+ <text class="note" x="594" y="82" text-anchor="middle">reads the catalog</text>
75
+ <rect class="box" x="492" y="188" width="204" height="48" rx="8" />
76
+ <text class="label" x="594" y="208" text-anchor="middle">CatalogApi</text>
77
+ <text class="note" x="594" y="226" text-anchor="middle">open host service</text>
78
+ <path class="link" d="M 594 188 L 594 94" marker-end="url(#acl-flow-arrow)" />
79
+ <text class="note" x="604" y="145">calls</text>
80
+ </svg>
81
+ </div>
82
+
83
+ ::: tip
84
+ If the catalog moves behind HTTP, only the anti-corruption layer changes: it fetches and validates
85
+ the JSON instead of calling `CatalogApi`. The port, the handler and the domain stay the same.
86
+ :::
87
+
88
+ ## API
89
+
90
+ ```ts
91
+ import type { AntiCorruptionLayer } from "@alveolus/core";
92
+ // or: import type { AntiCorruptionLayer } from
93
+ // "@alveolus/core/anti-corruption-layers";
94
+ ```
95
+
96
+ ### `AntiCorruptionLayer` <Badge type="info" text="abstract · no members" /> <Badge type="tip" text="you implement it" />
97
+
98
+ ```ts
99
+ abstract class AntiCorruptionLayer {}
100
+ ```
101
+
102
+ Marks the class as the translator of another context. It has no members: the class declares its
103
+ role with `implements`, and the rules recognise it.
104
+
105
+ ```ts
106
+ export class CatalogPriceList
107
+ extends PriceList
108
+ implements AntiCorruptionLayer { … }
109
+ ```
110
+
111
+ ### The methods of the port <Badge type="tip" text="you implement them" />
112
+
113
+ ```ts
114
+ async priceOf(productId: ProductId): Promise<Money | undefined>
115
+ ```
116
+
117
+ For a driven anti-corruption layer: the methods of the [port](../domain/ports.md) it extends,
118
+ such as `priceOf` of `PriceList`. Each one calls the other context, reads its representation and
119
+ returns your objects. Your handlers call them through the port, in your language.
120
+
121
+ ### `consume(event)` <Badge type="info" text="any name" /> <Badge type="tip" text="called by your message broker" />
122
+
123
+ ```ts
124
+ async consume(event: OrderPlacedRepresentation): Promise<void>
125
+ ```
126
+
127
+ For a driving anti-corruption layer: receives an event of the other context, in its published
128
+ language, and turns it into one of your commands.
129
+
130
+ ::: warning Caveats
131
+ - Use `implements`, not `extends`: a driven anti-corruption layer already extends its port.
132
+ - It is the only place, with the composition root, that may import another bounded context: see
133
+ [`strategic/no-cross-context-import`](../../rules/strategic/no-cross-context-import.md).
134
+ - A driven anti-corruption layer is placed like any adapter, in `driven/<context>/adapters/`; a
135
+ consumer goes in `driving/`: see [`tactical/no-misplaced-class`](../../rules/tactical/no-misplaced-class.md).
136
+ :::
137
+
138
+ ## Usage
139
+
140
+ Build the anti-corruption layer through which ordering reads prices from the catalog, one idea at a
141
+ time. Each step shows the whole file: added lines are highlighted, replaced lines are struck out.
142
+
143
+ <div class="al-cards">
144
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">1</span><a href="#_1-declare-what-your-context-needs">Declare what your context needs</a></span>Name the need in your own words.</div>
145
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">2</span><a href="#_2-declare-the-adapter">Declare the adapter</a></span>One class at the boundary.</div>
146
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">3</span><a href="#_3-ask-the-other-context">Ask the other context</a></span>Call its open host service.</div>
147
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">4</span><a href="#_4-type-the-answer-in-your-own-words">Type the answer in your own words</a></span>Redeclare what you read.</div>
148
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">5</span><a href="#_5-translate-into-your-model">Translate into your model</a></span>Answer with your objects.</div>
149
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">6</span><a href="#_6-wire-it">Wire it</a></span>Hand it the open host service.</div>
150
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">7</span><a href="#_7-check-it">Check it</a></span>Let the rules keep it that way.</div>
151
+ </div>
152
+
153
+ ### 1. Declare what your context needs
154
+
155
+ The domain of ordering asks for prices through its own [port](../domain/ports.md), `PriceList`,
156
+ with an abstract `priceOf(productId)` that returns a `Money` or `undefined`.
157
+
158
+ ### 2. Declare the adapter
159
+
160
+ So that the rest of ordering never learns the catalog exists, one driven adapter extends the port
161
+ and implements `AntiCorruptionLayer`. It receives `CatalogApi`, the open host service of the
162
+ catalog, in its constructor.
163
+
164
+ ```ts [src/ordering/driven/catalog/adapters/catalog-price-list.adapter.ts]
165
+ import type { AntiCorruptionLayer } from "@alveolus/core";
166
+
167
+ import { CatalogApi } from
168
+ "../../../../catalog/driving/in-process/catalog-api";
169
+ import { PriceList } from "../../../domain/ports/price-list.port";
170
+
171
+ export class CatalogPriceList
172
+ extends PriceList
173
+ implements AntiCorruptionLayer
174
+ {
175
+ constructor(private readonly catalog: CatalogApi) {
176
+ super();
177
+ }
178
+ }
179
+ ```
180
+
181
+ TypeScript now asks for `priceOf`: the next step adds it.
182
+
183
+ ### 3. Ask the other context
184
+
185
+ The adapter calls the catalog in the catalog's terms: a raw string id, and `undefined` when the
186
+ product does not exist. Ordering's `ProductId` stays on this side.
187
+
188
+ ```ts [src/ordering/driven/catalog/adapters/catalog-price-list.adapter.ts]
189
+ import type { AntiCorruptionLayer } from "@alveolus/core";
190
+
191
+ import { CatalogApi } from
192
+ "../../../../catalog/driving/in-process/catalog-api";
193
+ import { Money } from // [!code ++]
194
+ "../../../../shared-kernel/domain/value-objects/money.value-object"; // [!code ++]
195
+ import { PriceList } from "../../../domain/ports/price-list.port";
196
+ import type { ProductId } from // [!code ++]
197
+ "../../../domain/value-objects/product-id.identifier"; // [!code ++]
198
+
199
+ export class CatalogPriceList
200
+ extends PriceList
201
+ implements AntiCorruptionLayer
202
+ {
203
+ constructor(private readonly catalog: CatalogApi) {
204
+ super();
205
+ }
206
+
207
+ async priceOf(productId: ProductId): Promise<Money | undefined> { // [!code ++]
208
+ const product = // [!code ++]
209
+ await this.catalog.productById(productId.value); // [!code ++]
210
+ if (product === undefined) { // [!code ++]
211
+ return undefined; // [!code ++]
212
+ } // [!code ++]
213
+ } // [!code ++]
214
+ }
215
+ ```
216
+
217
+ ### 4. Type the answer in your own words
218
+
219
+ So that a change in the catalog breaks the build instead of production, the answer is typed with
220
+ ordering's own [published language](./published-language.md): `CatalogProductRepresentation`
221
+ redeclares the fields ordering reads.
222
+
223
+ ```ts [src/ordering/driven/catalog/adapters/catalog-price-list.adapter.ts]
224
+ import type { AntiCorruptionLayer } from "@alveolus/core";
225
+
226
+ import { CatalogApi } from
227
+ "../../../../catalog/driving/in-process/catalog-api";
228
+ import { Money } from
229
+ "../../../../shared-kernel/domain/value-objects/money.value-object";
230
+ import { PriceList } from "../../../domain/ports/price-list.port";
231
+ import type { ProductId } from
232
+ "../../../domain/value-objects/product-id.identifier";
233
+ import type { CatalogProductRepresentation } from // [!code ++]
234
+ "../../../published-language/catalog-product.representation"; // [!code ++]
235
+
236
+ export class CatalogPriceList
237
+ extends PriceList
238
+ implements AntiCorruptionLayer
239
+ {
240
+ constructor(private readonly catalog: CatalogApi) {
241
+ super();
242
+ }
243
+
244
+ async priceOf(productId: ProductId): Promise<Money | undefined> {
245
+ const product = // [!code --]
246
+ const product: CatalogProductRepresentation | undefined = // [!code ++]
247
+ await this.catalog.productById(productId.value);
248
+ if (product === undefined) {
249
+ return undefined;
250
+ }
251
+ }
252
+ }
253
+ ```
254
+
255
+ ### 5. Translate into your model
256
+
257
+ The domain expects a `Money`, not JSON: the adapter builds the `Currency`, then the `Money`, with
258
+ the factories of the value objects, chained by `andThen`. An invalid price is a broken contract,
259
+ not a business failure, so the adapter throws.
260
+
261
+ ```ts [src/ordering/driven/catalog/adapters/catalog-price-list.adapter.ts]
262
+ import type { AntiCorruptionLayer } from "@alveolus/core"; // [!code --]
263
+ import { type AntiCorruptionLayer, andThen } from "@alveolus/core"; // [!code ++]
264
+
265
+ import { CatalogApi } from
266
+ "../../../../catalog/driving/in-process/catalog-api";
267
+ import { Currency } from // [!code ++]
268
+ "../../../../shared-kernel/domain/value-objects/currency.value-object";// [!code ++]
269
+ import { Money } from
270
+ "../../../../shared-kernel/domain/value-objects/money.value-object";
271
+ import { PriceList } from "../../../domain/ports/price-list.port";
272
+ import type { ProductId } from
273
+ "../../../domain/value-objects/product-id.identifier";
274
+ import type { CatalogProductRepresentation } from
275
+ "../../../published-language/catalog-product.representation";
276
+
277
+ export class CatalogPriceList
278
+ extends PriceList
279
+ implements AntiCorruptionLayer
280
+ {
281
+ constructor(private readonly catalog: CatalogApi) {
282
+ super();
283
+ }
284
+
285
+ async priceOf(productId: ProductId): Promise<Money | undefined> {
286
+ const product: CatalogProductRepresentation | undefined =
287
+ await this.catalog.productById(productId.value);
288
+ if (product === undefined) {
289
+ return undefined;
290
+ }
291
+ const { amount, currency } = product.price; // [!code ++]
292
+ const price = andThen(Currency.of(currency), (code) => // [!code ++]
293
+ Money.of(amount, code), // [!code ++]
294
+ ); // [!code ++]
295
+ if (!price.ok) { // [!code ++]
296
+ const reason = price.error.type; // [!code ++]
297
+ throw new Error( // [!code ++]
298
+ `The catalog sent an invalid price: ${reason}`, // [!code ++]
299
+ ); // [!code ++]
300
+ } // [!code ++]
301
+ return price.value; // [!code ++]
302
+ }
303
+ }
304
+ ```
305
+
306
+ ### 6. Wire it
307
+
308
+ The composition root of ordering receives the catalog's module and builds the adapter with its open
309
+ host service. The handlers only see `PriceList`.
310
+
311
+ ```ts [src/ordering/ordering.module.ts]
312
+ export class OrderingModule {
313
+ constructor(db: Pool, catalog: CatalogModule) {
314
+ const prices = new CatalogPriceList(catalog.api);
315
+ …
316
+ }
317
+ }
318
+ ```
319
+
320
+ ### 7. Check it
321
+
322
+ Run the checks. Three rules keep the boundary where it is now:
323
+
324
+ ```sh
325
+ npx alveolus arch check
326
+ ```
327
+
328
+ <div class="al-cards">
329
+ <div class="al-card"><span class="al-card-title"><a href="../../rules/strategic/no-cross-context-import"><code>strategic/no-cross-context-import</code></a></span>Only the anti-corruption layer may use the open host service of another context.</div>
330
+ <div class="al-card"><span class="al-card-title"><a href="../../rules/layers/no-portless-adapter"><code>layers/no-portless-adapter</code></a></span>The adapter extends a port declared by the domain.</div>
331
+ <div class="al-card"><span class="al-card-title"><a href="../../rules/tactical/no-misplaced-class"><code>tactical/no-misplaced-class</code></a></span>It stays in <code>driven/&lt;technology&gt;/adapters/*.adapter.ts</code>.</div>
332
+ </div>
333
+
334
+ A handler that calls the catalog directly is reported:
335
+
336
+ ```
337
+ src/ordering/application/commands/place-order.command.ts
338
+ 3 error strategic/no-cross-context-import: Uses the open host service of
339
+ catalog outside an AntiCorruptionLayer: translate it in an
340
+ anti-corruption layer.
341
+ ```
342
+
343
+ ## See also
344
+
345
+ - [Open host services](./open-host-services.md), what a driven anti-corruption layer calls
346
+ - [Published Language](./published-language.md), what it reads
347
+ - [Ports](../domain/ports.md), what a driven anti-corruption layer extends
348
+ - [Project layout: bounded contexts](../../guide/project-layout.md#bounded-contexts)
349
+ - Rules: [`strategic/no-cross-context-import`](../../rules/strategic/no-cross-context-import.md), [`layers/no-portless-adapter`](../../rules/layers/no-portless-adapter.md), [`tactical/no-misplaced-class`](../../rules/tactical/no-misplaced-class.md)
@@ -0,0 +1,83 @@
1
+ ---
2
+ description: "Strategic Domain-Driven Design in TypeScript: split a system into bounded contexts and decide how they talk without sharing their models."
3
+ ---
4
+
5
+ # Strategic
6
+
7
+ Strategic design splits a system into bounded contexts, each with its own model, and decides how
8
+ they talk without sharing that model.
9
+
10
+ ## Why
11
+
12
+ The catalog knows a product by its name, photos and stock. Ordering only needs its price. If
13
+ ordering imports the catalog's `Product` class, every change in the catalog breaks ordering, and
14
+ the two teams can no longer move alone.
15
+
16
+ ::: tip The fix
17
+ Each context keeps its model to itself. They exchange plain JSON through a documented entry point,
18
+ and each side translates it into its own words.
19
+ :::
20
+
21
+ ## How the contexts meet
22
+
23
+ <div class="al-diagram">
24
+ <svg viewBox="0 0 720 260" role="img" aria-label="In ordering, AddLineHandler asks the PriceList port for a price. CatalogPriceList, the anti-corruption layer, extends the port and reads the JSON of the published language, answered by CatalogApi, the open host service of catalog, which calls GetProductHandler.">
25
+ <defs>
26
+ <marker id="strategic-overview-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
27
+ <path class="arrow" d="M 0 0 L 10 5 L 0 10 z" />
28
+ </marker>
29
+ </defs>
30
+ <rect class="boundary" x="8" y="8" width="272" height="244" rx="14" />
31
+ <text class="note" x="24" y="30">src/ordering/ · a context</text>
32
+ <rect class="box" x="24" y="44" width="240" height="48" rx="8" />
33
+ <text class="label" x="144" y="64" text-anchor="middle">AddLineHandler</text>
34
+ <text class="note" x="144" y="82" text-anchor="middle">asks for a price</text>
35
+ <rect class="box" x="24" y="116" width="240" height="48" rx="8" />
36
+ <text class="label" x="144" y="136" text-anchor="middle">PriceList</text>
37
+ <text class="note" x="144" y="154" text-anchor="middle">port · in your words</text>
38
+ <rect class="box" x="24" y="188" width="240" height="48" rx="8" />
39
+ <text class="label" x="144" y="208" text-anchor="middle">CatalogPriceList</text>
40
+ <text class="note" x="144" y="226" text-anchor="middle">anti-corruption layer</text>
41
+ <path class="link" d="M 144 92 L 144 114" marker-end="url(#strategic-overview-arrow)" />
42
+ <path class="link" d="M 144 188 L 144 166" marker-end="url(#strategic-overview-arrow)" />
43
+ <text class="note" x="154" y="181">extends</text>
44
+ <rect class="box" x="300" y="188" width="156" height="48" rx="8" />
45
+ <text class="label" x="378" y="208" text-anchor="middle">JSON</text>
46
+ <text class="note" x="378" y="226" text-anchor="middle">published language</text>
47
+ <path class="link" d="M 300 212 L 266 212" marker-end="url(#strategic-overview-arrow)" />
48
+ <path class="link" d="M 492 212 L 458 212" marker-end="url(#strategic-overview-arrow)" />
49
+ <rect class="boundary" x="476" y="8" width="236" height="244" rx="14" />
50
+ <text class="note" x="492" y="30">src/catalog/ · a context</text>
51
+ <rect class="box" x="492" y="44" width="204" height="48" rx="8" />
52
+ <text class="label" x="594" y="64" text-anchor="middle">GetProductHandler</text>
53
+ <text class="note" x="594" y="82" text-anchor="middle">reads the catalog</text>
54
+ <rect class="box" x="492" y="188" width="204" height="48" rx="8" />
55
+ <text class="label" x="594" y="208" text-anchor="middle">CatalogApi</text>
56
+ <text class="note" x="594" y="226" text-anchor="middle">open host service</text>
57
+ <path class="link" d="M 594 188 L 594 94" marker-end="url(#strategic-overview-arrow)" />
58
+ <text class="note" x="604" y="145">calls</text>
59
+ </svg>
60
+ </div>
61
+
62
+ <div class="al-cards">
63
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">1</span>The catalog opens a door</span>Its <a href="/core/strategic/open-host-services">open host service</a> is the only class other contexts may call.</div>
64
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">2</span>It answers in JSON</span>The <a href="/core/strategic/published-language">published language</a> is the contract: plain data, versioned, no class.</div>
65
+ <div class="al-card"><span class="al-card-title"><span class="al-card-step">3</span>Ordering translates</span>Its <a href="/core/strategic/anti-corruption-layers">anti-corruption layer</a> turns that JSON into its own words, behind a port.</div>
66
+ </div>
67
+
68
+ ## The building blocks
69
+
70
+ | Building block | What it is | Use it when |
71
+ | --- | --- | --- |
72
+ | [Published Language](./published-language.md) | The JSON format exchanged between contexts. | Data crosses a context boundary, as a question, an answer or an event. |
73
+ | [Open host services](./open-host-services.md) | The documented entry point of a context, the only class others may import. | Another context needs to ask yours something. |
74
+ | [Anti-corruption layers](./anti-corruption-layers.md) | The adapter that reads another context and translates it into yours. | Your context needs something from another one. |
75
+
76
+ The events a context sends to the others are [integration events](../application/integration-events.md),
77
+ written in the published language.
78
+
79
+ ## See also
80
+
81
+ - [Bounded contexts](../../guide/project-layout.md#bounded-contexts) in the project layout
82
+ - Rule: [`strategic/no-cross-context-import`](../../rules/strategic/no-cross-context-import.md)
83
+ - [Domain](../domain/index.md), the model inside each context