All writing

Who owns the data? Thinking about application boundaries

Thinking about data ownership as the right to define meaning, enforce rules and control changes, rather than simply the location of a database table.

about 27 minutes min read

A system diagram might contain several application components:

catalog
basket
inventory
order
payment

Then one large database appears underneath them:

CATALOG     BASKET     INVENTORY     ORDER     PAYMENT
    \          |           |           |          /
     \         |           |           |         /
      └────────┴───────────┴───────────┴─────────┘
                           │
                           ▼
                     SHARED DATABASE

The applications have different names. They may run as different processes. They may even be deployed separately.

But if they all read and write the same tables, where are the real boundaries?

A catalog deployment might change a column used by the basket. The payment service might update order state directly. A reporting query might rely on an internal status value nobody remembers promising. One team might be unable to change its schema without coordinating with four others.

The boxes are separate.

The data model is not.

This raises a question that sounds administrative but is actually architectural:

Who owns the data?

My first answer would have been:

The team that owns the database.

That is not precise enough.

A database server can contain data belonging to several areas. A platform or database team may operate the infrastructure without owning the business meaning of every row. A service might be the only writer while many systems read its tables directly. The application may own the code while somebody else carries legal or regulatory responsibility for the information.

Here, ownership means architectural authority over application data. It does not mean legal ownership of information, and it does not merely mean responsibility for operating the database platform.

For an application boundary, the most useful definition seems to be:

The owner defines what the data means, decides which changes are valid, controls its internal representation, and provides the contracts through which other parts of the system use it.

That is much stronger than knowing which server stores the bytes.

Ownership begins with meaning

Suppose a furniture store has a product with this identifier:

chair-42

Several parts of the business may care about it.

Catalog knows the name, description, images, dimensions, listed price and category.

Inventory knows the warehouse location, quantity available, quantity reserved and reorder threshold.

Ordering knows the product identifier, description at purchase, unit price at purchase, tax treatment and quantity ordered.

Search knows the searchable name, keywords, category facets, popularity score and an availability indicator.

All four areas refer to the same real-world product. That does not mean they need one universal Product record containing every attribute.

                       REAL-WORLD PRODUCT
                                │
              ┌─────────────────┼─────────────────┐
              │                 │                 │
              ▼                 ▼                 ▼
          CATALOG VIEW     INVENTORY VIEW     ORDER VIEW
              │                 │                 │
         descriptive         stock state       purchase facts
          information

Each part of the system has a different reason for knowing about the product. The identifier may be shared. The model does not have to be.

The same applies to language.

Consider the word customer.

A marketing system might mean a person who can receive campaigns. An ordering system might mean the buyer responsible for an order. A support system might mean a person associated with cases and conversations. A payment system might care only about a payer identifier, billing details and risk information.

Trying to force all of these meanings into one enterprise-wide object can produce a model full of marketing preferences, order history, support priority, payment methods, fraud scores, addresses, account status, loyalty tiers and dozens of fields nobody may remove.

Everybody reuses the same model.

Nobody can change it safely.

A boundary gives language a local meaning. Within ordering, customer can mean exactly what ordering needs it to mean. Within support, the same person can be represented differently.

This does not make the models carelessly inconsistent. It recognises that one real-world thing can participate in several business contexts.

A boundary protects meaning, rules and behaviour

A useful application boundary surrounds:

  • language
  • rules
  • behaviour
  • data

For example:

ORDER BOUNDARY
│
├── language
│   ├── order
│   ├── order line
│   ├── checkout
│   └── cancellation
│
├── rules
│   ├── an order must contain at least one line
│   ├── confirmed orders cannot be edited freely
│   └── cancellation depends on fulfilment state
│
├── behaviour
│   ├── create order
│   ├── confirm order
│   └── cancel order
│
└── data
    ├── orders
    ├── order lines
    └── order status history

The data is not an unrelated storage concern beneath the boundary. It holds the state on which the boundary’s rules operate.

If another component updates that data directly, it can bypass those rules.

Suppose the order table contains:

order_id
status
total

Another component might discover that this SQL works:

UPDATE orders
SET status = 'cancelled'
WHERE order_id = 5001;

