Language: English Español
Bounded Context: Configuration (Ums.Domain.Configuration)
Aggregate Root: FeatureFlag
Module: Ums.Domain.Configuration.FeatureFlag
Status: Production
The FeatureFlag aggregate controls runtime feature enablement scoped to a specific SystemSuite. It stores a technical flag code, flag type, target definitions, an optional dynamic criteria collection, rollout percentage, and state transitions across inactive, active, and archived states. Flags are never global — every flag belongs to exactly one SystemSuite.
SystemSuite.FeatureFlag is the aggregate root. It is an independent aggregate in BC-C (Configuration). It references SystemSuite (BC-B, Authorization) via the foreign key SystemSuiteId, but it is not a child of the SystemSuite aggregate. State transitions, criteria management, and evaluation behavior are coordinated through this aggregate.
| ID | Rule | Source |
|---|---|---|
| INV-FF1 | FlagCode is unique within (SystemSuiteId, FlagCode) — not globally |
ADR-0068 |
| INV-FF2 | Percentage flags require RolloutPercentage between 0 and 100 |
FS-08 |
| INV-FF3 | Archived flags cannot be re-activated, deactivated, or have criteria modified | FS-08 |
| INV-FF4 | SystemSuiteId is mandatory and immutable once the flag is created |
ADR-0068 |
| INV-FF5 | DateRange criteria require start date strictly before end date |
ADR-0068 |
| INV-FF6 | No duplicate (CriteriaType, Operator, Value) combination is allowed within the same flag |
ADR-0068 |
| Entity / VO | Type | Ownership |
|---|---|---|
FeatureFlagId |
Value Object | Aggregate identifier |
SystemSuiteId |
Value Object (ref BC-B) | Mandatory scope; immutable |
TenantId |
Value Object | Optional tenant scope |
FlagType |
Enumeration | BOOLEAN / VARIANT / PERCENTAGE |
FlagStatus |
Enumeration | Inactive, Active, Archived |
FeatureFlagCriteria |
Entity | Aggregate-owned evaluation criteria |
FlagEvaluationLog |
Entity | Aggregate-owned evaluation history |
| Event | Trigger |
|---|---|
FeatureFlagCreatedEvent(Guid FlagId, string FlagCode, Guid SystemSuiteId) |
New flag created |
FeatureFlagActivatedEvent |
Flag activated |
FeatureFlagDeactivatedEvent |
Flag deactivated |
FeatureFlagArchivedEvent |
Flag archived |
FeatureFlagStateChangedEvent |
State transition emitted |
FeatureFlagTargetingRulesUpdatedEvent(Guid FlagId, string FlagCode, Guid SystemSuiteId) |
Criteria collection replaced |
FeatureFlagCriteriaAddedEvent(Guid FlagId, string FlagCode, string CriteriaType) |
Single criterion added |
FeatureFlagCriteriaRemovedEvent(Guid FlagId, string FlagCode, Guid CriteriaId) |
Single criterion removed |
FlagEvaluatedEvent |
Runtime evaluation executed |
FeatureFlag (Aggregate Root)
├── Props: FeatureFlagProps
│ ├── Id: IdValueObject
│ ├── SystemSuiteId: IdValueObject [NEW — mandatory, immutable]
│ ├── TenantId: IdValueObject? [NEW — optional]
│ ├── FlagCode: string [unique within SystemSuiteId]
│ ├── FlagType: FlagType
│ ├── FlagTargets: string
│ ├── Status: FlagStatus
│ ├── RolloutPercentage?: int
│ └── Audit: AuditValueObject
└── Children
├── IReadOnlyCollection<FeatureFlagCriteria> [NEW]
└── IReadOnlyCollection<FlagEvaluationLog>
classDiagram
direction TB
class FeatureFlag {
<<AggregateRoot>>
+Guid Id
+Guid SystemSuiteId
+Guid TenantId
+string FlagCode
+FlagType FlagType
+string FlagTargets
+FlagStatus Status
+int RolloutPercentage
+List~FeatureFlagCriteria~ Criteria
+List~FlagEvaluationLog~ EvaluationLog
+Create(systemSuiteId, tenantId, flagCode, flagType, flagTargets, rolloutPercentage, actor)
+Activate(actor)
+Deactivate(actor)
+Archive(actor)
+AddCriteria(criteriaType, operator, value, actor)
+RemoveCriteria(criteriaId, actor)
+Evaluate(context, evaluator)
}
class FeatureFlagCriteria {
<<Entity>>
+Guid Id
+Guid FeatureFlagId
+string CriteriaType
+string Operator
+string Value
+DateTimeOffset CreatedAtUtc
}
class FlagEvaluationLog {
<<Entity>>
+Guid Id
+string Context
+bool Result
+DateTimeOffset EvaluatedAt
}
FeatureFlag "1" --> "0..*" FeatureFlagCriteria : owns
FeatureFlag "1" --> "0..*" FlagEvaluationLog : records
stateDiagram-v2
[*] --> Inactive : CreateFeatureFlag
Inactive --> Active : ActivateFlag
Active --> Inactive : DeactivateFlag
Active --> Archived : ArchiveFlag
Inactive --> Archived : ArchiveFlag
note right of Archived : Terminal state — create a new flag version
Allowed transitions summary:
| From | To | Command |
|---|---|---|
| — | Inactive |
CreateFeatureFlagCommand |
Inactive |
Active |
ActivateFlagCommand |
Active |
Inactive |
DeactivateFlagCommand |
Active |
Archived |
ArchiveFlagCommand |
Inactive |
Archived |
ArchiveFlagCommand |
Criteria may be added or removed while the flag is in Inactive or Active state. Archived flags reject all mutations.
sequenceDiagram
participant C as Caller
participant H as EvaluateFeatureFlagHandler
participant R as IFeatureFlagRepository
participant E as IFeatureFlagEvaluator
participant F as FeatureFlag (AR)
C->>H: EvaluateFeatureFlagCommand(flagId, evaluationContext)
H->>R: GetByIdAsync(flagId)
R-->>H: FeatureFlag (with Criteria)
H->>E: Evaluate(featureFlag, evaluationContext)
E->>F: Read Criteria collection
alt Criteria collection is empty
E-->>H: true (active for everyone)
else Criteria exist
E->>E: Group criteria by CriteriaType
loop Each CriteriaType group
E->>E: OR across criteria in group
alt Context does not provide value for this type
E-->>H: false (safe posture)
end
end
E->>E: AND across groups
E-->>H: bool result
end
H->>F: RecordEvaluation(evaluatedBy, context, result)
F->>F: Append FlagEvaluationLog
F->>F: Raise FlagEvaluatedEvent
H-->>C: EvaluationResult
erDiagram
SYSTEM_SUITE ||--o{ FEATURE_FLAG : "scopes"
FEATURE_FLAG ||--o{ FEATURE_FLAG_CRITERIA : "has"
FEATURE_FLAG ||--o{ FLAG_EVALUATION_LOG : "records"
FEATURE_FLAG {
uniqueidentifier Id PK
uniqueidentifier SystemSuiteId FK
uniqueidentifier TenantId
nvarchar FlagCode
int FlagTypeId
nvarchar FlagTargets
int StatusId
int RolloutPercentage
nvarchar CreatedBy
datetime2 CreatedAtUtc
nvarchar UpdatedBy
datetime2 UpdatedAtUtc
nvarchar AuditTimeSpan
}
FEATURE_FLAG_CRITERIA {
uniqueidentifier Id PK
uniqueidentifier FeatureFlagId FK
nvarchar CriteriaType
nvarchar Operator
nvarchar Value
datetime2 CreatedAtUtc
}
Database constraints:
FEATURE_FLAG: Unique constraint on (SystemSuiteId, FlagCode) replaces the previous global unique constraint on FlagCode.FEATURE_FLAG.SystemSuiteId: FK to ums_authorization.SystemSuites.Id.FEATURE_FLAG_CRITERIA: No unique constraint on CriteriaType alone — a flag may have multiple criteria of the same type. Duplicate (FeatureFlagId, CriteriaType, Operator, Value) is rejected by INV-FF6.FeatureFlag (BC-C, Configuration) references SystemSuite (BC-B, Authorization) through a Customer-Supplier relationship:
SystemSuite.Id as a stable external identifier.SystemSuiteId as a FK and validates its existence at creation time.SystemSuiteId is resolved once at creation; evaluation does not call BC-B.The port IFeatureFlagEvaluator is defined in the domain layer and implemented in the infrastructure layer, keeping evaluation logic free from external dependencies.
| Command | Description |
|---|---|
CreateFeatureFlagCommand(systemSuiteId, tenantId, flagCode, flagType, flagTargets, rolloutPercentage, actor) |
Creates a new flag scoped to a SystemSuite |
UpdateFeatureFlagCommand(flagId, flagTargets, rolloutPercentage, actor) |
Updates mutable properties of an existing flag |
ActivateFlagCommand(flagId, actor) |
Transitions flag from Inactive to Active |
DeactivateFlagCommand(flagId, actor) |
Transitions flag from Active to Inactive |
ArchiveFlagCommand(flagId, actor) |
Archives the flag (terminal) |
AddFeatureFlagCriteriaCommand(flagId, criteriaType, operator, value, actor) |
Adds a single evaluation criterion |
RemoveFeatureFlagCriteriaCommand(flagId, criteriaId, actor) |
Removes a single evaluation criterion |
EvaluateFeatureFlagCommand(flagId, evaluationContext, evaluatedBy) |
Evaluates the flag against a typed EvaluationContext |
| Query | Description |
|---|---|
GetFeatureFlagsBySystemSuiteQuery(systemSuiteId) |
Returns all flags scoped to a SystemSuite |
GetFeatureFlagCriteriaQuery(flagId) |
Returns all criteria for a specific flag |
GetFeatureFlagByIdQuery(flagId) |
Returns a single flag by identifier |
All tables reside in the ums_configuration schema.
| Table | Notes |
|---|---|
ums_configuration.FeatureFlags |
Stores aggregate root; SystemSuiteId FK references ums_authorization.SystemSuites; UK on (SystemSuiteId, FlagCode) |
ums_configuration.FeatureFlagCriteria |
Stores owned criteria entities; FK to FeatureFlags.Id with cascade delete |
ums_configuration.FlagEvaluationLogs |
Stores evaluation history; FK to FeatureFlags.Id |
Cross-schema FK: ums_configuration.FeatureFlags.SystemSuiteId → ums_authorization.SystemSuites.Id. This FK enforces referential integrity at the database level while keeping the aggregates in separate bounded contexts.
| Permission Code | Description |
|---|---|
FEATURE_FLAG_VIEW |
Read flags and their criteria for a given SystemSuite |
FEATURE_FLAG_CREATE |
Create a new flag within a SystemSuite |
FEATURE_FLAG_UPDATE |
Update mutable properties and criteria of an existing flag |
FEATURE_FLAG_TOGGLE |
Activate or deactivate a flag (change status) |
FEATURE_FLAG_ARCHIVE |
Archive a flag (terminal; irreversible) |
| Back to Configuration Index | FeatureFlagCriteria |