Data Ownership is a Decision, Not a Default
In a multi-service architecture, every piece of data has to live somewhere. When nobody makes that decision deliberately, data ends up duplicated across services, inconsistently updated, and impossible to reason about. Data ownership is not a technical problem — it is a design decision that most teams skip and then spend years paying for.
DESCRIPTION
The question is not where data lives. It is: which service is the authoritative source of truth for this data? And: who has the right to mutate it?
In a monolith, this question does not arise — everything is in one place. In a multi-service architecture, it is the foundational question. Every service that needs data about an entity has to answer it: do I call the owning service to get current data, do I maintain a local copy, or do I own this data entirely?
Each answer has consequences. Most teams fall into the third option by accident, end up with three services that each think they own the customer record, and call the resulting inconsistency "a sync issue."
THE ACCIDENTAL DUPLICATE
This is how it happens. The order service needs the customer's email to send a receipt. Nobody wants to call the user service on every order creation — that is a synchronous dependency. So the order service stores a copy of the email at order time.
UserService.users: { id: 1, email: "old@example.com" }
OrderService.order_customers: { user_id: 1, email: "old@example.com" }
The user updates their email.
UserService.users: { id: 1, email: "new@example.com" }
OrderService.order_customers: { user_id: 1, email: "old@example.com" }
Now which is correct? For sending a new receipt, the new email is correct. For displaying the email that was on file when the order was placed, the old email might be correct. The duplicate exists. The semantics were never defined. Both answers are defensible. The system will give you the wrong one depending on which service you ask.
OWNERSHIP PATTERNS
There are three valid patterns. The mistake is using them interchangeably without deciding.
Pattern 1: Single owner, synchronous read. One service owns the data and others call it directly to read current state. Simple, correct, synchronously coupled.
PaymentService needs to verify account status before charging:
→ calls UserService.getAccount(user_id)
→ gets current, authoritative data
→ proceeds or rejects based on real state
Cost: latency, availability coupling
When to use: when you need authoritative current state
Pattern 2: Single owner, event-driven local cache. One service owns the data and emits change events. Other services maintain a local projection they keep current via event subscription.
UserService emits: user.email_updated { user_id: 1, new_email: "new@example.com" }
OrderService subscribes:
→ receives event
→ updates local order_customers table
→ all future reads use local copy
Cost: eventual consistency lag, projection maintenance
When to use: when you need read performance without synchronous coupling
Pattern 3: Point-in-time snapshot. The data is copied at a specific moment and the copy is intentionally immutable — a record of what was true then, not what is true now.
# When the order is placed, record what was true at that moment
order = {
"id": new_order_id,
"user_id": user.id,
"snapshot": {
"email": user.email,
"billing_address": user.billing_address,
"plan_tier": user.plan_tier
},
"snapshot_taken_at": now()
}
This is not a sync issue waiting to happen. It is a historical record. An invoice should show the address that was on file at the time of billing, not the address the user updated last month. The snapshot is the correct model.
DECIDING WHO OWNS WHAT
Ownership follows mutation rights. The service that is authoritative for creating and modifying an entity owns it. Every other service is a consumer.
Entity: Customer
Who creates it? UserService
Who modifies it? UserService (profile), BillingService (payment methods)
Who reads it? Everyone
Ownership: UserService owns core identity
BillingService owns payment methods as a subdomain
No one else mutates customer data — they read or project
When mutation rights are ambiguous — when two services both believe they can update the same field — you have a design problem, not a sync problem. The sync issues are a symptom. The missing ownership decision is the disease.
THE MIGRATION PATH FOR EXISTING SYSTEMS
If you inherit a system where three services all have copies of customer data and none of them are clearly authoritative, you cannot fix it with a sync job. You fix it by deciding.
Step one: for each entity, identify who creates new records. That service is the owner.
Step two: audit all mutation paths. For every service that updates a field on an entity it does not own, you have either an event to emit (tell the owner to update it) or a data model problem (that field actually belongs to the updating service).
Step three: eliminate duplicate writes. Replace them with events published to the owner or local projections derived from owner events.
# Before: OrderService directly updating customer email
db.execute("UPDATE customers SET email = %s WHERE id = %s", new_email, user_id)
# After: OrderService emits a request to the owner
event_bus.publish("customer.email_change_requested", {
"user_id": user_id,
"requested_email": new_email,
"requested_by_service": "order-service"
})
# UserService handles validation, deduplication, and the actual update
WHAT GOES WRONG WITHOUT THIS
- Conflicting updates: two services update the same record concurrently, last write wins, one update silently lost
- Inconsistent reads: same entity queried from two services returns different state, both "correct" by their own records
- Impossible rollbacks: a transaction spanning multiple owning services cannot be atomically reversed
- Debug nightmares: an entity is in a broken state and you cannot determine which service's write caused it because three services all had mutation rights
MODEL
Data ownership is a design decision that must be made explicitly, per entity, before the services that need that entity are built. The decision determines who can mutate, who reads current state synchronously, who maintains local projections, and who stores point-in-time snapshots. If you cannot answer "which service is the authoritative source of truth for this entity," you have not made the decision yet — and the system will make it for you, incorrectly, at the worst possible time.
SEE ALSO
event-driven-architecture(5), outbox-pattern(4), cqrs(6), eventual-consistency(2)