The database accepted the statement. That does not prove the transition was valid.

Cancellation may require checking whether payment has settled, fulfilment has begun, items have shipped or a refund is required.

The meaningful operation is not:

update status column

It is:

cancel order

The order boundary should own the decision.

CALLER
   │
   │ CancelOrder
   ▼
ORDER BOUNDARY
   │
   ├── load order
   ├── evaluate cancellation rules
   ├── change state
   ├── record consequences
   └── commit

Direct table access replaces a domain operation with a storage mutation. It lets the caller decide which rules matter.

That is not data ownership.

It is a communal stationery cupboard containing production state.

Database constraints are still valuable. Application ownership does not mean every rule should live only in application code.

CREATE TABLE order_lines (
    order_id   bigint  NOT NULL,
    product_id text    NOT NULL,
    quantity   integer NOT NULL CHECK (quantity > 0),
    PRIMARY KEY (order_id, product_id),
    FOREIGN KEY (order_id) REFERENCES orders(order_id)
);

Constraints can protect required values, uniqueness, valid ranges and referential integrity. Transactions can group related changes.

The important point is that these constraints belong to the order boundary’s implementation. The order boundary decides which tables exist, what their columns mean, which constraints apply and how state transitions occur.

Other components should consume the boundary’s public contract rather than treating its tables as their own API.

Private persistence makes ownership real

Suppose the basket reads catalog data directly:

SELECT name, price
FROM products
WHERE product_id = ?;

That looks efficient. There is no network call, API client or duplicated data.

But the basket is now coupled to the table name, column names, data types, status representation, price representation, schema location, migration timing and database availability.

The database schema has become a public interface.

It just lacks most of the things I would normally expect from one:

  • explicit ownership
  • versioning policy
  • compatibility guarantees
  • documentation
  • deprecation process
  • consumer tests

The dependency is still there. It is merely hiding inside SQL.

The problem becomes sharper when several applications write the same tables.

SERVICE A ─────┐
               ├────► shared table
SERVICE B ─────┤
               │
SERVICE C ─────┘

Who decides which transitions are legal? Which writer validates which rule? Which writer publishes change events? Who may alter the schema? Who repairs conflicting updates? Who owns an incident caused by invalid state?

If the answer is, “It depends which code path changed the row,” then the data has no clear behavioural owner.

A table may have many readers. Having many independent writers is a much stronger warning sign.

Read access can also weaken ownership.

“Only the owner writes the table. Other services merely read it” is better than shared writes, but those readers are still bound to the internal schema.

Suppose catalog changes price from one decimal column into amount_minor and currency because the domain now supports multiple currencies. Every direct reader must change with the migration. If five consumers query the table, the catalog team may need to coordinate five deployments.

Catalog does not fully control its implementation anymore. Its internal representation has become a cross-system contract.

A useful default is therefore:

Only the owner reads and writes its persistence directly.

Other components use a supported contract such as an API, event, export, published view or purpose-built read model. The exact mechanism can vary. The boundary should remain explicit.

This does not require one database server per service.

Several arrangements are possible:

SHARED SERVER, PRIVATE TABLES

database server
├── catalog tables
├── basket tables
└── order tables


SHARED SERVER, PRIVATE SCHEMAS

database server
├── catalog schema
├── basket schema
└── order schema


SEPARATE DATABASES

catalog database
basket database
order database

A smaller system could use one database server while giving each application separate credentials, access only to its own schema and control over its own migrations.

The important property is that one application cannot casually reach into another application’s state.

Physical isolation can increase later for availability, performance, security, scaling or operational independence. Ownership begins with enforced logical boundaries.

Credentials can help make the architecture real.

Documentation might say:

The order service must not access inventory tables.

But if its database user can run:

SELECT *
FROM inventory.stock;

the boundary depends on continued restraint.

A more enforceable design gives each application credentials limited to its own data.

catalog_runtime
    └── catalog schema only

inventory_runtime
    └── inventory schema only

order_runtime
    └── order schema only

This turns “please do not cross the boundary” into “the system prevents ordinary boundary crossing”.

Good architecture should not require every developer to resist the same convenient shortcut forever.

