no-cross-context-import
A bounded context is closed: another context reaches it only through its open host service, from an anti-corruption layer or its composition root.
- Rule
strategic/no-cross-context-import- Category
- Strategic: what crosses a bounded context
- Reports
- An import from another bounded context that is not its open host service, used where it may be
- Applies to
- Every file of every bounded context and of the shared kernel
- Turn off
"strategic/no-cross-context-import": "off"
Why
An adapter of ordering imports Product from the catalog domain, because it had the fields it needed. The two contexts now share one model without anyone deciding it: renaming a field in the catalog breaks ordering, and nothing showed the dependency until it broke.
The fix
One door on each side. The catalog exposes an open host service; ordering calls it from an anti-corruption layer that translates the answer into its own model. Everything in between is free to change.
What it checks
When a file of one bounded context imports a file of another one:
OpenHostService.AntiCorruptionLayer, or is the composition root of its context.The shared kernel imports no bounded context at all. Every context may import the shared kernel.
What it reports
src/ordering/driven/pg/adapters/stock.adapter.ts:1
strategic/no-cross-context-import: Imports
src/catalog/domain/aggregates/product.aggregate.ts (catalog domain):
only an OpenHostService of another bounded context may be imported.
src/ordering/driven/pg/adapters/stock.adapter.ts:2
strategic/no-cross-context-import: Imports the published language
of catalog: redeclare the fields you read in your own
published-language/.
src/ordering/driven/pg/adapters/stock.adapter.ts:3
strategic/no-cross-context-import: Uses the open host service of
catalog outside an AntiCorruptionLayer: translate it in an
anti-corruption layer.
src/shared-kernel/domain/value-objects/money.value-object.ts:1
strategic/no-cross-context-import: The shared kernel imports no
bounded context, but imports
src/catalog/domain/value-objects/currency.value-object.ts
(catalog domain).Fix it
Go through an anti-corruption layer
So that only one class knows the other context exists, declare in your domain a port that asks in your own words, and implement it in an anti-corruption layer that calls the open host service.
import type { Product } from "../../../../catalog/domain/aggregates/product.aggregate";
import type { ProductRepresentation } from "../../../../catalog/published-language/product.representation";import type { AntiCorruptionLayer } from "@alveolus/core";
import type { CatalogApi } from "../../../../catalog/driving/in-process/catalog-api";
import { PriceList } from "../../../domain/ports/price-list.port";
export class CatalogPriceList
extends PriceList
implements AntiCorruptionLayer
{
constructor(private readonly catalog: CatalogApi) {
super();
}
}Redeclare the fields you read
So that the upstream context can change its published language without breaking yours, the downstream context redeclares the fields it reads in its own published-language/, instead of importing the upstream types. When the open host service is called in the same process, TypeScript still checks that both shapes match, at the anti-corruption layer and nowhere else.
Keep the shared kernel independent
So that a change in one context never reaches all the others, the shared kernel imports no bounded context. Move what it needs into the shared kernel, or keep it in the context that owns it.
Turn it off
rules: { "strategic/no-cross-context-import": "off" },On an existing project, prefer a baseline: new code keeps the boundaries while you remove the old shortcuts.
See also
- Project layout: bounded contexts
- Open host services and Anti-corruption layers
layers/no-outward-import, for dependencies inside a context- Rules, every rule by category