Skip to content

Bounded Contexts & Domain Model

Validated against PRD v1.0
Principle: Each bounded context owns its data. Contexts communicate through well-defined contracts, never through shared tables or internal imports.


1. Context Map

flowchart TB
    CE[Config Engine<br/>templates, thresholds, rules]
    WF[Workflow Orchestrator<br/>Onboarding Process]
    CM[Case Management<br/>Onboarding Cases<br/>Screening Cases]
    NS[Name Screening]
    RR[Risk Rating]
    NA[Network Analysis]
    NT[Notification Engine]

    CE -->|reads config| WF
    WF -->|creates| CM
    WF -->|invokes| NS
    WF -->|invokes| RR
    WF -->|invokes| NA
    WF -->|triggers| NT

    subgraph Decision [Decision Modules]
        NS
        RR
        NA
        NT
    end

    subgraph CrossCutting [Cross-Cutting]
        AUDIT[Audit Service<br/>ALL contexts write]
        IAM[Identity & Access RBAC<br/>ALL contexts check]
    end

    WF -.-> AUDIT
    CM -.-> AUDIT
    NS -.-> AUDIT
    RR -.-> AUDIT
    NA -.-> AUDIT

Context Relationships

Upstream Downstream Relationship Pattern
Configuration Engine Workflow Orchestrator Conformist — Workflow reads config, doesn't change it Synchronous API call at workflow start
Configuration Engine All Decision Modules Conformist — Modules read thresholds/rules Synchronous API call at module invocation
Workflow Orchestrator Name Screening Customer/Supplier — Workflow invokes, Screening responds Async with callback
Workflow Orchestrator Risk Rating Customer/Supplier Async with callback
Workflow Orchestrator Network Analysis Customer/Supplier Async with callback
Workflow Orchestrator Case Management Partnership — close collaboration Sync + events
Workflow Orchestrator Notification Customer/Supplier — fire and forget Events
All Contexts Audit Service Conformist — all write, none read Events / append-only log

2. Bounded Context Details

2.1 Configuration Engine Context

Responsibility: Manage workflow templates, thresholds, rules, approval matrices, and document requirements. All config is versioned and immutable.

Concept Type Description
WorkflowTemplate Aggregate Root Defines states, transitions, conditions, tasks, SLA timers for a workflow type
ThresholdConfig Entity Risk score boundaries (LOW/MEDIUM/HIGH)
ApprovalMatrix Entity Who can approve what type of decision
DocumentRequirementRule Entity Which documents are mandatory/optional per customer type + jurisdiction
EscalationRule Entity Routing rules for escalation (who gets what at which level)
ConfigVersion Entity Immutable snapshot of all config at a point in time
VersionNumber Value Object Semantic version of config
EffectiveDate Value Object When this config version became active

Domain Events:

  • ConfigVersionCreated
  • ConfigVersionPromoted (DRAFT → TEST → PROD)
  • ConfigVersionRolledBack

2.2 Workflow Orchestration Context

Responsibility: Drive process state machines. Invoke decision modules. Manage human tasks. Enforce SLAs.

Concept Type Description
WorkflowInstance Aggregate Root Running instance of a workflow. Tracks current state, config version, and execution context.
WorkflowState Value Object Current state in the state machine
WorkflowContext Value Object Customer ID, case ID, all data captured so far
Task Entity A work item — either system (invoke module) or human (review, approve)
TaskAssignment Value Object Who the task is assigned to
SLATimer Entity Deadline for a task with breach escalation rules
RetryPolicy Value Object Max retries, backoff strategy for module invocations
ModuleInvocation Entity Record of a decision module being called — request, response, timing

Domain Events:

  • WorkflowStarted
  • StateTransitioned
  • TaskCreated / TaskAssigned / TaskCompleted
  • SLAWarning / SLABreached
  • ModuleInvoked / ModuleCompleted / ModuleFailed

2.3 Customer Onboarding Context

Responsibility: Manage the customer onboarding lifecycle — intake, classification, data capture, document management, identity validation, UBO capture, and final decision.