Owners publish contracts, not tables

If the basket needs product information, catalog can provide it through an API.

BASKET
   │
   │ GetProduct
   ▼
CATALOG
   │
   │ ProductSummary
   ▼
BASKET

The response might contain:

{
  "product_id": "chair-42",
  "name": "Gopher reading chair",
  "price_minor": 24900,
  "currency": "GBP",
  "purchasable": true
}

That contract does not have to mirror the catalog database. Catalog may store the information across several tables.

The API contract is designed for consumers. The database schema is designed for catalog persistence.

Separating them gives the owner room to change its implementation without changing every consumer. It also lets the service expose meaning rather than storage accidents.

For example, purchasable: true may be more useful than exposing several internal flags and expecting the basket to reconstruct catalog policy.

An API preserves authority, but it introduces runtime coupling. The basket may now depend on catalog being available during the interaction. That brings network latency, timeouts, partial failure, availability dependencies and contract versioning.

This may be appropriate when the basket needs current authoritative information, such as whether the product is currently purchasable.

Ownership does not mean every consumer must always call the owner synchronously. It means the owner decides how authoritative information is made available.

Events are another option.

Suppose search needs product information. Calling catalog for every search result could be expensive and unnecessarily coupled. Catalog could publish events such as:

  • ProductCreated
  • ProductNameChanged
  • ProductPriceChanged
  • ProductDiscontinued

Search consumes those events and maintains its own model.

CATALOG
   │
   │ ProductPriceChanged
   ▼
MESSAGE SYSTEM
   │
   ▼
SEARCH
   │
   └── update search document

Search owns its documents, tokenisation, ranking fields and facets. Catalog remains the authority for catalog facts.

That is duplication, but it is not necessarily confused ownership.

The distinction is clear when everybody knows:

  • who may originate the fact
  • who stores a copy
  • how the copy is refreshed
  • how staleness is detected
  • how the copy is rebuilt

A copied value should also have understood provenance.

SEARCH INDEX FIELD

product_name
    ├── source: catalog
    ├── refreshed by: catalog events
    ├── allowed staleness: 60 seconds
    └── rebuild source: catalog export

Without this, duplicated data becomes archaeological sediment. Nobody knows where it came from, whether it is current, whether it may be edited locally or how to repair it.

A derived model should normally be read-only with respect to the facts it copies. Search may change ranking, tokenisation or index structure. It should not become a second place for editing the official product name.

Facts have owners, lifecycles and copies

I had previously treated duplicated data as automatically bad. Normalisation taught me to avoid unnecessary repetition inside a relational model.

Across application boundaries, some duplication may be the price of autonomy.

Suppose an order records:

product_id
product_name
unit_price

The current product name and price also exist in catalog.

Is the order duplicating catalog data?

Yes.

But the order may need a historical fact:

What description and price were agreed when this purchase was made?

If catalog later changes Gopher reading chair to Deluxe Gopher reading chair, the existing order should not necessarily be rewritten.

The values now have different meanings:

CATALOG

current product presentation


ORDER

description captured at purchase

They are not competing authorities over the same fact. They are different facts with different lifecycles.

This is why ownership is usually divided by facts, not rows.

A convenient products row might contain:

name
price
stock_quantity
average_rating
search_rank

But those fields belong to different business areas.

Catalog owns description and listed price. Inventory owns stock and reservation state. Reviews owns review records and rating calculations. Search owns ranking and indexing.

Several teams updating different columns in one row creates a shared boundary.

A composed view may present those facts together to users. Storage convenience should not determine business ownership.

The phrase “single source of truth” also needs precision.

One system might be authoritative for the current customer name, while another is authoritative for the name captured on an order. A reporting store may be authoritative for an approved monthly financial report without being the operational owner of every underlying transaction.

Instead of asking:

Which database is the source of truth?

Ask:

Which boundary has authority over this specific fact at this point in its lifecycle?

For example:

current listed price
    └── catalog

price agreed for order 5001
    └── order

captured payment amount
    └── payment

searchable price copy
    └── search owns the projection
        catalog owns the originating fact

Truth has a subject.

Invariants help reveal the right boundary

