Developer Guide
Subdomain type: Supporting · Grounded in: User Stories IA-1 through IA-6
This page captures the design-time model of the Identity & Access context: the language, the aggregates, and the rules we committed to before writing code. It deliberately stays at the conceptual level — concrete routes, storage, and wire formats are deliberately left open, decisions for whoever builds this model.
The context answers three questions: who is this principal, which organizations do they belong to, and what may they do there. It is the upstream source of identity for the platform — it produces the identity token that every other context treats as a Published Language.
| Term | Meaning in this context |
|---|---|
| Account | A principal (human or system) that can authenticate. |
| Domain | An organization or tenant. The unit of multi-tenancy. |
| Membership | The link that places an Account inside a Domain with a specific Role. |
| Role | A ranked permission level used for authorization decisions. |
| Auth Strategy | How a Domain verifies identity: managed internally, or delegated to an external identity provider. |
We model two aggregate roots — Account and Domain. They are kept separate on purpose: an Account refers to a Domain only by its name (the shared identifier described in Strategic Design, so the two can evolve independently rather than through a hard reference.
classDiagram
class Account {
<<Aggregate Root>>
+name
+email
+credential
+secondFactorSeed
+Membership[] memberships
}
class Membership {
<<Value Object>>
+domainName
+Role role
+externalId
}
class Domain {
<<Aggregate Root>>
+name
+visibleFromOutside
+authStrategy
+Domain[] subdomains
+InviteSecrets inviteSecrets
+SSOConfig ssoConfig
}
class SSOConfig {
<<Value Object>>
+issuerUrl
+clientId
+clientSecret
}
class InviteSecrets {
<<Value Object>>
+perRoleSecret
}
class Role {
<<Value Object>>
+name
+rank
}
Account "1" *-- "many" Membership : holds
Membership "1" --> "1" Role : carries
Domain "1" *-- "0..1" SSOConfig : may hold
Domain "1" *-- "1" InviteSecrets : holdsThe consistency boundary around a principal and its memberships.
memberships inside the Account aggregate rather than as a separate association. The intent is that resolving a principal also resolves everything needed to make an authorization decision, without consulting another aggregate.This shape directly answers story IA-1 (one account recognized across organizations) and IA-5 (one sign-in, every membership carried together).
The consistency boundary around an organization and the rules under which principals join it.
authStrategy is the central policy switch. It is expected to drive how a principal proves identity and which value objects a Domain carries.inviteSecrets and ssoConfig hold confidential material. The model treats them as write-mostly: they participate in the Domain’s behaviour but are not part of its public representation.subdomains expresses an organizational hierarchy. We left the depth of that hierarchy open at this stage rather than fixing it to a single level.authStrategy and SSOConfig together answer story IA-3 (sign in through the company’s own identity provider); subdomains answers IA-4 (campus/department structure); visibleFromOutside answers IA-6 (a domain invisible to public discovery).
The smaller concepts living inside these aggregates are modelled as immutable, identity-less value objects:
Role is modelled as a ranked value object rather than a free-form label, so that authorization can be expressed as “at least this powerful” rather than an enumeration of cases. The comparison is the single rule the context exposes for permission checks:
role_satisfies(held, required):
return rank(held) >= rank(required)
ranking (high to low):
admin > business_admin > business_staff > standard_customerWhere in the request path this comparison is invoked — and against which required rank — is left open at the model level; the rule only states that it must hold. A ranked Role, rather than an enumerated list of cases, is what lets story IA-2 grant a member a role more powerful than an ordinary one from their first sign-in.
Two pieces of behaviour do not belong to a single entity, so we foresee them as domain services:
| Service | Responsibility |
|---|---|
| Identity token issuance | Assemble and sign the identity token, embedding the principal’s memberships so downstream contexts can authorize on their own. |
| Role invitation | Derive and validate the short, time-bounded codes that grant a Role within a Domain. |
The output of this context is a signed identity token whose contents — the principal, their memberships, roles, and domains — are the contract every other context conforms to. Designing this payload as a stable Published Language is what lets the other contexts authorize locally without calling back here.
sequenceDiagram
participant U as Client
participant A as Identity & Access
U->>A: authenticate (credentials)
Note over A: resolve the account and its memberships
Note over A: assemble identity token (memberships, roles, domains)
A-->>U: signed identity tokenThe interaction above is intentionally generic: the exact endpoints, token format, and federation handshake are design decisions deferred to the implementation. What matters at the domain level is that authentication yields a self-contained statement of identity and authority.