Skip to content

Migrate audience identity to application-generated UUIDv7 #35

Description

@notfoundsam

Context

CustomerAccount (first aggregate root, introduced with customer registration) currently uses database-generated identity: it's created with id = null, the autoincrement value is assigned inside SiteUserRepository::save() via CustomerAccount::assignId(), and only then can the aggregate emit CustomerAccountRegistrationRequested (hence the emitPendingEvents() indirection).

This is a known DDD anti-pattern (Evans, Vernon): the aggregate is not valid immediately after construction — it has no identity until persisted, which is why the registration event can't carry the accountId at construction time.

Problem

  • Aggregate has a "half-created" state (id = null) between register() and save().
  • Domain events can't carry the id before persist → forced the assignId() + emitPendingEvents() workaround in RegisterCustomerHandler.
  • Identity is owned by infrastructure, not the domain.

Proposed direction

Move to application-generated identity using UUIDv7 (time-ordered, so it keeps B-tree index locality like autoincrement, unlike random UUIDv4):

  • Domain generates CustomerAccountId before construction: CustomerAccount::register($id, $email, ...) — valid from birth, event carries id immediately, no emitPendingEvents.
  • Store as BINARY(16) in MySQL (not CHAR(36)) for index/space efficiency; convert via UUID_TO_BIN() / BIN_TO_UUID(). Library: symfony/uid.

Scope (this is an ADR-level change, not a single feature)

This touches the whole identity model from ADR-014, which currently defines admin_users / partner_users / customer_users with id BIGINT auto_increment and a polymorphic remember_tokens.user_id.

Doing UUID for customer_users only would break schema symmetry and complicate the polymorphic remember_tokens.user_id. So the migration should cover all three audience tables at once:

  • New ADR documenting application-generated UUIDv7 identity, superseding the relevant part of ADR-014
  • Migrate admin_users / partner_users / customer_users PKs to BINARY(16)
  • Update *_user_roles FKs
  • Update polymorphic remember_tokens.user_id
  • Update email_verification_codes.customer_user_id FK
  • CustomerAccountId value object + generator; remove assignId() / emitPendingEvents() workaround from CustomerAccount + RegisterCustomerHandler
  • Update login / remember-me / role-resolution paths that assume integer ids

Not doing now

Customer registration ships with BIGINT + assignId() as a deliberate trade-off: it keeps symmetry with admin/partner (ADR-014) and avoids expanding the registration feature into a project-wide identity rewrite. This issue tracks the dedicated initiative.

Reference: backend/src/SharedKernel/Domain/Security/Aggregate/CustomerAccount.php (register(), assignId(), emitPendingEvents()).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions