@alveolus/arch 0.4.0 → 0.5.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/dist/bin.mjs +1 -1
- package/dist/{docs-DcFgskuN.mjs → docs-B1Tzkjrn.mjs} +338 -14
- package/dist/docs-B1Tzkjrn.mjs.map +1 -0
- package/dist/index.d.mts +39 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/docs/guide/project-layout.md +3 -1
- package/docs/rules/index.md +12 -3
- package/docs/rules/layers/no-outward-import.md +7 -3
- package/docs/rules/strategic/no-cross-context-import.md +35 -2
- package/docs/rules/strategic/no-fat-shared-kernel.md +1 -0
- package/docs/rules/strategic/no-shared-state.md +100 -0
- package/docs/rules/strategic/no-unmapped-context.md +37 -4
- package/package.json +1 -1
- package/dist/docs-DcFgskuN.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -69,11 +69,13 @@ interface MemberProps {
|
|
|
69
69
|
readonly kind: MemberKind;
|
|
70
70
|
readonly visibility: Visibility;
|
|
71
71
|
readonly isStatic: boolean;
|
|
72
|
+
readonly isReadonly: boolean;
|
|
72
73
|
readonly isParameterProperty: boolean;
|
|
73
74
|
readonly isCallable: boolean;
|
|
74
75
|
readonly valueTypes: readonly ClassType[];
|
|
75
76
|
readonly parameterTypes: readonly ClassType[];
|
|
76
77
|
readonly returns: ReturnShape | undefined;
|
|
78
|
+
readonly holdsCollection: boolean;
|
|
77
79
|
}
|
|
78
80
|
declare class Member {
|
|
79
81
|
readonly name: string;
|
|
@@ -81,11 +83,13 @@ declare class Member {
|
|
|
81
83
|
readonly kind: MemberKind;
|
|
82
84
|
readonly visibility: Visibility;
|
|
83
85
|
readonly isStatic: boolean;
|
|
86
|
+
readonly isReadonly: boolean;
|
|
84
87
|
readonly isParameterProperty: boolean;
|
|
85
88
|
readonly isCallable: boolean;
|
|
86
89
|
readonly valueTypes: readonly ClassType[];
|
|
87
90
|
readonly parameterTypes: readonly ClassType[];
|
|
88
91
|
readonly returns: ReturnShape | undefined;
|
|
92
|
+
readonly holdsCollection: boolean;
|
|
89
93
|
constructor(props: MemberProps);
|
|
90
94
|
get isPublicInstance(): boolean;
|
|
91
95
|
get isField(): boolean;
|
|
@@ -119,8 +123,8 @@ declare class ClassDeclaration {
|
|
|
119
123
|
}
|
|
120
124
|
//#endregion
|
|
121
125
|
//#region src/model/dependencies/dependency.d.ts
|
|
122
|
-
type DependencyForm = "import" | "re-export" | "inline type" | "dynamic import" | "require" | "global";
|
|
123
|
-
type TargetVisibility = "analysed" | "ignored" | "unresolved";
|
|
126
|
+
type DependencyForm = "import" | "re-export" | "inline type" | "dynamic import" | "require" | "reference" | "augmentation" | "dynamic load" | "global";
|
|
127
|
+
type TargetVisibility = "analysed" | "ignored" | "unresolved" | "dynamic";
|
|
124
128
|
type DependencyTarget = {
|
|
125
129
|
readonly kind: "file";
|
|
126
130
|
readonly path: string;
|
|
@@ -151,6 +155,19 @@ declare class GlobalUse {
|
|
|
151
155
|
constructor(line: number, name: string, origin: GlobalOrigin, effect: GlobalEffect | undefined);
|
|
152
156
|
}
|
|
153
157
|
//#endregion
|
|
158
|
+
//#region src/model/dependencies/wiring.d.ts
|
|
159
|
+
interface WiringLink {
|
|
160
|
+
readonly text: string;
|
|
161
|
+
readonly declaredIn: string | undefined;
|
|
162
|
+
readonly types: readonly ClassType[];
|
|
163
|
+
}
|
|
164
|
+
declare class Wiring {
|
|
165
|
+
readonly line: number;
|
|
166
|
+
readonly receiver: string;
|
|
167
|
+
readonly links: readonly WiringLink[];
|
|
168
|
+
constructor(line: number, receiver: string, links: readonly WiringLink[]);
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
154
171
|
//#region src/model/statements/disable-comment.d.ts
|
|
155
172
|
declare class DisableComment {
|
|
156
173
|
readonly line: number;
|
|
@@ -185,6 +202,7 @@ interface SourceFileProps {
|
|
|
185
202
|
readonly statements: readonly TopLevelStatement[];
|
|
186
203
|
readonly throws: readonly Throw[];
|
|
187
204
|
readonly globals: readonly GlobalUse[];
|
|
205
|
+
readonly wirings: readonly Wiring[];
|
|
188
206
|
readonly disables: readonly DisableComment[];
|
|
189
207
|
}
|
|
190
208
|
declare class SourceFile {
|
|
@@ -194,6 +212,7 @@ declare class SourceFile {
|
|
|
194
212
|
readonly statements: readonly TopLevelStatement[];
|
|
195
213
|
readonly throws: readonly Throw[];
|
|
196
214
|
readonly globals: readonly GlobalUse[];
|
|
215
|
+
readonly wirings: readonly Wiring[];
|
|
197
216
|
readonly disables: readonly DisableComment[];
|
|
198
217
|
private readonly lines;
|
|
199
218
|
constructor(props: SourceFileProps);
|
|
@@ -261,7 +280,7 @@ interface Settings {
|
|
|
261
280
|
//#endregion
|
|
262
281
|
//#region src/architecture/paths/location.d.ts
|
|
263
282
|
type Area = "context" | "shared-kernel" | "root" | "outside";
|
|
264
|
-
type Unseen = "ignored" | "unresolved";
|
|
283
|
+
type Unseen = "ignored" | "unresolved" | "dynamic";
|
|
265
284
|
interface LocationProps {
|
|
266
285
|
readonly area: Area;
|
|
267
286
|
readonly context?: string;
|
|
@@ -316,6 +335,15 @@ declare class BuildingBlocks {
|
|
|
316
335
|
foldersOf(layer: "domain" | "application"): readonly string[];
|
|
317
336
|
}
|
|
318
337
|
//#endregion
|
|
338
|
+
//#region src/architecture/crossing.d.ts
|
|
339
|
+
interface Crossing {
|
|
340
|
+
readonly line: number;
|
|
341
|
+
readonly from: string;
|
|
342
|
+
readonly to: string;
|
|
343
|
+
readonly expression: string;
|
|
344
|
+
readonly isOpenHostService: boolean;
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
319
347
|
//#region src/architecture/layers.d.ts
|
|
320
348
|
declare class Layers {
|
|
321
349
|
isLayer(name: string): name is Layer;
|
|
@@ -349,12 +377,15 @@ export declare class Architecture {
|
|
|
349
377
|
private readonly shape;
|
|
350
378
|
private readonly layout;
|
|
351
379
|
private readonly locations;
|
|
380
|
+
private readonly crossings;
|
|
352
381
|
constructor(project: Project$1, settings: Settings);
|
|
353
382
|
get files(): readonly SourceFile[];
|
|
354
383
|
get domainDependencies(): AllowedPackages;
|
|
355
384
|
get applicationDependencies(): AllowedPackages;
|
|
356
385
|
get contextMap(): ContextMap;
|
|
357
386
|
locationOf(file: SourceFile): Location;
|
|
387
|
+
locationOfPath(path: string): Location;
|
|
388
|
+
crossingsIn(file: SourceFile): Crossing[];
|
|
358
389
|
locationOfTarget(target: FileTarget$1): Location;
|
|
359
390
|
shapeIssueOf(location: Location): ShapeIssue | undefined;
|
|
360
391
|
relativePath(path: string): string;
|
|
@@ -418,7 +449,7 @@ declare class NoLooseDisableRule extends Rule<"tooling/no-loose-disable", Messag
|
|
|
418
449
|
}
|
|
419
450
|
//#endregion
|
|
420
451
|
//#region src/rules/registry.d.ts
|
|
421
|
-
declare const ruleIds: readonly ["strategic/no-cross-context-import", "strategic/no-leaky-host-service", "strategic/no-unmapped-context", "strategic/no-fat-shared-kernel", "layers/no-impure-domain", "layers/no-outward-import", "layers/no-portless-adapter", "layers/no-driving-shortcut", "tactical/no-aggregate-reference", "tactical/no-public-field", "tactical/no-foreign-command-dependency", "tactical/no-foreign-query-dependency", "tactical/no-stateful-service", "tactical/no-thrown-failure", "tactical/no-misplaced-class", "tactical/no-loose-code", "tooling/no-loose-disable"];
|
|
452
|
+
declare const ruleIds: readonly ["strategic/no-cross-context-import", "strategic/no-leaky-host-service", "strategic/no-unmapped-context", "strategic/no-fat-shared-kernel", "strategic/no-shared-state", "layers/no-impure-domain", "layers/no-outward-import", "layers/no-portless-adapter", "layers/no-driving-shortcut", "tactical/no-aggregate-reference", "tactical/no-public-field", "tactical/no-foreign-command-dependency", "tactical/no-foreign-query-dependency", "tactical/no-stateful-service", "tactical/no-thrown-failure", "tactical/no-misplaced-class", "tactical/no-loose-code", "tooling/no-loose-disable"];
|
|
422
453
|
type RuleId = (typeof ruleIds)[number];
|
|
423
454
|
export declare class RuleRegistry {
|
|
424
455
|
readonly rules: readonly Rule<RuleId>[];
|
|
@@ -459,6 +490,7 @@ interface ImportScope {
|
|
|
459
490
|
readonly projectDir: string;
|
|
460
491
|
readonly rootDir: string;
|
|
461
492
|
isIgnored(path: string): boolean;
|
|
493
|
+
readsWiring(path: string): boolean;
|
|
462
494
|
}
|
|
463
495
|
export declare abstract class Importer {
|
|
464
496
|
abstract read(scope: ImportScope): Project$1;
|
|
@@ -469,6 +501,7 @@ export declare abstract class Importer {
|
|
|
469
501
|
export declare class TsMorphImporter extends Importer {
|
|
470
502
|
private readonly sources;
|
|
471
503
|
private readonly classes;
|
|
504
|
+
private readonly types;
|
|
472
505
|
private readonly statements;
|
|
473
506
|
private readonly disables;
|
|
474
507
|
private readonly throws;
|
|
@@ -617,10 +650,12 @@ export declare class Config implements CheckSettings {
|
|
|
617
650
|
readonly contextMap: ContextMap;
|
|
618
651
|
private readonly ignored;
|
|
619
652
|
private readonly rules;
|
|
653
|
+
private readonly layout;
|
|
620
654
|
constructor(config: AlveolusConfig, projectDir: string);
|
|
621
655
|
private classifiedContexts;
|
|
622
656
|
private validContextMap;
|
|
623
657
|
isIgnored(path: string): boolean;
|
|
658
|
+
readsWiring(path: string): boolean;
|
|
624
659
|
severityOf(rule: RuleId): Severity | "off";
|
|
625
660
|
}
|
|
626
661
|
//#endregion
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":["Project","FileTarget","Project","Project","MorphProject","Project"],"sources":["../src/architecture/allowed-packages.ts","../src/conventions/core-api.ts","../src/conventions/layers.ts","../src/conventions/building-blocks.ts","../src/model/classes/named-type.ts","../src/model/classes/class-type.ts","../src/model/classes/heritage.ts","../src/model/classes/return-shape.ts","../src/model/classes/member.ts","../src/model/classes/class-declaration.ts","../src/model/dependencies/dependency.ts","../src/model/dependencies/global-use.ts","../src/model/statements/disable-comment.ts","../src/model/statements/throw.ts","../src/model/statements/top-level-statement.ts","../src/model/source-file.ts","../src/model/project.ts","../src/architecture/core-api.ts","../src/architecture/context-map.ts","../src/architecture/settings.ts","../src/architecture/paths/location.ts","../src/architecture/paths/place.ts","../src/architecture/building-blocks.ts","../src/architecture/layers.ts","../src/architecture/paths/layer-shape.ts","../src/architecture/architecture.ts","../src/rules/framework/finding.ts","../src/rules/framework/wording.ts","../src/rules/framework/rule.ts","../src/rules/tooling/no-loose-disable.rule.ts","../src/rules/registry.ts","../src/check/violation.ts","../src/check/baseline.ts","../src/importer/importer.ts","../src/importer/ts-morph/ts-morph-importer.ts","../src/check/checker.ts","../src/check/report.ts","../src/docs/page.ts","../src/docs/docs.ts","../src/cli/cli.ts","../src/config/alveolus-config.ts","../src/config/config.ts","../src/config/config-loader.ts","../src/config/define-config.ts","../src/init/init.ts"],"mappings":";;KAAY,sBAAsB,SAAS;qBAE9B;mBACO;EAAnB,YAAmB,WAA2B;EAE9C,IAAW;EAIX,eAAsB,qBAAqB;EAW3C,aAAoB;EAQpB,KAAY,OAAO,kBAAkB;;;;cC1BzB;KAmBD,mBAAmB;cAElB;KAED,qBAAqB;;;cCzBpB;KAED,gBAAgB;;;UCCX;WACP,OAAO;WACP;WACA;;;;UCNO;WACP;WACA;;;;cCAG;WAEX;WACA;WACA,kBAAkC;EAHnC,YACC,cACA,gCACA,kBAAkC;MAGxB;MAIA;;;;UCXK;WACP;WACA;WACA,gBAAgB;;UAGT;WACP;WACA,wBAAwB;;;;UCRjB;WACP,OAAO;WACP,gBAAgB;;;;KCDd;KAEA;UAEK;WACP;WACA;WACA,MAAM;WACN,YAAY;WACZ;WACA;WACA;WACA,qBAAqB;WACrB,yBAAyB;WACzB,SAAS;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":["Project","FileTarget","Project","Project","MorphProject","Project"],"sources":["../src/architecture/allowed-packages.ts","../src/conventions/core-api.ts","../src/conventions/layers.ts","../src/conventions/building-blocks.ts","../src/model/classes/named-type.ts","../src/model/classes/class-type.ts","../src/model/classes/heritage.ts","../src/model/classes/return-shape.ts","../src/model/classes/member.ts","../src/model/classes/class-declaration.ts","../src/model/dependencies/dependency.ts","../src/model/dependencies/global-use.ts","../src/model/dependencies/wiring.ts","../src/model/statements/disable-comment.ts","../src/model/statements/throw.ts","../src/model/statements/top-level-statement.ts","../src/model/source-file.ts","../src/model/project.ts","../src/architecture/core-api.ts","../src/architecture/context-map.ts","../src/architecture/settings.ts","../src/architecture/paths/location.ts","../src/architecture/paths/place.ts","../src/architecture/building-blocks.ts","../src/architecture/crossing.ts","../src/architecture/layers.ts","../src/architecture/paths/layer-shape.ts","../src/architecture/architecture.ts","../src/rules/framework/finding.ts","../src/rules/framework/wording.ts","../src/rules/framework/rule.ts","../src/rules/tooling/no-loose-disable.rule.ts","../src/rules/registry.ts","../src/check/violation.ts","../src/check/baseline.ts","../src/importer/importer.ts","../src/importer/ts-morph/ts-morph-importer.ts","../src/check/checker.ts","../src/check/report.ts","../src/docs/page.ts","../src/docs/docs.ts","../src/cli/cli.ts","../src/config/alveolus-config.ts","../src/config/config.ts","../src/config/config-loader.ts","../src/config/define-config.ts","../src/init/init.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as TsMorphImporter, c as Config, d as RuleRegistry, f as Rule, h as Baseline, i as Init, l as Report, m as AllowedPackages, n as Page, o as Importer, p as Architecture, r as Cli, s as ConfigLoader, t as Docs, u as Checker } from "./docs-
|
|
1
|
+
import { a as TsMorphImporter, c as Config, d as RuleRegistry, f as Rule, h as Baseline, i as Init, l as Report, m as AllowedPackages, n as Page, o as Importer, p as Architecture, r as Cli, s as ConfigLoader, t as Docs, u as Checker } from "./docs-B1Tzkjrn.mjs";
|
|
2
2
|
//#region src/config/define-config.ts
|
|
3
3
|
function defineConfig(config) {
|
|
4
4
|
return config;
|
|
@@ -313,7 +313,9 @@ may import it; it imports none of them.
|
|
|
313
313
|
Each change to the shared kernel reaches every context. `Clock` and `IdGenerator` already come
|
|
314
314
|
with `@alveolus/core`; only their adapters live here. An aggregate, a repository or a handler in
|
|
315
315
|
the shared kernel is reported by
|
|
316
|
-
[`strategic/no-fat-shared-kernel`](../rules/strategic/no-fat-shared-kernel.md)
|
|
316
|
+
[`strategic/no-fat-shared-kernel`](../rules/strategic/no-fat-shared-kernel.md), and a static field
|
|
317
|
+
that holds state, such as a registry of services, by
|
|
318
|
+
[`strategic/no-shared-state`](../rules/strategic/no-shared-state.md).
|
|
317
319
|
:::
|
|
318
320
|
|
|
319
321
|
## See also
|
package/docs/rules/index.md
CHANGED
|
@@ -51,8 +51,9 @@ its own business. The "Applies to" line of each rule page says which it is.
|
|
|
51
51
|
|
|
52
52
|
| Rule | Reports |
|
|
53
53
|
| --- | --- |
|
|
54
|
-
| [`strategic/no-cross-context-import`](./strategic/no-cross-context-import.md) | An import from another bounded context that is not its open host service, a composition root that re-exports. |
|
|
54
|
+
| [`strategic/no-cross-context-import`](./strategic/no-cross-context-import.md) | An import from another bounded context that is not its open host service, a file the analysis does not see, a composition root that re-exports. |
|
|
55
55
|
| [`strategic/no-fat-shared-kernel`](./strategic/no-fat-shared-kernel.md) | An aggregate, a repository or a handler in the shared kernel. |
|
|
56
|
+
| [`strategic/no-shared-state`](./strategic/no-shared-state.md) | A static field of the shared kernel that holds state. |
|
|
56
57
|
| [`strategic/no-leaky-host-service`](./strategic/no-leaky-host-service.md) | An open host service that exposes a class of its context instead of the published language. |
|
|
57
58
|
| [`strategic/no-unmapped-context`](./strategic/no-unmapped-context.md) | A context consuming one the context map does not allow, or two contexts that depend on each other. |
|
|
58
59
|
|
|
@@ -120,12 +121,15 @@ The rules that check imports read every way a file can depend on another one, no
|
|
|
120
121
|
`import … from`:
|
|
121
122
|
|
|
122
123
|
```ts
|
|
124
|
+
/// <reference path="../legacy/pool.ts" />
|
|
125
|
+
/// <reference types="pg" />
|
|
123
126
|
import { Pool } from "pg";
|
|
124
127
|
export { Pool } from "pg";
|
|
125
128
|
type Pool = import("pg").Pool;
|
|
126
129
|
const pg = await import("pg");
|
|
127
130
|
const pg = require("pg");
|
|
128
131
|
import pg = require("pg");
|
|
132
|
+
declare module "pg" { interface Pool { tenant: string } }
|
|
129
133
|
```
|
|
130
134
|
|
|
131
135
|
A global declared by the project, in a `declare global` block, counts as an import of the file that
|
|
@@ -133,8 +137,13 @@ declares it.
|
|
|
133
137
|
|
|
134
138
|
An import the analysis cannot see through counts as a file outside the project: one that does not
|
|
135
139
|
resolve, such as a `.js` file without types, one whose path is computed at runtime, or one that is
|
|
136
|
-
ignored, such as a test file. No
|
|
137
|
-
root of `src
|
|
140
|
+
ignored, such as a test file. No file imports it, not even a composition root or a file at the
|
|
141
|
+
root of `src/`: a test file that re-exports another context would otherwise carry the import past
|
|
142
|
+
every rule.
|
|
143
|
+
|
|
144
|
+
Code loaded at runtime counts the same way, wherever it is: `node:module` (`createRequire`),
|
|
145
|
+
`node:vm`, `module.require`, `process.getBuiltinModule`, `eval`, `Function` and `new Function`.
|
|
146
|
+
To read a JSON file, import it: `import pkg from "../package.json" with { type: "json" }`.
|
|
138
147
|
|
|
139
148
|
```
|
|
140
149
|
src/ordering/domain/services/pricing.service.ts
|
|
@@ -40,8 +40,11 @@ Within the same context, or towards the shared kernel:
|
|
|
40
40
|
| `published-language/` | Its own `published-language/`. From `@alveolus/core`, only `PublishedLanguage`, `IntegrationEvent` and `JsonValue`; other packages, such as a schema library, are fine. |
|
|
41
41
|
| `driven/` | `domain/`, `application/`, `published-language/`, `driven/`, any package. |
|
|
42
42
|
| `driving/` | `domain/`, `application/`, `published-language/`, `driving/`, any package. |
|
|
43
|
-
| the composition root | Anything in its context and in the shared kernel. |
|
|
44
|
-
| files at the root of `src/` | Composition roots,
|
|
43
|
+
| the composition root | Anything in its context and in the shared kernel, any package. |
|
|
44
|
+
| files at the root of `src/` | Composition roots, each other, any package. |
|
|
45
|
+
|
|
46
|
+
No file imports a file the analysis does not see: ignored, unresolved, computed at runtime, or
|
|
47
|
+
outside the declared contexts and the shared kernel.
|
|
45
48
|
|
|
46
49
|
No layer imports a composition root. The domain has its own rule,
|
|
47
50
|
[`layers/no-impure-domain`](./no-impure-domain.md); imports from another context are checked by
|
|
@@ -100,7 +103,8 @@ The rule also reports:
|
|
|
100
103
|
| Case | Message |
|
|
101
104
|
| --- | --- |
|
|
102
105
|
| A layer imports a composition root | `Imports … : only the composition root wires the layers.` |
|
|
103
|
-
| A file at the root imports inside a context | `Files at the root import composition roots only, not ….` |
|
|
106
|
+
| A file at the root imports inside a context, or a file the analysis does not see | `Files at the root import composition roots only, not ….` |
|
|
107
|
+
| A composition root imports a file the analysis does not see | `The composition root imports src/ordering/wiring.fixture.ts (ignored by the analysis): it wires its own context and the shared kernel only.` |
|
|
104
108
|
| Two composition roots in a context | `ordering has 2 composition roots (ordering.module.ts, pricing.module.ts): keep one, and move the rest into the layers.` |
|
|
105
109
|
| The published language imports another name from core | `The published language imports … from @alveolus/core: only published-language types are allowed.` |
|
|
106
110
|
| A name not allowed from a restricted package | `The application imports Controller from @nestjs/common: applicationDependencies only allows Injectable.` |
|
|
@@ -10,7 +10,7 @@ an anti-corruption layer or its composition root.
|
|
|
10
10
|
<dl class="al-glance">
|
|
11
11
|
<dt>Rule</dt><dd><code>strategic/no-cross-context-import</code></dd>
|
|
12
12
|
<dt>Category</dt><dd><a href="/rules/#strategic">Strategic</a>: what crosses a bounded context</dd>
|
|
13
|
-
<dt>Reports</dt><dd>An import from another bounded context that is not its open host service, used where it may be</dd>
|
|
13
|
+
<dt>Reports</dt><dd>An import from another bounded context that is not its open host service, used where it may be; a value of another context given in the wiring that is not its open host service; an import of a file the analysis does not see</dd>
|
|
14
14
|
<dt>Applies to</dt><dd>Every file of every bounded context, whatever its subdomain, and of the shared kernel</dd>
|
|
15
15
|
<dt>Turn off</dt><dd><a href="#turn-it-off"><code>"strategic/no-cross-context-import": "off"</code></a></dd>
|
|
16
16
|
</dl>
|
|
@@ -41,8 +41,21 @@ When a file of one bounded context imports a file of another one:
|
|
|
41
41
|
|
|
42
42
|
The shared kernel imports no bounded context at all. Every context may import the shared kernel.
|
|
43
43
|
|
|
44
|
+
No file of a context or of the shared kernel imports a file the analysis does not see: ignored,
|
|
45
|
+
unresolved, computed at runtime, or outside the declared contexts and the shared kernel. Such a
|
|
46
|
+
file could re-export another context, and the rule could not tell which one. Code loaded at
|
|
47
|
+
runtime, with `createRequire`, `eval`, `new Function` or `node:vm`, is reported the same way.
|
|
48
|
+
|
|
44
49
|
Every form of import counts, see [Every import counts](../index.md#every-import-counts).
|
|
45
50
|
|
|
51
|
+
The wiring follows the same contract. In the composition roots and the files at the root of
|
|
52
|
+
`src/`, a value of one context given to another one, such as
|
|
53
|
+
`new LedgerModule({ redemptions: () => this.emoney.commands.requestRedemption })`, is an
|
|
54
|
+
`OpenHostService` of the giving context, or one of its methods. A handler, a repository or any
|
|
55
|
+
other class of its model is reported, even when the receiving side declares a type of the same
|
|
56
|
+
shape. Whether the receiving context may consume the giving one at all is checked by
|
|
57
|
+
[`strategic/no-unmapped-context`](./no-unmapped-context.md).
|
|
58
|
+
|
|
46
59
|
## What it reports
|
|
47
60
|
|
|
48
61
|
```
|
|
@@ -67,6 +80,23 @@ src/shared-kernel/domain/value-objects/money.value-object.ts
|
|
|
67
80
|
bounded context, but imports
|
|
68
81
|
src/catalog/domain/value-objects/currency.value-object.ts
|
|
69
82
|
(catalog domain).
|
|
83
|
+
|
|
84
|
+
src/ordering/domain/services/pricing.service.ts
|
|
85
|
+
1 error strategic/no-cross-context-import: Imports
|
|
86
|
+
src/ordering/domain/value-objects/product-id.fixture.ts (ignored by
|
|
87
|
+
the analysis): the analysis cannot tell which bounded context it
|
|
88
|
+
reaches; move the file into a bounded context or the shared kernel.
|
|
89
|
+
|
|
90
|
+
src/ordering/driven/memory/adapters/memory-prices.adapter.ts
|
|
91
|
+
1 error strategic/no-cross-context-import: Loads code at runtime with
|
|
92
|
+
node:module: the analysis cannot tell which bounded context it
|
|
93
|
+
reaches; use a static import.
|
|
94
|
+
|
|
95
|
+
src/app.module.ts
|
|
96
|
+
14 error strategic/no-cross-context-import: Gives
|
|
97
|
+
this.emoney.commands.requestRedemption, from emoney, to ledger: only
|
|
98
|
+
an OpenHostService of another bounded context may cross, in an import
|
|
99
|
+
or in the wiring.
|
|
70
100
|
```
|
|
71
101
|
|
|
72
102
|
## Fix it
|
|
@@ -119,7 +149,10 @@ bounded context. Move what it needs into the shared kernel, or keep it in the co
|
|
|
119
149
|
- The rule checks what an anti-corruption layer imports, not what it does with it: an adapter that
|
|
120
150
|
returns the open host service's answer as is, untranslated, is accepted. In review, the ACL
|
|
121
151
|
should build values of its own context.
|
|
122
|
-
- A file that matches `ignore` in `alveolus.config.ts` is not analysed at all
|
|
152
|
+
- A file that matches `ignore` in `alveolus.config.ts` is not analysed at all, and no file of a
|
|
153
|
+
context may import it. Review a change to `ignore` as you would review a rule turned off.
|
|
154
|
+
- In the wiring, a value whose type is erased on the way, by a cast or a container token written
|
|
155
|
+
as a string, is not seen. In review, the composition root holds no cast.
|
|
123
156
|
:::
|
|
124
157
|
|
|
125
158
|
## Turn it off
|
|
@@ -77,5 +77,6 @@ rules: { "strategic/no-fat-shared-kernel": "off" },
|
|
|
77
77
|
## See also
|
|
78
78
|
|
|
79
79
|
- [Project layout: shared kernel](../../guide/project-layout.md#shared-kernel)
|
|
80
|
+
- [`strategic/no-shared-state`](./no-shared-state.md), no state in the shared kernel either
|
|
80
81
|
- [`strategic/no-unmapped-context`](./no-unmapped-context.md), the other way contexts get tied
|
|
81
82
|
- [Rules](../index.md), every rule by category
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Architecture rule: the shared kernel shares a model, not state; a static field that holds state would let two contexts talk where the context map does not show it."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# no-shared-state
|
|
6
|
+
|
|
7
|
+
The shared kernel shares a model, never state. A static field that the code can change is reached
|
|
8
|
+
by every context at once: a channel between them that no import and no context map shows.
|
|
9
|
+
|
|
10
|
+
<dl class="al-glance">
|
|
11
|
+
<dt>Rule</dt><dd><code>strategic/no-shared-state</code></dd>
|
|
12
|
+
<dt>Category</dt><dd><a href="/rules/#strategic">Strategic</a>: what crosses a bounded context</dd>
|
|
13
|
+
<dt>Reports</dt><dd>A static field of the shared kernel without <code>readonly</code>, or holding a collection</dd>
|
|
14
|
+
<dt>Applies to</dt><dd>Every class of the shared kernel</dd>
|
|
15
|
+
<dt>Turn off</dt><dd><a href="#turn-it-off"><code>"strategic/no-shared-state": "off"</code></a></dd>
|
|
16
|
+
</dl>
|
|
17
|
+
|
|
18
|
+
## Why
|
|
19
|
+
|
|
20
|
+
Ledger needs to create a redemption in e-money, and the context map says ledger consumes nothing.
|
|
21
|
+
Someone adds a `ServiceRegistry` to the shared kernel: e-money registers its handler under a name,
|
|
22
|
+
ledger resolves it. No file of ledger imports e-money, every import rule passes, and ledger now
|
|
23
|
+
depends on e-money in a way nobody decided.
|
|
24
|
+
|
|
25
|
+
::: tip The fix
|
|
26
|
+
Contexts talk through an [open host service](../../core/strategic/open-host-services.md), consumed
|
|
27
|
+
in an [anti-corruption layer](../../core/strategic/anti-corruption-layers.md), and the
|
|
28
|
+
[context map](./no-unmapped-context.md) says who consumes whom. The shared kernel holds what both
|
|
29
|
+
contexts mean the same way: value objects, identifiers, ports. Constants are fine; a place to put
|
|
30
|
+
things is not.
|
|
31
|
+
:::
|
|
32
|
+
|
|
33
|
+
## What it checks
|
|
34
|
+
|
|
35
|
+
Every static field of a class in the shared kernel:
|
|
36
|
+
|
|
37
|
+
| Static field | Allowed |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `static readonly ZERO = new Money({ amount: 0 })` | ✅ |
|
|
40
|
+
| `static readonly PRECISION = 2` | ✅ |
|
|
41
|
+
| `static readonly CURRENCIES: readonly string[] = ["EUR"]`, a `ReadonlyMap`, a `ReadonlySet` | ✅ |
|
|
42
|
+
| `static count = 0`: without `readonly` | ❌ |
|
|
43
|
+
| `static readonly services = new Map()`: a `Map`, a `Set`, a `WeakMap`, a `WeakSet` | ❌ |
|
|
44
|
+
| `static readonly names: string[] = []`: an array that is not `readonly` | ❌ |
|
|
45
|
+
| `static readonly byId: Record<string, Handler> = {}`: an index signature | ❌ |
|
|
46
|
+
| `static readonly options = { strict: true }`: an object literal | ❌ |
|
|
47
|
+
|
|
48
|
+
Instance fields are not checked: an adapter of the shared kernel may hold its own state, each
|
|
49
|
+
context builds its own instance.
|
|
50
|
+
|
|
51
|
+
Module-level state, such as `let current` or `const services = new Map()` at the top of a file, is
|
|
52
|
+
reported by [`tactical/no-loose-code`](../tactical/no-loose-code.md).
|
|
53
|
+
|
|
54
|
+
## What it reports
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
src/shared-kernel/driven/memory/registry/service-registry.ts
|
|
58
|
+
2 error strategic/no-shared-state: ServiceRegistry.services holds a
|
|
59
|
+
collection in a static field: every context reaches the same one, a
|
|
60
|
+
channel the context map does not show. The shared kernel shares a
|
|
61
|
+
model, not state: integrate through an open host service.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Fix it
|
|
65
|
+
|
|
66
|
+
### Integrate through an open host service
|
|
67
|
+
|
|
68
|
+
So that the dependency is decided and visible, the upstream context exposes an open host service,
|
|
69
|
+
the downstream context adds it to `contextMap.<downstream>.consumes` and calls it from an
|
|
70
|
+
anti-corruption layer. The composition root passes the service; no registry is needed.
|
|
71
|
+
|
|
72
|
+
### Keep a constant constant
|
|
73
|
+
|
|
74
|
+
So that a constant cannot become a channel, make it `readonly` and give it an immutable type: a
|
|
75
|
+
value object, a primitive, a `readonly` array, a `ReadonlyMap`.
|
|
76
|
+
|
|
77
|
+
## Limits
|
|
78
|
+
|
|
79
|
+
::: warning What the rule cannot see
|
|
80
|
+
- A `static readonly` field typed by a class whose instances can change, such as
|
|
81
|
+
`static readonly bus = new EventEmitter()`, passes: the rule reads the type of the field, not
|
|
82
|
+
what its class does. In review, a static field of the shared kernel holds a value, never a
|
|
83
|
+
service.
|
|
84
|
+
- A `Readonly<Record<…>>` counts as a collection, because it has an index signature: use a
|
|
85
|
+
`ReadonlyMap` for a constant dictionary.
|
|
86
|
+
- State held outside the shared kernel is not checked: a package with a global container, such as
|
|
87
|
+
the default container of a dependency injection library, or a write to `globalThis`.
|
|
88
|
+
:::
|
|
89
|
+
|
|
90
|
+
## Turn it off
|
|
91
|
+
|
|
92
|
+
```ts [alveolus.config.ts]
|
|
93
|
+
rules: { "strategic/no-shared-state": "off" },
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## See also
|
|
97
|
+
|
|
98
|
+
- [`strategic/no-fat-shared-kernel`](./no-fat-shared-kernel.md), what the shared kernel holds
|
|
99
|
+
- [`strategic/no-unmapped-context`](./no-unmapped-context.md), the relations between contexts
|
|
100
|
+
- [Rules](../index.md), every rule by category
|
|
@@ -10,8 +10,8 @@ never depend on each other.
|
|
|
10
10
|
<dl class="al-glance">
|
|
11
11
|
<dt>Rule</dt><dd><code>strategic/no-unmapped-context</code></dd>
|
|
12
12
|
<dt>Category</dt><dd><a href="/rules/#strategic">Strategic</a>: what crosses a bounded context</dd>
|
|
13
|
-
<dt>Reports</dt><dd>An import of another context that <code>contextMap</code> does not allow</dd>
|
|
14
|
-
<dt>Applies to</dt><dd>Every file of every bounded context
|
|
13
|
+
<dt>Reports</dt><dd>An import of another context, or a value of another context given in the wiring, that <code>contextMap</code> does not allow</dd>
|
|
14
|
+
<dt>Applies to</dt><dd>Every file of every bounded context; for the wiring, the composition roots and the files at the root of <code>src/</code></dd>
|
|
15
15
|
<dt>Turn off</dt><dd><a href="#turn-it-off"><code>"strategic/no-unmapped-context": "off"</code></a></dd>
|
|
16
16
|
</dl>
|
|
17
17
|
|
|
@@ -43,6 +43,26 @@ never allowed, whichever way the code is written.
|
|
|
43
43
|
|
|
44
44
|
Imports of the shared kernel are not consumptions: every context may import it.
|
|
45
45
|
|
|
46
|
+
### The wiring counts too
|
|
47
|
+
|
|
48
|
+
A context can consume another one without importing it: `app.module.ts` sees every module, and
|
|
49
|
+
can hand a value of one context to another. Ledger declares a port, its adapter calls whatever it
|
|
50
|
+
is given, and the root composition gives it a handler of e-money:
|
|
51
|
+
|
|
52
|
+
```ts [src/app.module.ts]
|
|
53
|
+
this.ledger = new LedgerModule({
|
|
54
|
+
redemptions: () => this.emoney.commands.requestRedemption,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
No file of ledger imports e-money, yet ledger now consumes it. In the composition roots and the
|
|
59
|
+
files at the root of `src/`, every value given to a class, a function or a field of one context
|
|
60
|
+
(an argument, a property of an object, the body of an arrow function, an assignment, a variable
|
|
61
|
+
typed by that context) is read: when it comes from another context, by where it is declared or by
|
|
62
|
+
its type, the receiving context consumes that one, and the map must say so. A module handed whole,
|
|
63
|
+
such as `new PaymentsModule(this.ledger)`, is not a consumption yet: what that module then takes
|
|
64
|
+
from it is.
|
|
65
|
+
|
|
46
66
|
## What it reports
|
|
47
67
|
|
|
48
68
|
```
|
|
@@ -51,6 +71,13 @@ src/ledger/driven/payments/adapters/payment-status.adapter.ts
|
|
|
51
71
|
context map does not allow: reverse the dependency, or if ledger
|
|
52
72
|
really is downstream of payments, add payments to
|
|
53
73
|
contextMap.ledger.consumes.
|
|
74
|
+
|
|
75
|
+
src/app.module.ts
|
|
76
|
+
14 error strategic/no-unmapped-context: ledger receives
|
|
77
|
+
this.emoney.commands.requestRedemption from emoney here, which the
|
|
78
|
+
context map does not allow: reverse the dependency, or if ledger
|
|
79
|
+
really is downstream of emoney, add emoney to
|
|
80
|
+
contextMap.ledger.consumes.
|
|
54
81
|
```
|
|
55
82
|
|
|
56
83
|
A map that would allow it is refused before the check:
|
|
@@ -94,8 +121,14 @@ So that `Ledger` stays upstream, it does not ask `Payments` anything: it publish
|
|
|
94
121
|
|
|
95
122
|
::: warning What the rule cannot see
|
|
96
123
|
- A dependency that goes through the database, a queue or an HTTP call to another context's API
|
|
97
|
-
written as a string: the map covers imports. In review, every consumption of
|
|
98
|
-
is an import of its open host service.
|
|
124
|
+
written as a string: the map covers imports and the wiring. In review, every consumption of
|
|
125
|
+
another context is an import of its open host service.
|
|
126
|
+
- A value whose type is erased on the way: a cast (`as unknown as Handler`, `any`) in the
|
|
127
|
+
composition root, or a container token written as a string, such as NestJS
|
|
128
|
+
`{ provide: "redemptions", useFactory: … }`. In review, the composition root holds no cast, and
|
|
129
|
+
a token is the abstract class of a port.
|
|
130
|
+
- With the rule off, the wiring is no longer checked against the map, even though
|
|
131
|
+
[`strategic/no-cross-context-import`](./no-cross-context-import.md) still checks what crosses.
|
|
99
132
|
:::
|
|
100
133
|
|
|
101
134
|
## Turn it off
|