Concept Type Description
Customer Aggregate Root The party being onboarded. Can be an individual or organization.
Individual Entity Natural person details (name, DOB, nationality, PEP flags)
LegalEntity Entity Registered organization details (name, registration number, incorporation country)
Address Value Object Physical or registered address
OwnershipRelationship Entity Parent → child ownership with percentage and control type
UBO Entity Ultimate Beneficial Owner — derived from ownership chain traversal
Document Entity Uploaded evidence (passport, incorporation cert, utility bill)
DocumentValidation Value Object Validation status (PENDING, VERIFIED, EXPIRED, REJECTED)
ApplicationIntake Entity The initial submission from the RM
Classification Value Object Customer archetype (Retail, SME, Corporate, etc.)
OnboardingDecision Entity Final decision: APPROVED, APPROVED_WITH_RESTRICTIONS, REJECTED, etc.

Domain Events:

  • CustomerCreated
  • DocumentUploaded / DocumentValidated
  • IdentityVerified / IdentityMismatch
  • UBOIdentified
  • OnboardingDecisionMade

2.4 Name Screening Context

Responsibility: Match customer names against watchlists (sanctions, PEP, internal). Return match results and support analyst adjudication.

Concept Type Description
ScreeningRequest Aggregate Root A screening invocation for a specific subject
ScreeningResult Entity Match outcome: NO_MATCH, POTENTIAL_MATCH, CONFIRMED_MATCH
MatchDetail Value Object Matched list entry, similarity score, matched fields
Watchlist Entity External list metadata (source, version, last updated)
Adjudication Entity Analyst decision: CLEAR, CONFIRM, REQUEST_INFO
ScreeningSubject Value Object Name, aliases, DOB, nationality, identifiers — the entity being screened
MatchAlgorithm Value Object EXACT or FUZZY, with configurable threshold

Domain Events:

  • ScreeningRequested
  • MatchFound / NoMatchFound
  • AdjudicationMade

2.5 Customer Risk Rating Context

Responsibility: Calculate risk scores using configurable rules. Produce explainable risk bands.

Concept Type Description
RiskAssessment Aggregate Root A risk evaluation for a customer
RiskFactor Entity A single contributing factor (geography, customer type, PEP, product, etc.)
RiskScore Value Object Numeric score
RiskBand Value Object LOW, MEDIUM, or HIGH
RiskMethodology Value Object Config version of the rules used
FactorWeight Value Object Weight assigned to a factor

Domain Events:

  • RiskAssessmentRequested
  • RiskAssessmentCompleted

2.6 Case Management Context

Responsibility: Manage investigation cases — lifecycle, assignment, evidence, notes, escalation, decisions.

Concept Type Description
Case Aggregate Root An investigation container. Links tasks, evidence, decisions.
CaseType Value Object ONBOARDING_REVIEW, SANCTIONS_INVESTIGATION, EDD_REVIEW, etc.
CaseState Value Object CREATED → ASSIGNED → IN_PROGRESS → PENDING_REVIEW → ESCALATED → DECIDED → CLOSED
CaseNote Entity Append-only note with author and timestamp
Evidence Entity Document or data attached to the case
Decision Entity A formal decision with rationale, actor, and timestamp
CaseAssignment Value Object Who is currently assigned

Domain Events:

  • CaseCreated / CaseAssigned / CaseEscalated
  • NoteAdded
  • EvidenceAttached
  • DecisionMade
  • CaseClosed

2.7 Network Analysis Context

Responsibility: Map ownership structures and discover linked entities.

Concept Type Description
EntityGraph Aggregate Root The graph of entities related to a customer
GraphNode Value Object An entity in the graph (customer, UBO, intermediate company)
GraphEdge Value Object A relationship with type (OWNS, DIRECTS, SHARES_ADDRESS)
LinkedEntity Entity An entity discovered through shared attributes
OwnershipChain Entity A traversal path from customer to UBO
ControlType Value Object DIRECT, INDIRECT, BENEFICIAL

Domain Events:

  • GraphBuilt
  • LinkedEntityDiscovered

2.8 Notification Context

Responsibility: Send in-platform and external notifications.

Concept Type Description
Notification Aggregate Root A message to a user
NotificationType Value Object TASK_ASSIGNED, SLA_WARNING, ESCALATION, STATUS_CHANGE
DeliveryChannel Value Object IN_PLATFORM, EMAIL (P1)
NotificationStatus Value Object UNREAD, READ, ACKNOWLEDGED

Domain Events:

  • NotificationSent
  • NotificationAcknowledged

2.9 Audit & Governance Context

Responsibility: Record every system action immutably. Support deterministic replay.

