Bounded Context: Approvals
Aggregate Root: DocumentType
Module: Ums.Domain.Approvals.DocumentType
Status: Production
The DocumentType aggregate governs the classifications, rules, and policy schemas for user-uploaded documents (e.g. Passports, Certification documents). It declares critical thresholds (Criticity), binds recurring notification intervals (NotificationRule entities), and configures proactive security enforcement actions (EnforcementPolicy entity) to run automatically when a mandatory document expires or is deleted. NotificationRule specifies how many days before expiration a user must be alerted, and which communication channels are authorized.
- Register and classify corporate verification documents.
- Set criticity guidelines (Low, Medium, High, Critical).
- Maintain dynamic notification intervals to pre-alert users before a document expires.
- Define automatic enforcement blocks (e.g. Blocking access, restricting profiles) when critical compliance items fail.
- Map expiration warning rules to specific document categories and define alert transmission channels.
DocumentType is the aggregate root. Defining enforcement actions must go through it to enforce invariants. NotificationRule is an independent Aggregate Root in the Approvals BC. DocumentType may reference NotificationRule entities by ID but does not own their lifecycle.
- Every
DocumentTypemust possess a uniqueCodewithin itsTenantIdnamespace. - DaysBefore Uniqueness (INV-DT2): The alert thresholds (
DaysBefore) in the ownedNotificationRulesmust be unique across the rules list. - Critical Mandates (INV-DT1): If a
DocumentTypeis set toCritical, it must have exactly one activeEnforcementPolicydefined to secure system compliance. - Single Policy (INV-DT3): Only one active
EnforcementPolicyis permitted perDocumentType. - Criticity Matching (INV-DT4): Non-critical/high document types cannot apply
BlockUserorRestrictProfileenforcement actions. - Referenced
NotificationRuleaggregates must exist and be active before being associated with aDocumentType.
| Entity / VO | Type | Ownership |
|---|---|---|
DocumentTypeId |
Value Object | Guid-based aggregate root identifier |
DocumentCriticity |
Enum | LOW · MEDIUM · HIGH · CRITICAL |
NotificationRule |
Entity | Related Aggregate Root (external reference by ID) |
NotificationRuleId |
Value Object | Entity unique identifier |
NotificationChannel |
Enum | EMAIL · SMS · IN_APP · WEB_PUSH |
EnforcementPolicy |
Entity | Owned child detailing grace periods and blocks |
AuditValueObject |
Value Object | Tracks creation and modification metadata |
Code |
Value Object | Alpha-numeric camelCase notification type identifier |
| Event | Trigger |
|---|---|
DocumentTypeRegisteredEvent |
A new document category is successfully registered |
NotificationRuleConfiguredEvent |
An expiration pre-alert rule is configured |
NotificationRuleRemovedEvent |
A pre-alert rule is removed |
EnforcementPolicyDefinedEvent |
An enforcement block is defined |
EnforcementPolicyUpdatedEvent |
The enforcement parameters are updated |
| Command | Description |
|---|---|
CreateDocumentTypeCommand |
Register a new document type with default parameters |
ConfigureNotificationRuleCommand |
Configure a new alert threshold and channels |
RemoveNotificationRuleCommand |
Remove an existing notification pre-alert rule |
DefineEnforcementPolicyCommand |
Add a blocking or profile downgrade enforcement policy |
UpdateEnforcementPolicyCommand |
Modify the actions or grace periods of a policy |
IDocumentTypeRepository— Persists classification schemas.- Scoped strictly by
TenantIdto prevent multi-tenant configuration cross-overs.
DocumentType (Aggregate Root)
├── Props: DocumentTypeProps
│ ├── Id: DocumentTypeId
│ ├── TenantId: TenantId
│ ├── Code: Code
│ ├── Name: Name
│ ├── Description: Description
│ ├── Criticity: DocumentCriticity
│ └── Audit: AuditValueObject
├── Children
│ └── IReadOnlyCollection<NotificationRule>
│ └── Props: NotificationRuleProps
│ ├── Id: NotificationRuleId
│ ├── DaysBefore: int
│ ├── Channels: NotificationChannel[]
│ ├── Code: Code
│ └── Description: Description
└── Child (Nullable)
└── EnforcementPolicy
classDiagram
direction LR
class DocumentType {
+Guid Id
+Guid TenantId
+Code Code
+Name Name
+Description Description
+DocumentCriticity Criticity
+List~NotificationRule~ NotificationRules
+EnforcementPolicy EnforcementPolicy
+Create()
+ConfigureNotificationRule()
+RemoveNotificationRule()
+DefineEnforcementPolicy()
}
class DocumentCriticity {
<<enumeration>>
LOW
MEDIUM
HIGH
CRITICAL
}
class NotificationRule {
+Guid Id
+int DaysBefore
+NotificationChannel[] Channels
+Code Code
+Description Description
+Create()
}
class NotificationChannel {
<<enumeration>>
EMAIL
SMS
IN_APP
WEB_PUSH
}
class EnforcementPolicy {
+Guid Id
+AccessEnforcementAction ActionOnExpiration
+int? GracePeriodDays
}
DocumentType "1" *-- "1" DocumentCriticity
DocumentType "1" *-- "0..*" NotificationRule
NotificationRule "1" *-- "1..*" NotificationChannel
DocumentType "1" *-- "0..1" EnforcementPolicy
sequenceDiagram
participant C as TenantAdmin
participant H as DefinePolicyHandler
participant R as IDocumentTypeRepository
participant D as DocumentType (AR)
C->>H: DefineEnforcementPolicyCommand(docTypeId, action, graceDays)
H->>R: GetById(docTypeId)
R-->>H: DocumentType (AR)
H->>D: DefineEnforcementPolicy(action, graceDays, actorId)
D->>D: Guard INV-DT4 (non-critical restrictions)
D->>D: Guard INV-DT3 (single policy limit)
D->>D: Raise EnforcementPolicyDefinedEvent
H->>R: Save(docType)
R-->>H: ok
H-->>C: ok
erDiagram
TENANT ||--o{ DOCUMENT_TYPE : "configures"
DOCUMENT_TYPE ||--o{ NOTIFICATION_RULE : "defines"
DOCUMENT_TYPE ||--o| ENFORCEMENT_POLICY : "restricts"
DOCUMENT_TYPE {
uniqueidentifier DocumentTypeId PK
uniqueidentifier TenantId FK
nvarchar Code "Unique per TenantId"
nvarchar Name
nvarchar Description
nvarchar Criticity "LOW-MEDIUM-HIGH-CRITICAL"
datetime2 UpdatedAt
uniqueidentifier UpdatedBy
}
NOTIFICATION_RULE {
uniqueidentifier RuleId PK
uniqueidentifier DocumentTypeId FK
int DaysBefore "Unique per DocumentTypeId"
nvarchar ChannelsJson "Serialized channels array"
nvarchar Code
nvarchar Description
}
ENFORCEMENT_POLICY {
uniqueidentifier PolicyId PK
uniqueidentifier DocumentTypeId FK
nvarchar ActionOnExpiration "BlockUser-RestrictProfile-Notify"
int GracePeriodDays "Nullable"
}
- Classified schemas are partitioned strictly by
TenantId. All verification routing queries enforce isolation limits. NotificationRuleis an independent Aggregate Root with its ownTenantIdscope. It is not owned byDocumentType.
- Upstream: Inherits context rules from
Identity(validating tenant registers). - Downstream: Consulted by
UserDocumentto check alert thresholds, andAccessEnforcementPolicyduring compliance check passes. Alerts triggered are processed by background compliance runners to notify users from theIdentitycontext.
CreateDocumentTypeCommand-> Inputs:TenantId, Code, Name, Description, Criticity-> Returns:GuidConfigureNotificationRuleCommand-> Inputs:DocumentTypeId, DaysBefore, Channels, Code, Description-> Returns:voidRemoveNotificationRuleCommand-> Inputs:DocumentTypeId, RuleId-> Returns:voidDefineEnforcementPolicyCommand-> Inputs:DocumentTypeId, Action, GracePeriodDays?-> Returns:void
- Index: Unique index on
TenantId, Code. - Index: Composite index on
DocumentTypeId, DaysBeforeto secure threshold uniqueness. - Transaction: Child updates (policies and pre-alert rule entries) are stored atomically within the parent
DOCUMENT_TYPEdatabase transaction.
- Adjusting critical classification or rules: Restricted strictly to
Tenant:Adminroles. Rule configurations are inherited from the parentDocumentType. - Compliance: Altering enforcement rules represents a high security impact and triggers high-severity audit logging.
EnforcementPolicyis modelled as an owned child entity insideDocumentTypeto protect domain boundaries.NotificationRuleis an independent Aggregate Root referenced by ID, enabling reuse across multiple document types.- Storing allowed communication channels as a serialized array (
ChannelsJson) within a single database column guarantees database flexibility without massive schema join overheads.