Skip to main content

Config Service — Application Logic

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


1. Use Cases — Commands

CommandUse Case ClassDescription
CreateFeatureDefinitionCommandCreateFeatureDefinitionUseCaseCreate a feature within a module
UpdateFeatureDefinitionCommandUpdateFeatureDefinitionUseCaseUpdate feature actions or data scope
CreateRoleDefinitionCommandCreateRoleDefinitionUseCaseCreate a new role (system or tenant)
UpdateRoleDefinitionCommandUpdateRoleDefinitionUseCaseUpdate role display name or flags
AddRoleInheritanceCommandAddRoleInheritanceUseCaseAdd a parent-child role inheritance edge
SetRoleFeatureGrantCommandSetRoleFeatureGrantUseCaseSet granted/denied actions for role+feature
CreateUserNodeOverrideCommandCreateUserNodeOverrideUseCaseCreate explicit allow/deny for user at node
DeleteUserNodeOverrideCommandDeleteUserNodeOverrideUseCaseSoft-delete a user node override
CreateUIDefinitionCommandCreateUIDefinitionUseCaseRegister a UI element definition
UpdateUIDefinitionCommandUpdateUIDefinitionUseCaseUpdate a UI element's action binding
SetUIVisibilityRuleCommandSetUIVisibilityRuleUseCaseSet role/user visibility rule for an element
SetDesignTokenCommandSetDesignTokenUseCaseSet a design token at a scope

2. Use Cases — Queries

QueryUse Case ClassDescription
ResolveConfigQueryResolveConfigUseCaseExecute 9-step resolution pipeline
ResolveUIConfigQueryResolveUIConfigUseCaseReturn UI visibility tree for a feature
ResolveDesignTokensQueryResolveDesignTokensUseCaseReturn merged token map for module/locale
GetUserOverridesQueryGetUserOverridesUseCaseList all overrides for a user
GetRoleFeatureGrantsQueryGetRoleFeatureGrantsUseCaseList grants for a role

3. Ports (Interfaces)

PortInterfaceAdapters
ConfigNodeRepositoryConfigNodeRepository portPostgresConfigNodeAdapter
FeatureDefinitionRepositoryFeatureDefinitionRepository portPostgresFeatureDefinitionAdapter
RoleRepositoryRoleRepository portPostgresRoleAdapter
UserNodeOverrideRepositoryUserNodeOverrideRepository portPostgresUserOverrideAdapter
UIDefinitionRepositoryUIDefinitionRepository portPostgresUIDefinitionAdapter
DesignTokenRepositoryDesignTokenRepository portPostgresDesignTokenAdapter
HierarchyClientHierarchyClient portHttpHierarchyAdapter → facility-service
LicenseClientLicenseClient portHttpLicenseAdapter → platform-admin-service
FeatureFlagClientFeatureFlagClient portHttpFeatureFlagAdapter → platform-admin-service
ABACClientABACClient portHttpABACAdapter → access-policy
IdentityClientIdentityClient portHttpIdentityAdapter → identity-service
CachePortCachePort portRedisAdapter
EventPublisherEventPublisher portNatsEventPublisher
AuditClientAuditClient portNatsAuditPublisher → audit-service

4. Resolution Pipeline — Sequence


5. Cache Invalidation Flow

When a mutation event is published (e.g. config.feature.updated.v1), the service subscribes via NATS and evicts relevant Redis keys:

Mutation eventCache keys evicted
config.feature.*cfg:{tenantId}:*:*:{moduleKey}:{featureKey}
config.role.*cfg:roles:{tenantId}:{roleKey}:expanded + all user resolution caches
config.role_grant.*cfg:roles:{tenantId}:{roleKey}:expanded
config.ui_definition.*cfg:ui:{tenantId}:*:{featureKey}
config.user_override.*cfg:{tenantId}:{userId}:* + cfg:ui:{tenantId}:{userId}:*
config.design_token.*cfg:tokens:{tenantId}:*:{moduleKey}

After eviction, config.config.cache_busted.v1 is published for observability.


6. Outbox Pattern

All mutation use cases persist the domain event to the outbox table in the same database transaction before committing. A background relay worker reads the outbox and publishes to NATS JetStream. This guarantees at-least-once delivery and prevents event loss on crash.


7. Inbox / Deduplication

For events the config-service consumes from NATS (e.g. tenant mutation events from tenant-service), an inbox table deduplicates by (source, eventId). Duplicate events are acknowledged but not processed.


8. Error Handling

ScenarioBehaviour
facility-service unavailableReturn { effect: "deny", reason: "DEPENDENCY_UNAVAILABLE" } with 503; do NOT fallback to an allow
access-policy unavailableReturn { effect: "deny", reason: "DEPENDENCY_UNAVAILABLE" } with 503; fail closed
Redis unavailableBypass cache; run full pipeline; log warning; alert if > 30 s
Resolution timeout > 500 msReturn 504 RESOLUTION_TIMEOUT; circuit breaker trips after 5 consecutive timeouts
Cycle detected at role definitionReject with 409 CIRCULAR_ROLE_INHERITANCE
Cycle detected at config node creationReject with 409 CONFIG_CIRCULAR_REFERENCE
Cross-tenant nodeIdReturn { effect: "deny", reason: "CROSS_TENANT" } in 200 body

9. Business Rules Enforced in Application Layer

RuleEnforcement
tenantId from JWT only — never from body/query in user pathJWT extraction middleware; DTO strips tenant field
ExplicitDeny is finalResolution use case short-circuits after Step 7 deny; no further evaluation
isAbstract role cannot be assignedSetRoleAssignmentUseCase validates isAbstract = false
isSystem role only by SUPER_ADMINGuard in CreateRoleDefinitionUseCase
justification required for user overridesDTO validation; empty string → 422
BFS depth max 10BFS loop tracks depth counter; terminates + throws at 10