Concept Type Description
AuditEvent Aggregate Root Immutable record of one action
EventType Value Object STATE_TRANSITION, MODULE_INVOCATION, DECISION, CONFIG_CHANGE, DATA_ACCESS
Actor Value Object Who performed the action (user ID or SYSTEM)
CorrelationId Value Object Links events across contexts for one request
AuditTrail Entity The complete ordered sequence of events for a case or customer
ReplayRequest Entity An auditor's request to reconstruct a decision

Domain Events:

  • AuditEventRecorded (this is the fundamental event — everything else becomes an audit event)

2.10 Identity & Access Context

Responsibility: Authentication, authorization, role management.

Concept Type Description
User Aggregate Root A platform user
Role Entity RM, KYC_ANALYST, SANCTIONS_ANALYST, EDD_ANALYST, FCC_REVIEWER, SUPERVISOR, AUDITOR, ADMIN
Permission Value Object What a role can do (CREATE_CASE, APPROVE_DECISION, VIEW_CUSTOMER, etc.)
Session Entity Active user session with expiry

Domain Events:

  • UserAuthenticated / AuthenticationFailed
  • RoleAssigned
  • AccessDenied

3. Canonical Domain Model

Shared entities across all contexts:

Customer (Aggregate Root)
├── customerId: UUID
├── customerType: INDIVIDUAL | LEGAL_ENTITY
├── individual: Individual?
├── legalEntity: LegalEntity?
├── addresses: List<Address>
├── riskStatus: RiskBand?
├── lifecycleStatus: ACTIVE | SUSPENDED | CLOSED
├── createdAt: Instant
└── updatedAt: Instant

Individual (Entity)
├── individualId: UUID
├── firstName: String
├── lastName: String
├── aliases: List<String>
├── dateOfBirth: LocalDate
├── nationality: Country
├── residenceCountry: Country
├── taxResidency: Country?
├── occupation: String?
├── pepFlag: Boolean
├── pepLevel: PEP_LEVEL?
├── sourceOfWealth: String?
└── sourceOfFunds: String?

LegalEntity (Entity)
├── entityId: UUID
├── legalName: String
├── tradeName: String?
├── registrationNumber: String
├── incorporationCountry: Country
├── legalForm: LEGAL_FORM
├── industryCode: String?
└── incorporationDate: LocalDate?

OwnershipRelationship (Entity)
├── relationshipId: UUID
├── parentEntityId: UUID
├── childEntityId: UUID
├── ownershipPercentage: BigDecimal
├── controlType: DIRECT | INDIRECT | BENEFICIAL
├── effectiveFrom: LocalDate
├── effectiveTo: LocalDate?
└── confidenceScore: Double

Document (Entity)
├── documentId: UUID
├── documentType: PASSPORT | INCORPORATION_CERT | SHAREHOLDER_REGISTER | DIRECTOR_ID | UBO_DECLARATION | PROOF_ADDRESS | SOURCE_WEALTH
├── issueDate: LocalDate?
├── expiryDate: LocalDate?
├── validationStatus: PENDING | VERIFIED | EXPIRED | REJECTED
├── storageReference: String
└── uploadedAt: Instant

ScreeningResult (Entity)
├── resultId: UUID
├── screeningType: SANCTIONS | PEP | INTERNAL
├── matchStatus: NO_MATCH | POTENTIAL_MATCH | CONFIRMED_MATCH
├── matchedEntry: String?
├── matchScore: Double?
├── matchFields: List<String>
├── adjudication: CLEAR | CONFIRM | REQUEST_INFO?
├── adjudicationRationale: String?
└── createdAt: Instant

RiskAssessment (Entity)
├── assessmentId: UUID
├── methodologyVersion: String
├── riskScore: Int
├── riskBand: LOW | MEDIUM | HIGH
├── factors: List<RiskFactor>
├── createdAt: Instant
└── validUntil: Instant?

RiskFactor (Value Object)
├── factorName: String
├── factorScore: Int
└── rationale: String

Case (Aggregate Root)
├── caseId: UUID
├── caseType: ONBOARDING_REVIEW | SANCTIONS_INVESTIGATION | EDD_REVIEW | QA_REVIEW
├── state: CaseState
├── priority: LOW | MEDIUM | HIGH | CRITICAL
├── customerId: UUID
├── workflowInstanceId: UUID?
├── assignedTo: UUID?
├── escalationLevel: Int
├── createdAt: Instant
├── slaDeadline: Instant?
├── resolvedAt: Instant?
└── resolution: DECISION?

