Microservice Data Ownership and Bounded Context Model
Model data ownership dan bounded context untuk enterprise microservices in CPQ/Quote/Order/Billing systems, termasuk aggregate ownership, service boundary, source of truth, shared database anti-pattern, reference by ID, local projection, read model, integration events, cross-context consistency, and production correctness.
Microservice Data Ownership and Bounded Context Model
1. Core Idea
Microservice data modelling starts with one question:
Which service owns which data, and who is allowed to change it?
In enterprise CPQ / Quote / Order / Billing / Catalog / Telco BSS/OSS, many concepts are connected:
- customer,
- account,
- catalog,
- price,
- quote,
- order,
- product inventory,
- service inventory,
- billing account,
- charge,
- invoice,
- approval,
- user/authority,
- fulfillment task.
If every service reads and writes every table, system becomes distributed monolith.
Mental model:
Each bounded context owns its data and invariants. Other contexts reference, request, subscribe, or project that data, but do not casually mutate it.
2. Why Data Ownership Matters
Without clear ownership:
- two services update quote status differently,
- order service changes product inventory directly,
- billing service recomputes quote price from internal quote tables,
- catalog change breaks order decomposition,
- reporting reads inconsistent tables,
- services join across databases,
- one schema migration breaks many services,
- no one owns data quality,
- incident root cause becomes unclear,
- duplicate source of truth emerges.
Microservice correctness requires ownership boundaries.
3. Bounded Context
Bounded context is a boundary where a model has a specific meaning.
Example:
| Term | Quote Context | Order Context | Billing Context |
|---|---|---|---|
| Item | Quote line/proposal component | Execution instruction | Invoice/charge line |
| Status | Quote lifecycle | Order lifecycle | Invoice/payment lifecycle |
| Price | Proposed/accepted amount | Execution billing reference | Charge/invoice amount |
| Customer | Buyer context | Order party context | Payer/invoice context |
Same word can mean different things in different contexts.
Do not force one universal model if contexts have different semantics.
4. Possible Bounded Contexts in Quote-to-Cash
Typical contexts:
| Context | Owns |
|---|---|
| Customer/Account | Customer, account, organization hierarchy, contacts. |
| Catalog | Product offering/specification, characteristics, rules. |
| Pricing | Price lists, price rules, discount rules, price calculation. |
| Quote | Quote header/item, quote version, quote snapshot, approval request link. |
| Approval | Approval request, step, decision, authority evidence. |
| Order | Product order, order items, order lifecycle. |
| Fulfillment | Decomposition, fulfillment task, fallout, external work tracking. |
| Product Inventory | Product instance, product status/history. |
| Service/Resource Inventory | Technical service/resource instance. |
| Billing | Billing account, charge, invoice, payment reference. |
| Identity/Authority | Users, roles, permissions, commercial authority. |
| Integration | Outbox/inbox/message tracking, external system mapping. |
| Reporting/Analytics | Read models/facts, not usually source of truth. |
Actual internal boundaries must be verified.
5. Source of Truth
For each entity, define source of truth.
Example table:
| Entity | Source of truth | Other services do |
|---|---|---|
| Product offering | Catalog service | Reference offering ID/version. |
| Quote | Quote service | Read via API/event projection. |
| Order | Order service | Subscribe to order events. |
| Product instance | Product inventory service | Request changes via order/fulfillment. |
| Billing account | Billing/customer/billing system | Validate/reference/snapshot. |
| Invoice | Billing system | Mirror status/reference. |
| Approval decision | Approval service | Query/subscribe decision. |
| User role | IAM/identity service | Cache/evaluate via auth service. |
If two systems claim to own same mutable field, clarify ownership immediately.
6. Ownership vs Local Projection
A service may store data it does not own as local projection.
Example:
Order service owns order.
Billing service may store projection:
order_projection
- order_id
- order_number
- customer_id
- billing_account_id
- status
- last_event_version
Billing service uses this for lookup, but does not change order status.
Projection is not source of truth.
Rules:
- mark projection table clearly,
- update from events/sync,
- store source version,
- monitor lag,
- never use projection to override owner data.
7. Reference by ID and Snapshot
Cross-context references should use stable IDs and snapshots where needed.
Example order item:
product_offering_id
product_offering_version
product_offering_name_snapshot
Why include snapshot?
- catalog service may be unavailable,
- offering name may change,
- historical order must remain understandable,
- report/export needs stable display.
Pattern:
| Data type | Use |
|---|---|
| ID reference | For lookup and identity. |
| Version reference | For historical correctness. |
| Snapshot fields | For audit/display/stability. |
| Projection | For local query optimization. |
8. Avoid Shared Database Anti-Pattern
Microservice anti-pattern:
Quote service writes quote table.
Order service reads quote table directly.
Billing service joins quote, order, charge tables directly.
Fulfillment service updates order_item.status directly.
Problems:
- hidden coupling,
- unsafe migrations,
- invariant bypass,
- unclear ownership,
- no service boundary,
- hard-to-debug side effects.
Preferred:
- owner service exposes API/commands/events,
- consumers maintain projections,
- integration uses events/messages,
- reporting uses curated data pipeline/read model.
There may be legacy exceptions. If so, document and contain them.
9. Aggregate Ownership
Aggregate is a consistency boundary.
Examples:
| Aggregate | Owner | Invariants |
|---|---|---|
| Quote | Quote service | Quote version, item totals, approval requirement. |
| Product order | Order service | Order lifecycle, item state aggregation. |
| Product instance | Product inventory service | Product status, active characteristics. |
| Billing account | Billing/customer service | Billable status, payer profile. |
| Approval request | Approval service | Step/decision aggregation, authority evidence. |
| Catalog offering | Catalog service | Offering version, publication status. |
Only owner should enforce aggregate invariants and mutate aggregate state.
10. Cross-Aggregate Consistency
Microservices cannot always update multiple aggregates atomically.
Example:
Quote accepted -> Order created -> Product activated -> Charge activated
This is eventually consistent.
Use:
- events,
- outbox/inbox,
- saga/orchestration,
- reconciliation,
- idempotency,
- compensating actions,
- status tracking.
Do not pretend distributed transaction exists unless explicitly implemented.
11. Command Across Context
If one context needs another to act, send command/request.
Example:
Quote service should not create order rows directly.
Better:
Quote service emits QuoteConversionRequested
Order service creates ProductOrder
Order service emits ProductOrderCreatedFromQuote
Quote service records conversion completed
Or synchronous API:
POST /orders
{
"sourceQuoteId": "...",
"sourceQuoteVersion": 4,
"idempotencyKey": "..."
}
The receiving service validates and owns creation.
12. Event Across Context
If one context needs to notify others, publish integration event.
Example:
ProductInstanceActivated
Billing service consumes and activates recurring charge.
Important:
- producer owns event fact,
- consumer owns its side effects,
- consumer must be idempotent,
- event schema is contract,
- correlation ID preserved,
- reconciliation monitors gaps.
13. Local Read Models
Read models optimize queries crossing contexts.
Example support screen needs:
- customer name,
- quote number,
- order status,
- product status,
- billing account,
- latest invoice,
- open fallout.
Do not make UI call ten services synchronously if latency/reliability suffers.
Create read model:
customer_support_summary
- customer_id
- account_id
- active_product_count
- open_order_count
- open_fallout_count
- latest_invoice_status
- projection_updated_at
Read model is derived, not source of truth.
14. Reporting Context
Analytics/reporting often needs cross-domain joins.
Do not make operational services share databases just for reporting.
Better:
- event stream to warehouse/lake,
- CDC to analytics store,
- curated fact/dimension models,
- semantic layer,
- governed metric definitions,
- data quality checks.
Reporting can use denormalized facts, but operational command logic should use source-of-truth services.
15. Ownership Matrix
Every major entity needs ownership matrix.
Example:
| Entity/field | Owner | Mutable by | Read by | Notes |
|---|---|---|---|---|
| quote.status | Quote service | Quote service only | Order, reporting via events/API | Command-driven. |
| order.status | Order service | Order service only | Quote, fulfillment, billing projections | Derived from item state. |
| product_instance.status | Product inventory | Inventory/fulfillment commands | Billing, support | Update after fulfillment proof. |
| billing_account.status | Billing/account service | Billing/account service | Quote/order validation | May be external-owned. |
| price_rule | Pricing service | Pricing admin | Quote pricing | Versioned. |
| invoice.status | Billing system | Billing system | Support/reporting projection | External reference. |
This matrix is extremely useful for onboarding and PR reviews.
16. Data Contract Between Contexts
A data contract defines what one context promises another.
For events:
Event name
Schema version
Required fields
Field semantics
Ordering key
Idempotency expectation
PII classification
Retention expectation
Compatibility policy
For APIs:
Endpoint
Request/response schema
Error model
Idempotency behavior
Rate limits
Authorization model
SLA
Backward compatibility
For projections:
Source event/API
Projection owner
Freshness SLA
Rebuild strategy
Data quality checks
17. Reference Data Ownership
Reference data can be tricky.
Examples:
- country code,
- currency code,
- status enum,
- reason code,
- product category,
- tax category,
- approval reason,
- cancellation reason.
Decide ownership:
- centralized reference-data service,
- per bounded context,
- static code/config,
- external master data.
Avoid each service inventing different reason codes for the same business process.
But also avoid forcing unrelated contexts into one global enum when semantics differ.
18. Shared Kernel
Sometimes contexts share a small common model.
Examples:
- money value object,
- currency code,
- correlation ID,
- tenant ID,
- standard error model,
- event envelope.
Keep shared kernel small and stable.
Do not put large domain models into shared library:
shared QuoteEntity used by quote, order, billing
This recreates monolith coupling.
19. Anti-Corruption Layer
When integrating with external or legacy system, use anti-corruption layer.
Example:
External billing system uses:
Account = payer + contract + invoice group
Internal model has separate:
customer, account, billing account, agreement
Do not leak external model everywhere.
Create mapping layer:
billing_account_mapping
external_billing_account_id
local_billing_account_id
mapping_status
last_synced_at
Anti-corruption layer protects internal model integrity.
20. Cross-Service ID Strategy
IDs must be stable and unambiguous.
Options:
- UUID internal ID,
- business number,
- external ID,
- composite natural key,
- tenant-prefixed ID.
Cross-service references should specify:
entity_type
entity_id
source_system
entity_version if needed
Avoid assuming UUID alone is globally meaningful if multiple systems can produce IDs.
21. Tenant and Data Boundary
If multi-tenant:
- tenant_id must be part of ownership and access boundary,
- events should carry tenant_id,
- projections should include tenant_id,
- indexes should support tenant-scoped queries,
- cross-tenant references should be prevented,
- support/admin access should be audited.
Tenant boundary is data ownership at another dimension.
22. Consistency Pattern: Saga
Saga coordinates multi-step flow.
Example quote-to-order-to-billing:
Quote accepted
-> Create order
-> Decompose order
-> Fulfill order
-> Activate product
-> Activate charge
Saga state:
saga_instance
- id
- saga_type
- business_key
- status
- current_step
- correlation_id
- started_at
- completed_at
Saga step:
saga_step
- saga_instance_id
- step_name
- status
- retry_count
- compensation_action
Saga may be orchestrated by workflow or choreography via events.
23. Consistency Pattern: Reconciliation
Because eventual consistency can fail, reconciliation is mandatory.
Examples:
- quote accepted but no order,
- order fulfilled but no product instance,
- product active but no charge,
- billing account updated but order projection stale,
- event published but consumer projection missing.
Reconciliation belongs to production data model, not only ops script.
Store results:
cross_context_reconciliation_result
- source_context
- target_context
- entity_type
- entity_id
- expected_state
- actual_state
- mismatch_code
- severity
- checked_at
- repair_status
24. Ownership and Authorization
Ownership affects authorization.
Example:
- Quote service owns quote data.
- Authorization service owns permission.
- Quote service calls authorization/authority check before mutation.
- Quote service still enforces quote lifecycle invariant.
Do not let downstream service mutate owned data just because it has database access.
Ownership and permission are separate:
Service owns data.
User has permission.
Application service enforces both.
25. Data Ownership and Migration
Microservice boundaries evolve.
Migration scenarios:
- split quote and pricing service,
- move billing account ownership to billing system,
- extract approval service,
- replace legacy order DB,
- introduce product inventory service.
Migration needs:
- dual read/write strategy,
- ownership cutover date,
- event backfill,
- projection rebuild,
- data reconciliation,
- API compatibility,
- legacy table freeze,
- incident rollback plan.
Document temporary dual ownership carefully.
26. PostgreSQL Physical Design for Projections
Projection table example:
create table order_projection_for_billing (
order_id uuid primary key,
order_number text not null,
customer_id uuid not null,
billing_account_id uuid,
source_quote_id uuid,
status text not null,
aggregate_version integer not null,
last_event_id uuid not null,
projected_at timestamptz not null
);
Projection tracking:
create table projection_checkpoint (
projection_name text primary key,
source_name text not null,
last_event_id uuid,
last_offset text,
last_processed_at timestamptz,
status text not null,
error_message text
);
Read model should clearly signal source and freshness.
27. Java/JAX-RS Backend Implications
Service should expose operations for owned data.
Example:
Quote service:
POST /quotes
POST /quotes/{id}/accept
GET /quotes/{id}
Order service:
POST /orders
POST /orders/{id}/cancel
GET /orders/{id}
Avoid service A repository writing service B tables.
Application service should:
- enforce ownership,
- validate local invariants,
- call external context via API/event,
- store references/snapshots,
- use outbox for events,
- update local projection only from external events.
28. MyBatis/JPA/JDBC Implications
Data access layer should not cross bounded context.
Bad:
QuoteRepository joins order table and billing table.
Better:
Quote service stores quote-owned tables.
Quote service has order_projection if needed.
Billing data fetched via billing API/projection.
For monolith modular codebase, still apply logical ownership:
- module-owned tables,
- repository per module,
- no cross-module write,
- explicit service interface.
29. Event and Projection Failure Modes
| Failure mode | Symptom | Cause | Prevention |
|---|---|---|---|
| Projection stale | UI shows old order status | Consumer lag | Projection checkpoint/lag monitor |
| Duplicate projection update | Wrong count | Non-idempotent consumer | Aggregate version/inbox |
| Owner bypassed | Invalid state | Direct DB write by another service | Ownership enforcement |
| Data drift | Projection differs from source | Missed event | Reconciliation |
| Schema migration breaks consumer | Runtime failure | Shared DB or event breaking change | Contract versioning |
| Circular dependency | Services call each other synchronously | Poor boundary design | Events/read models |
| Chatty integration | High latency/failure | No local projection | Denormalized read model |
| Dual ownership | Conflicting updates | Unclear source of truth | Ownership matrix |
30. Reporting and Analytics Impact
Operational ownership differs from analytical modelling.
Analytics may create:
- quote fact,
- order fact,
- product snapshot fact,
- billing revenue fact,
- customer dimension,
- product dimension,
- account hierarchy dimension.
Analytics can join across domains, but must preserve:
- source system,
- data freshness,
- metric definition,
- historical hierarchy,
- event/effective date,
- data quality flags.
Do not let analytical fact table become operational source of truth.
31. Data Quality Checks
-- Projection stale compared to expected freshness
select projection_name, last_processed_at, status
from projection_checkpoint
where status <> 'HEALTHY'
or last_processed_at < now() - interval '15 minutes';
-- Projection version regression check pattern
-- Store and compare aggregate_version; consumers should reject lower versions.
-- Orders created from quote but quote projection missing source quote
select order_id, order_number
from order_projection_for_billing
where source_quote_id is null
and order_number is not null;
Actual checks depend on projection tables.
32. Security and Privacy
Ownership protects privacy too.
Risks:
- projection contains more data than consumer needs,
- service stores sensitive copied data without retention policy,
- events broadcast PII/margin/billing data,
- access controls differ between source and projection,
- deleted/anonymized data remains in projections.
Controls:
- minimal projection fields,
- data classification,
- access control on read models,
- projection purge/anonymization,
- event payload minimization,
- ownership of retention policy.
33. PR Review Checklist
When reviewing cross-service data changes, ask:
- Which bounded context owns this data?
- Is this source-of-truth or projection?
- Is any service writing data it does not own?
- Is reference by ID/version enough or is snapshot needed?
- Is integration via API, event, or shared DB?
- Are events versioned and outbox-backed?
- Are consumers idempotent?
- Is projection freshness monitored?
- Is reconciliation available?
- Is data duplicated for read model with clear ownership?
- Does this create circular dependency?
- Does this expose sensitive data to another service?
- Does reporting rely on operational projection incorrectly?
- Is ownership matrix updated?
- Are migration/cutover implications handled?
34. Internal Verification Checklist
Verify these in the internal CSG/team context:
- Actual service/module boundaries for quote, order, catalog, billing, product inventory, approval, fulfillment.
- Which services own which tables/entities.
- Whether database-per-service exists or shared DB with logical ownership.
- Whether any service writes another service's tables.
- Whether local projections exist and how they are updated.
- Whether ownership matrix exists.
- Whether outbox/inbox is used between contexts.
- Whether APIs or events are primary integration mechanism.
- Whether shared libraries contain domain models.
- Whether external systems own billing/customer/inventory data.
- Whether anti-corruption layer exists for external/legacy models.
- Whether reporting reads operational DBs or curated analytics store.
- Whether reconciliation exists between contexts.
- Whether incidents mention projection drift, shared table coupling, duplicate source of truth, or cross-service migration failure.
35. Summary
Microservice data ownership is a correctness boundary.
A strong model must define:
- bounded contexts,
- aggregate ownership,
- source of truth,
- reference vs snapshot,
- local projection,
- read model,
- integration events,
- command/API boundaries,
- shared database restrictions,
- cross-context consistency,
- saga/reconciliation,
- projection freshness,
- ownership matrix,
- migration strategy,
- security and retention for copied data.
The key principle:
In microservices, the most important data modelling question is not “where is the table?” but “who owns the truth, who may change it, and how everyone else learns about it safely.”
You just completed lesson 48 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.