Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

id

Every entity an EsRepo creates gets its id from the repository’s id source: the repo itself, or the caller. The source is also the type parameter of the builder that IntoEvents receives (see New Entity), so what a NewEntity can do with the id is checked by the compiler.

The id attribute

The id column is configured like any other column, by the repo-level id attribute. id = "CustomId" names the type and keeps every default, and the list form spells the options out:

#[es_repo(entity = "Partner", id(ty = "PartnerId", source = caller, scope), columns(...))]
optionmeaning
ty = "..."the id type; defaults to {Entity}Id
source = repothe repository generates the id (the default)
generator = "path"the repository generates the id with path
source = callerthe caller puts the id in the NewEntity
scope, scope(variant = "...")the id is the repo’s scope column, see Scoped repositories

It differs from an ordinary column in three ways: ty is optional, find_by and list_by are always on, and only the id takes a source. The id is never declared inside columns(...).

Source: the repo (the default)

By default the repository generates the id: <IdType>::new(), or the function named by generator = "...". The NewEntity declares type IdSource = FromRepo and reads the id from the builder:

#[derive(EsRepo)]
#[es_repo(entity = "User")] // same as `id(source = repo)`
pub struct Users { pool: sqlx::PgPool }

Because a collision of a freshly generated id is a fault rather than something a caller can act on, the primary key is not a case of {Entity}ConstraintViolation for such a repo. When nothing else can reject (no unique column, no check constraint, nothing nested) the create functions return the plain Result<Entity, RepoFault>.

To draw the id from somewhere else, name an async function taking the repo and the operation:

#[derive(EsRepo)]
#[es_repo(entity = "PublicId", id(generator = "Self::next_id"))]
pub struct PublicIds { pool: sqlx::PgPool }

impl PublicIds {
    async fn next_id<OP: AtomicOperation + ?Sized>(
        &self,
        op: &mut OP,
    ) -> Result<PublicIdId, RepoFault> {
        // e.g. `SELECT nextval(..)`
    }
}

Source: the caller

An entity with a deterministic id (derived from another aggregate, a well-known constant) takes it from the caller with id(source = caller). Its NewEntity declares type IdSource = FromCaller, carries the id and passes it to initial_events:

#[derive(EsRepo)]
#[es_repo(entity = "Customer", id(source = caller))]
pub struct Customers { pool: sqlx::PgPool }

An id from the caller is a uniqueness claim, so Pkey stays a case of the constraint violation. A repo that names its id type explicitly writes id(ty = "CustomId", source = caller). generator needs source = repo.

The repository checks that the id the NewEntity handed to initial_events is the one it inserts under; if they differ the create fails with Fatal(Invariant).

For entities that may already exist, the id is one of the keys of create_or_find_in_op.

A mismatch does not compile

The repo’s source and the NewEntity’s type IdSource must agree: a FromCaller NewEntity used with a default (repo-sourced) repo, and the other way round, are both compile errors.

Hydration checks the id

After TryFromEvents rebuilds an entity, its id field is compared with the id its events are stored under. A mismatch (an Initialized { id } payload that disagrees with the stream) is Fatal(CorruptState).