Skip to content

no-outward-import ​

Every dependency points towards the domain, only the composition root sees every layer, and every file belongs to a layer.

Rule
layers/no-outward-import
Category
Layers: what each layer may depend on
Reports
An import that points away from the domain, a package the application may not use, a file outside the layers
Applies to
application/, published-language/, driven/, driving/, composition roots and files at the root of src/
Turn off
"layers/no-outward-import": "off"

Why ​

The PlaceOrderHandler imports PgOrders to save the order, and a controller imports the mailer adapter to send a confirmation. Replacing the database now means rewriting use cases, and a controller has started sending emails.

The fix

Every arrow points inwards. The application knows the domain and its ports, never an adapter; each adapter knows the application, never the adapters on the other side. The composition root is the one place that knows how everything fits, so each layer can be replaced, tested and read on its own.

What it checks ​

Imports between layers ​

Within the same context, or towards the shared kernel:

FromMay import
application/domain/, application/, its own published-language/. Packages: @alveolus/core, domainDependencies, applicationDependencies.
published-language/Its own published-language/. From @alveolus/core, only PublishedLanguage, IntegrationEvent and JsonValue; other packages, such as a schema library, are fine.
driven/domain/, application/, published-language/, driven/, any package.
driving/domain/, application/, published-language/, driving/, any package.
the composition rootAnything in its context and in the shared kernel.
files at the root of src/Composition roots, and each other.

No layer imports a composition root. The domain has its own rule, layers/no-impure-domain; imports from another context are checked by strategic/no-cross-context-import.

Files outside the layers ​

Inside a contextA file directly in a context that is not its composition root belongs to no layer.
Outside every contextA file under src/, outside every declared context and the shared kernel, and not at the root.

What it reports ​

src/ordering/application/commands/place-order.command.ts:1
  layers/no-outward-import: The application imports
  @nestjs/common: add it to applicationDependencies if the
  application really needs it.

src/ordering/application/commands/place-order.command.ts:3
  layers/no-outward-import: The application layer imports
  src/ordering/driven/pg/adapters/pg-orders.adapter.ts
  (ordering driven): it may only import domain, application,
  published-language.

src/ordering/helpers.ts:1
  layers/no-outward-import: The file is outside the layers:
  move it to domain/, application/, published-language/,
  driven/ or driving/.

The rule also reports:

CaseMessage
A layer imports a composition rootImports … : only the composition root wires the layers.
A file at the root imports inside a contextFiles at the root import composition roots only, not ….
The published language imports another name from coreThe published language imports … from @alveolus/core: only published-language types are allowed.
A name not allowed from a restricted packageThe application imports Controller from @nestjs/common: applicationDependencies only allows Injectable.
A file outside the declared contextsThe file is outside the bounded contexts and the shared kernel declared in alveolus.config.ts: move it, or add it to ignore.

Fix it ​

Depend on the port, not on the adapter ​

So that replacing the database touches no use case, the handler receives the repository it extends from the domain, and the composition root passes it the adapter.

❌ Avoid: src/ordering/application/commands/place-order.command.ts
ts
import { Controller } from "@nestjs/common";

import { PgOrders } from "../../driven/pg/adapters/pg-orders.adapter";
✅ Prefer: src/ordering/application/commands/place-order.command.ts
ts
import { CommandHandler } from "@alveolus/core";

import { Orders } from "../../domain/repositories/orders.repository";

Give every file a layer ​

So that every file has a known place, move a helper into the layer that uses it, as a building block. A file that has nothing to do with the architecture, such as a script, can be left out with ignore in alveolus.config.ts.

Allow a package ​

The application imports no framework: the composition root builds its classes. A package it really needs is declared, with true to allow everything it exports, or with the names you allow:

alveolus.config.ts
ts
export default defineConfig({
	applicationDependencies: {
		"@nestjs/common": ["Injectable"],
		zod: true,
	},
	boundedContexts: { ordering: "ordering" },
	root: "src",
});

The packages of domainDependencies are allowed in the application too.

Turn it off ​

alveolus.config.ts
ts
rules: { "layers/no-outward-import": "off" },

On an existing project, prefer a baseline: new code keeps the arrows inwards while you fix the old ones.

See also ​

Released under the MIT License.