domain

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 9, 2026 License: Apache-2.0 Imports: 2 Imported by: 0

Documentation

Overview

Package domain provides the DDD building blocks Warren's other contracts are expressed in terms of: identity, aggregates, events, and specifications.

It contains no persistence, no transport, and no publication. An aggregate records events; the persistence.UnitOfWork drains and stores them.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Aggregate

type Aggregate interface {
	PullEvents() []Event
}

Aggregate is the identity-agnostic view of an aggregate root: the events it has pending. Every Root satisfies it.

It exists because a unit of work holds a heterogeneous collection of saved aggregates and therefore cannot name their identifier types — the one method it needs is this one.

type AggregateRoot

type AggregateRoot[T ID] struct {
	Entity[T]
	// contains filtered or unexported fields
}

AggregateRoot is the consistency boundary of a cluster of entities and the only object a repository loads or saves. It accumulates the domain events raised while its invariants were enforced; those events are not published until the unit of work commits.

An aggregate is used through a pointer and confined to one goroutine at a time — it is a consistency boundary, not a shared structure. Copying an aggregate value after events are raised aliases the pending-event array, and two copies then corrupt each other's events; §3.1's pattern (*User everywhere after construction) is the contract, not a style choice.

func NewAggregateRoot

func NewAggregateRoot[T ID](id T) AggregateRoot[T]

NewAggregateRoot returns an aggregate root with its identity set at construction. It is called by the aggregate's own constructor — the aggregate mints its identifier before the first event is raised — and by the reconstitution path a repository loads through.

It is the only way identity is set: an aggregate assembled without it — the zero value — silently carries the zero identifier, which a repository would save under an empty key. Repositories reconstitute through this constructor, never by filling in the struct.

func (*AggregateRoot[T]) PullEvents

func (a *AggregateRoot[T]) PullEvents() []Event

PullEvents returns the events raised since the last call and clears them from the aggregate. It is drained by the unit of work inside the business transaction; calling it elsewhere loses events.

func (*AggregateRoot[T]) Raise

func (a *AggregateRoot[T]) Raise(e Event)

Raise records a domain event on the aggregate. It publishes nothing. Like every aggregate method it assumes single-goroutine confinement; concurrent Raise is a data race by design, not an oversight.

type Entity

type Entity[T ID] struct {
	// contains filtered or unexported fields
}

Entity is the identity-carrying base of a domain entity. Two entities are the same entity when their identifiers are equal, regardless of their other fields.

func (*Entity[T]) ID

func (e *Entity[T]) ID() T

ID returns the entity's identifier. It is the identity accessor: the identifier itself is set at construction and never reassigned.

type Event

type Event interface {
	// EventName returns the stable, dotted name of the fact, such as
	// "user.registered". It is the topic and type key for messaging.
	EventName() string
	// OccurredAt returns when the fact happened, not when it was published.
	OccurredAt() time.Time
	// AggregateID returns the identity of the aggregate the fact belongs to.
	AggregateID() string
}

Event is a fact that has already happened in the domain. Implementations are values: named, timestamped, and attributable to one aggregate.

type ID

type ID interface {
	comparable
	fmt.Stringer
}

ID constrains the identifier type of an entity. An identifier must be usable as a map key and must render itself for logs, URLs, and message keys.

type Root

type Root[T ID] interface {
	ID() T
	Aggregate
}

Root is the constraint repositories are generic over. AggregateRoot satisfies it. (A struct cannot serve as a Go type constraint — only this interface makes Repository[T Root[K], K ID] expressible.)

type Specification

type Specification[T any] interface {
	// IsSatisfiedBy reports whether the candidate matches the specification.
	IsSatisfiedBy(T) bool
}

Specification is a reusable predicate over T: a domain rule expressed once and evaluable in memory.

It renders no SQL. A domain type obliged to emit SQL knows its persistence technology by name, which is the substance of the dependency rule even though no import crosses — and it is unimplementable for Mongo and Redis. Pushing a predicate down to a query is the driver's business: a repository type-asserts for its own translator interface (postgres.SQLSpecification, a driver's own filter translator) and falls back to in-memory evaluation.

type Versioned added in v0.2.0

type Versioned interface {
	// Version reports the version this aggregate was loaded at. Zero means it
	// has never been persisted, so the write is an insert.
	Version() int64

	// SetVersion records the version the aggregate now holds. Repositories
	// call it at reconstitution and after a successful write; domain code
	// does not.
	SetVersion(v int64)
}

Versioned is the optional interface an aggregate implements to get optimistic concurrency. A repository that finds it on a root writes the version into the WHERE clause and reports errors.Conflict when the write matches no row — which is what makes §3.3's CodeConflict reachable for a real write conflict rather than only for a uniqueness violation.

It is optional, and deliberately so: the version costs a column, and an aggregate whose writes are already serialised by something else — a single consumer, a lock, an append-only log — does not need one. A plain AggregateRoot does NOT satisfy this interface, so nothing acquires a version column it has no schema for.

The two methods are the two halves of one protocol. A driver READS the version to build the write, and SETS it after the write succeeds so a second Save in the same request checks against what it just wrote instead of a stale number. A driver that reads but never advances turns the second save of one request into a spurious conflict.

type VersionedRoot added in v0.2.0

type VersionedRoot[T ID] struct {
	AggregateRoot[T]
	// contains filtered or unexported fields
}

VersionedRoot is an AggregateRoot that carries a version. Embed it instead of AggregateRoot to opt an aggregate into optimistic concurrency:

type Invoice struct {
    domain.VersionedRoot[InvoiceID]
    Total Money
}

Everything AggregateRoot promises still holds — identity set once at construction, events accumulated by Raise and drained by PullEvents. The version is the only addition, and it is meaningful only to repositories.

The concurrency note on AggregateRoot applies unchanged: an aggregate is confined to one goroutine at a time. The version does not make it safe to share — it makes concurrent *writers* of the same row detect each other, which is a different problem.

func NewVersionedRoot added in v0.2.0

func NewVersionedRoot[T ID](id T) VersionedRoot[T]

NewVersionedRoot returns a versioned root at version 0 — never persisted. It is the constructor an aggregate's own constructor calls, exactly as NewAggregateRoot is for the unversioned case.

func ReconstituteVersionedRoot added in v0.2.0

func ReconstituteVersionedRoot[T ID](id T, version int64) VersionedRoot[T]

ReconstituteVersionedRoot returns a versioned root as it was stored: its identity and the version the row carried. It is the LOAD path, and the version it takes is the one a later Save checks against — reconstituting at 0 would turn every update into an insert.

It raises no events. Reconstitution replays what already happened; the facts were published when they occurred.

func (*VersionedRoot[T]) SetVersion added in v0.2.0

func (a *VersionedRoot[T]) SetVersion(v int64)

SetVersion records the version the aggregate now holds. It is called by repositories — at reconstitution, and after a write succeeds.

func (*VersionedRoot[T]) Version added in v0.2.0

func (a *VersionedRoot[T]) Version() int64

Version reports the version this aggregate was loaded at, 0 if it has never been persisted.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL