---
url: /guide/project-layout.md
---
# Project layout

Every Alveolus project has the same shape: the same folders, the same file names, the same
direction between layers. Whoever opens the code, a teammate or an agent, knows where a concept
lives before searching for it, and `alveolus arch check` keeps it that way.

Arrows read "depends on". They all point inwards: the domain depends on nothing but itself, so the
business rules never change because a database, a framework or another context did.

## At a glance

```
src/
  main.ts                     # starts the application
  app.module.ts               # assembles the bounded contexts
  ordering/                   # a bounded context
    ordering.module.ts        # its composition root
    domain/
      aggregates/             # order.aggregate.ts
      entities/               # order-line.entity.ts
      value-objects/          # money.value-object.ts, order-id.identifier.ts
      events/                 # order-placed.event.ts
      errors/                 # invalid-total.error.ts
      services/               # shipping-cost.service.ts
      repositories/           # orders.repository.ts
      ports/                  # price-list.port.ts
      views/                  # order-summary.view.ts
    application/
      commands/               # place-order.command.ts
      queries/                # get-order-summary.query.ts
      translators/            # order-events.translator.ts
    published-language/       # order-placed.representation.ts
    driven/
      pg/
        adapters/             # pg-orders.adapter.ts
      catalog/
        adapters/             # catalog-price-list.adapter.ts
    driving/
      http/
        controllers/          # orders.controller.ts
      rabbitmq/
        consumers/            # payment-received.consumer.ts
  catalog/                    # another bounded context, same shape
  shared-kernel/              # shared by every bounded context, same shape
```

Only create a folder when it gets its first file: a small context may have no `entities/`,
`services/` or `driving/rabbitmq/`.

## Bounded contexts

A bounded context is a folder under `src/`, declared in `alveolus.config.ts`. Contexts may be
nested, for instance under `src/modules/`. Each one has its own model: a `Product` in the catalog
and a product in ordering are two different things, and neither imports the other.

A context is closed. The only class another context may import is an **open host service**, and
only from an **anti-corruption layer** or from its composition root. What crosses the boundary is
the published language: plain JSON that the reader redeclares on its side, never the classes of
the other model.

The ordering domain asks for prices in its own words, through the `PriceList` port. The
anti-corruption layer is the one place that knows the catalog exists: it calls `CatalogApi`,
reads its JSON and answers with ordering's objects. If the catalog moves behind HTTP, only that
adapter changes.

## Layers

| Layer | Holds | May import |
| --- | --- | --- |
| `domain/` | The model: aggregates, entities, value objects, events, errors, domain services, and the ports and repositories it needs. | The domain of its context and of the shared kernel, the domain building blocks of `@alveolus/core`, the packages listed in `domainDependencies`. No framework, no ORM. |
| `application/` | One class per use case: command handlers, query handlers, and the translators that turn domain events into the published language. | The domain, the application, its own published language, `@alveolus/core`, `domainDependencies`, `applicationDependencies`. No framework. |
| `published-language/` | The JSON types exchanged with other contexts: what this context publishes, and what it reads from the others. | Its own published language, the published-language types of core, and packages such as a schema library. |
| `driven/` | The adapters that implement the ports: database repositories, API clients, the outbox, the clock. | The domain, the application, the published language, any package. Never `driving/`. |
| `driving/` | The adapters that call the use cases: HTTP controllers, message consumers, scheduled jobs, CLI commands. | The domain, the application, the published language, any package. Never `driven/`. |

Inside `driven/` and `driving/`, files always sit under the name of their technology:
`driven/pg/adapters/`, `driven/http/adapters/`, `driving/http/controllers/`. Replacing a
technology then means adding a folder next to the old one, never touching it. An adapter that calls
another bounded context in the same process sits under the name of that context:
`driven/catalog/adapters/`. Inside `domain/` and `application/`, every
class extends a building block of `@alveolus/core`: there are no free functions and no plain
classes.

## Folders and file names

Each class goes in the folder of its kind, in a file whose name ends with that kind. One class per
file; the types that belong to it, such as its snapshot or its command input, stay in its file.

| Kind | Extends | Folder | File name |
| --- | --- | --- | --- |
| Aggregate | `AggregateRoot` | `domain/aggregates/` | `order.aggregate.ts` |
| Entity | `Entity` | `domain/entities/` | `order-line.entity.ts` |
| Value object | `ValueObject` | `domain/value-objects/` | `money.value-object.ts` |
| Identifier | `Identifier` | `domain/value-objects/` | `order-id.identifier.ts` |
| Domain event | `DomainEvent` | `domain/events/` | `order-placed.event.ts` |
| Domain error | `DomainError` | `domain/errors/` | `invalid-total.error.ts` |
| Domain service | `DomainService` | `domain/services/` | `shipping-cost.service.ts` |
| Repository | `CommandRepository`, `QueryRepository` | `domain/repositories/` | `orders.repository.ts` |
| Port | `Port` | `domain/ports/` | `price-list.port.ts` |
| View | a `View<…>` type | `domain/views/` | `order-summary.view.ts` |
| Command handler | `CommandHandler` | `application/commands/` | `place-order.command.ts` |
| Query handler | `QueryHandler` | `application/queries/` | `get-order-summary.query.ts` |
| Event translator | `EventTranslator` | `application/translators/` | `order-events.translator.ts` |
| Representation | a `PublishedLanguage<…>` type | `published-language/` | `order-placed.representation.ts` |
| Driven adapter | a port | `driven/<technology>/adapters/` | `pg-orders.adapter.ts` |
| Open host service | implements `OpenHostService` | `driving/<technology>/` | free |

Tests sit next to the code they test and keep its name: `order.aggregate.spec.ts` or
`order.aggregate.test.ts`.

## Composition root

Each bounded context has one file at its root that wires its adapters into its use cases, its
module: `ordering.module.ts`. It is a class that builds everything with `new`, or the module of your
framework's container, such as a NestJS `@Module`: see [Integrations](../integrations/index.md). It
is the only file that sees every layer, and the only one, besides an anti-corruption layer, that
may import from another context: another context's module, to reach its open host services.

At the root of `src/`, `main.ts` starts the application and `app.module.ts` builds or imports the
module of each bounded context. These files import composition roots only.

## Shared kernel

`src/shared-kernel/` holds what every bounded context needs in the same form: value objects such
as `Money`, ports such as a tracer, and their adapters. It has the same layers as a bounded
context, possibly grouped by feature (`shared-kernel/time/driven/system/adapters/`). Every context may import it;
it imports none of them.

Keep it small: each change to the shared kernel reaches every context. `Clock` and `IdGenerator`
already come with `@alveolus/core`; only their adapters live here.

## Who may import what

Read a row as "files in this layer may import…", within the same bounded context or from the
shared kernel.

| From ↓ · To → | domain | application | published-language | driven | driving | composition root |
| --- | :-: | :-: | :-: | :-: | :-: | :-: |
| **domain** | ✓ | ✕ | ✕ | ✕ | ✕ | ✕ |
| **application** | ✓ | ✓ | ✓ | ✕ | ✕ | ✕ |
| **published-language** | ✕ | ✕ | ✓ | ✕ | ✕ | ✕ |
| **driven** | ✓ | ✓ | ✓ | ✓ | ✕ | ✕ |
| **driving** | ✓ | ✓ | ✓ | ✕ | ✓ | ✕ |
| **composition root** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |

From another bounded context, only an open host service may be imported, by an anti-corruption
layer or the composition root.

## Checked for you

`alveolus arch check` verifies this layout on every run: see the [rules](../rules/index.md), one page
per rule, with what each one checks and why.
