Skip to main content

Tenant Service — Domain Model

Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 02 DDD · 04 Events

1. Aggregates

AggregateRoot entityPrimary IDInvariants
TenantTenantTenantId (ten_)slug globally unique; status transitions constrained; activation is one-time
HierarchyNodeHierarchyNodeNodeId (nod_)Exactly one root node per tenant; parent must belong to same tenant; node_type from allowed list
RoleRoleRoleId (rol_)code unique within tenant; built-in roles immutable
RoleAssignmentRoleAssignmentRoleAssignmentId (roa_)One role per (user, node) pair; user must be member of node
UserProfileUserProfileProfileId (prf_)One profile per UserId within tenant; userId references identity-service
OrgMembershipOrgMembershipMembershipId (mem_)One membership per (user, node) pair; node must belong to same tenant
AccessPolicyAccessPolicyPolicyId (pol_)name unique within tenant; ABAC attribute conditions valid JSON Schema

2. State machines

2.1 Tenant status

2.2 HierarchyNode

2.3 OrgMembership

3. Entities (non-root)

EntityParent aggregatePurpose
TenantConfigurationTenantKV store of tenant settings (mfa_required, branding, etc.)
SubscriptionRecordTenantImmutable log of subscription tier/date changes
NodeAttributeHierarchyNodeTyped attributes (location, phone, capacity) for ABAC evaluation
PermissionRoleNamed permission string resource:action
PolicyConditionAccessPolicyJSON Schema condition on request context attributes

4. Value objects

Value objectShapeNotes
TenantIdbranded ULID ten_<ulid>
TenantSlug[a-z0-9-]{3,100} globally uniqueImmutable once set
NodeIdbranded ULID nod_<ulid>
NodeTypeenum organization | facility | department | ward | bed
TenantStatusenum pending | active | suspended | terminated
SubscriptionTierenum STARTER | PROFESSIONAL | ENTERPRISE
CountryCodeISO 3166-1 alpha-2
HierarchyProfileIdstring AFG_MOPH | UAE_DOH | PRIVATE_HOSPITALDrives node type constraints
RoleCodestring (tenant-scoped)Built-ins: TENANT_ADMIN, CLINICIAN, NURSE, PATIENT
PermissionString{resource}:{action}e.g., patient_chart:read

5. Domain events

All events use platform EventEnvelope; subject format tenant.{aggregate}.{event}.v1.

5.1 Tenant lifecycle

EventSubjectFired by
TenantCreatedtenant.tenant.created.v1CreateTenant
TenantActivatedtenant.tenant.activated.v1ActivateTenant saga
TenantSuspendedtenant.tenant.suspended.v1SuspendTenant
TenantReactivatedtenant.tenant.reactivated.v1ReactivateTenant
TenantTerminatedtenant.tenant.terminated.v1TerminateTenant
TenantUpdatedtenant.tenant.updated.v1UpdateTenant
TenantConfigChangedtenant.config.changed.v1SetConfig
SubscriptionUpdatedtenant.subscription.updated.v1UpdateSubscription
SubscriptionExpiredtenant.subscription.expired.v1Cron job

5.2 Hierarchy

EventSubjectFired by
HierarchyNodeCreatedtenant.hierarchy_node.created.v1CreateNode
HierarchyNodeUpdatedtenant.hierarchy_node.updated.v1UpdateNode
HierarchyNodeArchivedtenant.hierarchy_node.archived.v1ArchiveNode

5.3 Membership and RBAC

EventSubjectFired by
UserProfileCreatedtenant.user_profile.created.v1CreateUserProfile / JIT from identity.user.registered.v1
OrgMembershipCreatedtenant.org_membership.created.v1AssignMembership
OrgMembershipRemovedtenant.org_membership.removed.v1RemoveMembership
RoleAssignedtenant.role_assignment.created.v1AssignRole
RoleRevokedtenant.role_assignment.removed.v1RevokeRole
UserInvitedtenant.user.invited.v1InviteUser

6. Ubiquitous language

TermMeaning
TenantThe legal entity (hospital system, clinic network) that signs the contract
HierarchyNodeAn org unit in the tenant's tree (organization, facility, department, ward, bed)
HierarchyProfileA template defining allowed node types and parent constraints for a jurisdiction
RootNodeThe top-level organization node auto-created on tenant activation
RoleA named set of permissions scoped to a tenant; can be assigned at a node
RoleAssignmentA binding of (User, Role, Node)
OrgMembershipA user's association to a specific node (may hold multiple)
UserProfileClinical identity (name, specialty, credentials) linked to an identity-service User
evaluate()The RBAC/ABAC decision endpoint that takes a context and returns allow/deny
AccessPolicyAn ABAC policy with named conditions evaluated against request context attributes
Subscription tierCommercial entitlement level (STARTER / PROFESSIONAL / ENTERPRISE)

7. Aggregate relationships

8. Invariants

#Invariant
INV-01tenant.slug must be globally unique (enforced by UNIQUE constraint)
INV-02A tenant can only be activated once; pending → active is a one-way gate
INV-03Exactly one root_node_id per active tenant; created during activation saga
INV-04terminated status is terminal; no lifecycle transitions out of it
INV-05A HierarchyNode parent must belong to the same tenant
INV-06Built-in roles (TENANT_ADMIN, CLINICIAN, NURSE, PATIENT) cannot be deleted
INV-07RoleAssignment requires the user to have an OrgMembership at the same or ancestor node
INV-08Trial subscriptions must have subscription_end set
INV-09Tenant configuration keys must be in the platform allow-list
INV-10Cross-tenant data access is prohibited; all queries scoped by tenant_id

9. Open questions

  • Should ABAC AccessPolicy evaluation cache invalidate per policy change or per affected user set?
  • SCIM 2.0 endpoint surface — tenant-service owns the SCIM provisioning or is it a bridge in identity-service?