An invariant is a rule that must remain true when state changes.

Examples include:

  • a reservation cannot reduce unallocated stock below zero
  • an order total matches the accepted lines, taxes, discounts and charges
  • a basket quantity must be positive
  • the same payment capture request must not be applied twice
  • a confirmed shippable order must have a valid delivery address

The data and behaviour needed to enforce one invariant usually belong together.

Suppose reserving stock requires checking available quantity, existing reservations and reservation expiry. If those facts are split across several independently controlled services, enforcing the rule becomes a distributed workflow.

That may be necessary. But it should make me ask whether the boundary has been drawn through the middle of one consistency requirement.

A useful heuristic is:

State that must change together to protect an immediate invariant often belongs within one transactional boundary.

That does not mean every business process belongs in one service. It means boundary design should follow rules and behaviour, not a desire to create the maximum number of boxes.

A service per table is not ownership.

A database might contain customers, orders, order_lines, payments and shipments. Turning each table into its own service would be decomposition by storage structure.

orders and order_lines may participate in one transactional model. An order line may have no useful independent lifecycle outside its order. Putting a network boundary between them would turn ordinary consistency into distributed coordination.

A service can own several tables:

ORDER BOUNDARY
│
├── orders
├── order_lines
├── order_addresses
└── order_status_history

The phrase “database per service” does not mean “table per service”. It means the service’s persistent state is private to that service.

A service can use many tables, one document collection, several key spaces or multiple internal stores, provided the ownership remains coherent.

Strong boundaries expose distributed-system costs

Private ownership gives each boundary autonomy, but it removes the convenience of one transaction spanning the whole system.

Inside one relational database, the order service can use a transaction:

BEGIN;

INSERT INTO orders (...);
INSERT INTO order_lines (...);
INSERT INTO order_status_history (...);

COMMIT;

Either the related changes commit together or they do not.

Once data is divided between independently owned stores, one local transaction cannot simply update all of them.

ORDER DATABASE       INVENTORY DATABASE       PAYMENT DATABASE
      │                       │                        │
      └──────── no ordinary local transaction ───────┘

Suppose placing an order requires:

  • creating the order
  • reserving inventory
  • authorising payment

If those are separate owners, the operation spans several local transactions.

Possible outcomes include an order being created while inventory reservation fails, inventory being reserved while payment is declined, or payment being authorised while the response is lost.

The system needs workflow rules:

  • which step happens first
  • what state records progress
  • which failures are retryable
  • which actions compensate earlier work
  • who owns the overall process
  • what the customer sees while it is incomplete

The database no longer provides one atomic umbrella over the whole business process. Intermediate states must become explicit.

Shared databases can hide this question by placing everything in one transaction. That is simple when one model genuinely owns all three changes. It becomes problematic when separate business capabilities are editing one another’s state.

Independent ownership forces decisions about which rules require immediate consistency, which information can propagate later, which failures require compensation and which service is authoritative at each stage.

This is harder. It can also be a more honest model of a business process that was never truly one indivisible event.

Queries become harder too.

A shared relational database makes cross-domain joins straightforward. Once data belongs to separate boundaries, a query may need one of several approaches:

  • API composition
  • a purpose-built read model
  • an analytics pipeline
  • a data warehouse

API composition keeps authority with each owner.

                   ACCOUNT VIEW
                         │
          ┌──────────────┼──────────────┐
          │              │              │
          ▼              ▼              ▼
       PROFILE          ORDER         LOYALTY

This gives fresh authoritative data and avoids a duplicated read model. It also adds several network calls, combined latency and partial-failure handling.

A read model can optimise a frequent cross-boundary query.

PROFILE EVENTS ─────┐
                    │
ORDER EVENTS ───────┼────► ACCOUNT READ MODEL
                    │
LOYALTY EVENTS ─────┘

The page reads one store instead of calling several services. The cost is event processing, lag, replay, duplicate handling, ordering policy, monitoring and rebuild procedures.

Reporting deserves its own architecture as well. Allowing reporting tools to query every operational database directly can create production load, schema coupling, broad credentials and migration constraints.

