Skip to content

Building blocks ​

@alveolus/core gives you the building blocks of Domain-Driven Design as abstract classes. Your classes extend them: Order extends AggregateRoot, Money extends ValueObject. The class says what it is, and alveolus arch check knows where it belongs and what it may depend on.

ts
import { AggregateRoot, err, ok, type Result } from "@alveolus/core";

export class Order extends AggregateRoot<OrderId, OrderPlaced, OrderSnapshot> {
	place(total: number, eventId: string, now: Date): Result<void, InvalidTotal> {
		if (total <= 0) {
			return err(new InvalidTotal({ total }));
		}
		this.record(new OrderPlaced({ aggregateId: this.id, id: eventId, occurredAt: now, payload: { total } }));
		return ok();
	}
}

Domain ​

The model and what it needs from the outside world. Everything here lives in domain/ and imports nothing but the domain.

Building blockWhat it is
AggregatesA cluster of objects changed as one unit, through its root, which records domain events.
EntitiesAn object defined by its identity, inside an aggregate.
Value objectsAn immutable value compared by its attributes, and the typed identifiers.
Domain eventsSomething that happened in the domain, in the past tense.
Domain errorsAn expected business failure, returned as a value.
Domain servicesA stateless operation that belongs to no single object.
PortsWhat the domain needs from the outside world, in its own words.
RepositoriesHow aggregates are loaded and saved, and how views are read.
ViewsWhat a query returns.

Application ​

The use cases, and the contracts that make them atomic and reliable. Everything here lives in application/, except the adapters that implement the contracts.

Building blockWhat it is
Command handlersThe application service of one use case that changes the system.
Query handlersThe application service of one read.
Event translatorsTurns domain events into integration events.
Integration eventsWhat other contexts receive when something happens: JSON.
Event publishersSends integration events to the rest of the system.
Unit of WorkMakes a use case atomic: commit on success, roll back otherwise.
OutboxStores integration events with the change, then relays them, so none is lost.

Strategic ​

How bounded contexts meet without sharing a model. See also the bounded contexts of the project layout.

Building blockWhat it is
Published LanguageThe JSON format exchanged between contexts.
Open host servicesThe documented entry point of a context, the only class others may import.
Anti-corruption layersThe adapter that reads another context and translates it into yours.

Utilities ​

Building blockWhat it is
ResultSuccess or failure as a value, with the helpers to combine them.

Principles ​

  • You extend, nothing is configured. No decorator, no registry, no reflection: the class you extend says what your class is. A class extending your own base class counts too.
  • Business errors are values. An expected failure is a DomainError returned in a Result, never thrown. Each signature lists what can go wrong. Exceptions stay for bugs.
  • The domain never reads the clock nor generates ids. Dates and ids are parameters of business methods; Clock and IdGenerator give them to the application.
  • No infrastructure. No bus, no container, no ORM. Repositories, ports and publishers are abstract classes your adapters extend with the tools you already use. Handlers take them in their constructor: wire them by hand or with any container, where the same classes are the injection tokens.
  • No runtime dependency. @alveolus/core ends up in your domain and brings nothing with it.

Import paths ​

Import everything from @alveolus/core, or each building block from its own entry point, named after its page: @alveolus/core/aggregates, @alveolus/core/value-objects, @alveolus/core/command-handlers, @alveolus/core/result… Both give the same classes.

Released under the MIT License.