Decision (Entity)
├── decisionId: UUID
├── decisionType: APPROVED | APPROVED_WITH_RESTRICTIONS | REJECTED | ESCALATED | CLEARED | CONFIRMED | PROHIBITED
├── actorType: USER | SYSTEM
├── actorId: UUID?
├── rationale: String
├── evidenceRefs: List<UUID>
├── configVersion: String
├── overrideJustification: String?
└── timestamp: Instant

WorkflowInstance (Aggregate Root)
├── workflowInstanceId: UUID
├── workflowDefinitionId: String
├── definitionVersion: String
├── currentState: String
├── context: JsonNode (customerId, caseId, captured data)
├── startedAt: Instant
├── completedAt: Instant?
└── status: RUNNING | COMPLETED | FAILED | CANCELLED

AuditEvent (Aggregate Root)
├── eventId: Long (sequential)
├── correlationId: UUID
├── eventType: STATE_TRANSITION | MODULE_INVOCATION | DECISION | CONFIG_CHANGE | DATA_ACCESS | AUTH
├── actorType: USER | SYSTEM
├── actorId: UUID?
├── contextType: String (CUSTOMER | CASE | WORKFLOW)
├── contextId: UUID?
├── action: String
├── payload: JsonNode
├── configVersion: String?
└── timestamp: Instant

4. Integration Patterns

4.1 Synchronous (within modular monolith)

Used when the caller needs the result to continue:

  • Workflow → Screen customer (invoke, wait, get result)
  • Workflow → Rate risk (invoke, wait, get result)
  • All modules → Fetch config (get active version)

Implementation: Direct method calls across module boundaries, through interface contracts. In Spring Boot: @Service interface in a shared API module, implementation in the domain module.

4.2 Asynchronous (events)

Used when the caller does not need the result:

  • Any module → Audit Service (fire and forget)
  • State transition → Notification (fire and forget)
  • Case state change → Workflow (trigger next state)

Implementation: Spring Application Events within the monolith. If extracted to services: Kafka or RabbitMQ.

4.3 Module Contract (Decision Module Standard)

Every decision module (Screening, Risk Rating, Network Analysis, Entity Resolution) must implement:

interface DecisionModule<I, O> {
    fun execute(request: ModuleRequest<I>): ModuleResponse<O>
}

data class ModuleRequest<I>(
    val requestId: UUID,
    val moduleType: String,
    val entityReference: UUID,
    val payload: I,
    val context: Map<String, Any>,
    val configVersion: String
)

data class ModuleResponse<O>(
    val requestId: UUID,
    val status: ModuleStatus,
    val decision: O?,
    val score: Double?,
    val rationale: String?,
    val evidence: List<EvidenceRef>?,
    val executionMetadata: ExecutionMetadata
)

enum class ModuleStatus { SUCCESS, FAILURE, TIMEOUT, EXTERNAL_UNAVAILABLE }

This is the contract that enables pluggable modules. Any new intelligence capability (Adverse Media, Transaction Monitoring in v2) just implements this interface.


5. Module Boundaries (What NOT to Do)

Violation Why It's Bad
Onboarding context imports from Screening.internal package Breaks modularity → extraction becomes impossible
Case Management reads Workflow DB tables directly Data ownership violation → stale reads, coupling
Risk Rating calls Screening to get data Should go through Workflow (orchestrator), not peer-to-peer
Audit Service queries Case Management for context Audit is write-only. Context is pushed to it, not pulled.
Shared "common" module with everything in it Becomes a dumping ground. Shared kernel limited to: IDs, value objects (Address, Money), module contract interfaces.

6. Architecture Fitness Tests

Tests that run in CI to prevent boundary violations:

class ArchitectureFitnessTest {

    @Test
    fun `onboarding module must not import from screening internals`() {
        noClasses()
            .that().resideInAPackage("..onboarding..")
            .should().dependOnClassesThat()
            .resideInAPackage("..screening.internal..")
            .check(importedClasses)
    }

    @Test
    fun `audit module must not depend on any domain module`() {
        noClasses()
            .that().resideInAPackage("..audit..")
            .should().dependOnClassesThat()
            .resideInAPackage("..onboarding..")
            .orShould().dependOnClassesThat()
            .resideInAPackage("..screening..")
            // ... etc for all domain modules
            .check(importedClasses)
    }

    @Test
    fun `no circular dependencies between modules`() {
        slices()
            .matching("com.fec.platform.(*)..")
            .should().beFreeOfCycles()
    }
}

Domain model derived from PRD v1.0 requirements. Re-evaluate if new bounded contexts are added or existing ones split.