A separate analytical pipeline can receive exports, events or change streams from operational owners and load them into a reporting store. That store owns reporting schemas, historical integration, aggregations and analytical performance. It does not become the authority for operational changes.

The loss of easy transactions and joins is a real cost. It should not be waved away.

Ownership includes evolution, operations and responsibility

If catalog owns its internal schema, it should be able to change its table layout, indexes, normalisation, storage technology and migration strategy without requiring every other application to update simultaneously.

Its public contracts still create obligations.

INTERNAL SCHEMA

owner may evolve independently


PUBLIC API OR EVENT

owner must respect consumer contract

If catalog publishes ProductPriceChanged, consumers may depend on its field names, field types, meaning, delivery expectations and ordering assumptions.

The owner must manage versioning, backward compatibility, deprecation, documentation and consumer communication.

Owning a contract means being responsible for its stability, not being free from responsibility.

The same applies to events. A service should usually publish facts it has authority to assert.

Catalog can publish ProductDiscontinued because it owns catalog publication state. Inventory can publish StockReservationFailed because it owns reservation state. Order can publish OrderConfirmed because it owns the order lifecycle.

A service should be cautious about publishing conclusions that belong to another owner.

Ownership also includes vocabulary.

What does PaymentCompleted mean?

Authorised? Captured? Settled? Recorded internally? Accepted by an external provider?

The owner must define its terms. It is not enough to own the table containing status = 3. The owner must define what status 3 means and which transitions make it valid.

Operational responsibility matters too.

The application owner should understand:

  • migrations
  • backups and restores
  • retention and archiving
  • monitoring and capacity
  • access controls
  • incident response

Infrastructure specialists may provide the database platform, but the application owner still needs to define the data’s semantics and operational requirements.

PLATFORM TEAM

operates database capability


APPLICATION OWNER

defines data semantics and requirements

Ownership can be collaborative without becoming ambiguous.

Security and privacy also follow the boundary.

A catalog service does not need payment credentials. A payment service does not need product descriptions. A shipping service should receive only the delivery information required for fulfilment.

Purpose-specific contracts support least privilege, data minimisation, auditability and retention control.

A boundary should not become an excuse to duplicate sensitive data everywhere. Every copy creates another lifecycle and another place to protect.

Ownership can also move.

Suppose catalog initially stores stock_quantity. As inventory becomes more complex, the business introduces multiple warehouses, reservations, replenishment, allocation and expiry. Inventory may become its own model.

A migration might:

  1. define the new inventory contract
  2. create the new inventory store
  3. copy existing stock state
  4. keep old and new models synchronised temporarily
  5. move readers to the new owner
  6. move write authority
  7. remove the old field

During the transition, ownership must remain explicit. Otherwise catalog may say 12 are available while inventory says 9.

Two sources of truth are usually one source of arguments.

Logical ownership matters more than deployment shape

Nothing about data ownership requires separate network services.

A modular monolith can contain catalog, basket, inventory and order modules. Each can own its domain model, tables, migrations and public application interface.

Other modules avoid direct access.

BASKET MODULE
      │
      │ catalog interface
      ▼
CATALOG MODULE
      │
      └── catalog persistence

The calls may remain in-process. The database server may be shared. The ownership boundaries can still be real.

This can provide strong modularity without introducing network calls, distributed deployment, cross-service tracing or remote failure.

The reverse is also true.

A system can have twenty repositories, twenty containers and twenty deployment pipelines while every service uses one shared database user, one shared schema and one shared domain library.

That is physical distribution without genuine autonomy.

The architecture has paid the cost of distribution while preserving the coupling of a poorly modularised monolith.

A process boundary does not automatically create an application boundary. Data ownership is one of the clearest tests.

Team responsibility should follow the same shape.

A useful alignment is:

BUSINESS CAPABILITY
        │
        ▼
APPLICATION BOUNDARY
        │
        ▼
DATA OWNERSHIP
        │
        ▼
TEAM RESPONSIBILITY

The team responsible for the capability should be able to change its code, evolve its internal data model, deploy, operate the application and respond to incidents within agreed organisational controls.

This does not mean teams never collaborate. It means routine internal change should not require an interdepartmental summit around one nullable column.

A practical ownership test

For any important data, ask:

Meaning

Who defines what this field or record means?

Authority

Who is allowed to originate or change this fact?

Rules

Who enforces the invariants around it?

Persistence

Which application controls the storage schema?

Access

How do other components obtain the information?

Contracts

Which APIs, events, views or exports are publicly supported?

Copies

Which systems store derived copies, and how are they refreshed?

Failure

What happens when a copy is stale or unavailable?

Operations

Who owns migrations, backup requirements and restoration testing?

Lifecycle

Who decides retention, archival and deletion?

Warning signs include:

  • several applications writing the same table
  • one service updating another service’s schema
  • consumers querying undocumented internal columns
  • schema changes requiring coordinated deployment everywhere
  • nobody knowing which copy is authoritative
  • cross-service reports running against production databases
  • one service publishing facts another service owns
  • every boundary sharing the same domain object
  • credentials allowing all services to access all tables
  • business rules being duplicated across several writers

None of these automatically proves the architecture is wrong. Legacy systems and transitional designs have constraints.

But they should be explicit compromises rather than invisible defaults.

A simple furniture-store ownership map might look like:

CATALOG
├── product descriptions
├── categories
├── listed prices
└── publication state

BASKET
├── active baskets
├── basket items
└── basket lifecycle

INVENTORY
├── stock positions
├── reservations
└── availability rules

ORDER
├── accepted orders
├── order lines
├── purchase-time descriptions
├── purchase-time prices
└── order lifecycle

PAYMENT
├── payment attempts
├── authorisation state
├── capture state
└── provider references

These boundaries collaborate through supported operations and events. The exact number of services is not the important part.

The important part is that each fact has one clear owner.

My old model was:

data belongs to
the database containing it

The model I am keeping is:

DATA OWNER
├── defines meaning
├── enforces rules
├── controls writes
├── controls internal schema
├── publishes supported contracts
├── manages evolution
└── accepts operational responsibility

Other components may hold copies, but a copy does not automatically become another authority.

The physical database arrangement sits underneath the ownership model. Private tables, schemas, databases or servers can all support it. The required degree of isolation depends on team size, system complexity, security requirements, availability needs and operational maturity.

So when I draw an application boundary, I should not stop at separate code, process or deployment.

I should ask:

Can this boundary define and change its model without another component reaching into its state?

If the answer is no, the database may be revealing the architecture more honestly than the service diagram.

The real boundary is wherever authority over behaviour and data changes hands.

Everything else is just a box.

References and further reading

Bounded contexts

Martin Fowler: Bounded Context
Introduces the domain-driven design concept of dividing a large domain model into explicit contexts within which terms and models have precise local meanings.

Data ownership and microservice boundaries

Microsoft: Data sovereignty per microservice
Explains the principle that each microservice owns its domain data and logic, and that other services access that data through synchronous or asynchronous contracts rather than direct database access.

Martin Fowler: Microservice trade-offs
Discusses strong module boundaries, decentralised data management, independent deployment and the distributed-system costs introduced by microservices.

Chris Richardson: Database per service
Describes keeping each service’s persistent state private and explains logical isolation options including private tables, schemas, databases and database servers.

Microservice architecture

Martin Fowler and James Lewis: Microservices
Describes common characteristics of microservice architecture, including componentisation around business capabilities and decentralised data management.

Cross-service queries

Chris Richardson: API Composition
Describes implementing cross-service queries by retrieving information from its owners and composing the results in application code.

Chris Richardson: Command Query Responsibility Segregation
Describes maintaining query-specific views from events when information from several service-owned databases must be queried efficiently.

Relational integrity and transactions

PostgreSQL 13: Data Definition
Documents relational schemas, tables, constraints, privileges and other structures a data-owning application can use to enforce its local model.

PostgreSQL 13: Constraints
Explains NOT NULL, CHECK, uniqueness, primary-key and foreign-key constraints used to protect relational data integrity.

PostgreSQL 13: Transactions
Introduces grouping several SQL operations into one all-or-nothing transaction within a database.

PostgreSQL 13: Concurrency Control
Documents PostgreSQL’s transaction isolation and concurrency model for maintaining consistent access to data within a database.