امنیت، هویت، استقرار و تابآوری · فصل ۶ — Chaos، Feature Flags، Rollout و ADR · 107
Module Data Ownership and Cross-module Boundaries
Define a module ownership contract that names each module’s authoritative data and invariants, forbids unowned writes, chooses explicit cross-boundary read/command/event contracts, and records where shared identity does not imply shared domain meaning. میتواند برای «Module Data Ownership و Cross-module Boundaries» run/release model و signals/recovery checks لازم را برای یک سرویس محدود طراحی کند.
Lesson summary
Define a module ownership contract that names each module’s authoritative data and invariants, forbids unowned writes, chooses explicit cross-boundary read/command/event contracts, and records where shared identity does not imply shared domain meaning.
میتواند برای «Module Data Ownership و Cross-module Boundaries» run/release model و signals/recovery checks لازم را برای یک سرویس محدود طراحی کند.
This lesson produces
میتواند برای «Module Data Ownership و Cross-module Boundaries» run/release model و signals/recovery checks لازم را برای یک سرویس محدود طراحی کند.
Who it is for
Foundation
Terminology bridge, guided evidence table and one worked Module Data Ownership and Cross-module Boundaries case.
Repair
Repair only the OPS-014 misconception exposed by its dedicated negative probe; do not replay another C06 lesson.
Experienced
Experienced learners may compress definitions but must still produce the full Module Ownership Boundary Contract v1 and independent transfer evidence.
Prerequisites
1. The problem: access is not ownership
In a modular system, the fact that Ordering can reach Catalog data does not mean Ordering owns that data. Ownership means one module is the decision authority for authoritative state and its invariants. When two modules mutate the same state without an explicit boundary, rule changes create ambiguity about which write is valid and which module must repair inconsistency.
This lesson produces Module Ownership Boundary Contract v1. For each module, record authoritative data, protected invariants, allowed read/command/event paths, forbidden direct writes, and the translation responsibility at the consumer boundary.
2. Separate logical boundaries from physical topology
A bounded context or module does not have to be a separately deployed microservice. Several modules can share a process or database while retaining clear logical ownership. Conversely, separate databases do not prove low coupling if a consumer can still write directly into the owner's store.
The first question is therefore not "how many deployments exist?" It is: which module is allowed to change authoritative state and enforce the invariant?
3. A shared domain model is not automatically a benefit
Reusing one Customer class across Identity, Ordering, and Billing can hide three different meanings under one name. Identity may care about profile and credential state, Ordering about delivery/contact snapshots, and Billing about payer/account rules. The identifier may be shared while attributes, behavior, and invariants remain context-specific.
Record shared identity separately from shared domain meaning. If contexts agree on an identifier but not on behavior, each context keeps its own model and translates at the boundary.
4. Define ownership through invariants
A list of tables is not an ownership contract. For each authoritative data set, name the invariant the owner protects. Catalog may own ProductPrice and enforce that an active price cannot be negative. Ordering may keep a price snapshot in an order line, but it does not gain the right to change Catalog's authoritative price.
This also explains why duplicated data is not automatically a defect. A consumer projection or snapshot can be intentionally duplicated because its meaning and lifecycle belong to the consumer context.
5. Owner Write API: one controlled mutation path
Authoritative state needs a write path controlled by the owner. The path may be an in-process application service, command handler, or network API; the technology is secondary. The important property is that the owner executes the invariant before changing authoritative state.
If Ordering needs inventory reserved, it should not update Catalog storage directly. It issues a meaningful command such as ReserveInventory; Catalog decides whether the requested change is valid.
6. Three common cross-module read patterns
Cross-module reads can use a synchronous request, a published read model, or a consumer-local projection. The choice depends on latency, consistency, availability, and ownership. None is universally correct.
A synchronous request can fit data that must be current at decision time. A local projection can fit high-volume reads or stronger runtime independence. In either case, the consumer should not treat the owner's internal storage shape as a public contract.
7. Published language and the anti-corruption layer
A boundary should separate the owner's published language from the consumer's internal model. A translation adapter or anti-corruption layer converts the external message or DTO into the consumer's local concepts.
That translation limits change propagation. If Catalog renames an internal field while preserving its published contract, Ordering should not need to change.
8. Events are an option, not a rule
An integration event is useful for publishing a fact that has already happened, such as ProductPriceChanged. A consumer may use it to update a local projection. But events are not mandatory for every interaction. When an operation needs an immediate owner response, a synchronous command/request may be clearer.
The contract should name which boundaries are synchronous, which are asynchronous, and the tradeoff. Merely labeling an architecture "event-driven" does not prove ownership.
9. Failure probe: one shared table, two writers
Suppose Catalog and Ordering both update available_stock in one shared table. Even if transactions succeed, ownership is ambiguous: which module defines the stock invariant? During a race or rule change, which write is authoritative?
This shortcut must fail. Assign one invariant owner and route the other module through a command contract, or redesign the state. "Both own it" is not a defensible answer for one authoritative invariant.
10. Failure probe: separate databases with a foreign write
Physical separation can help but it is not proof. If Ordering has a direct connection to the Catalog database and executes UPDATE, coupling has only moved behind a network boundary. Schema ownership and deployment topology are different concepts.
Acceptable evidence shows that the foreign write path is removed and the mutation now passes through the owner-controlled path. A new diagram alone does not close the boundary.
11. Consumer projections and intentional duplication
A consumer may need a local projection to support independent reads. The owner remains the source of the domain fact, while the consumer owns its local projection and its lifecycle. The consumer must not use the projection as a second authoritative write path.
For each projection, record the update source, freshness/consistency expectation, and translation rule. This prevents a cache or read model from silently becoming a second source of truth.
12. Build the evidence table row by row
For each boundary, record Module, Owned Data, Owned Invariant, Shared Identity, Allowed Read, Allowed Command, Event Contract, Forbidden Direct Write, Translation Rule, Evidence, and Open Boundary Risk. A conclusion must be tied to observed local evidence or a declared authority, not architecture intuition.
If you cannot prove that a direct write path is gone, keep the boundary OPEN. A green build or broad test suite is not ownership evidence by itself.
13. Repair proportionally
A valid repair does not necessarily require a microservice split. Sometimes replacing a direct repository reference with an application command is enough. Sometimes a shared entity must become two context-specific models. Sometimes a local projection is built from a published event.
The criterion is clear ownership of state and invariants plus an explicit consumer contract, not maximum architectural complexity.
14. Lab: derive the ownership contract from the fixture
OPS014-LAB1 contains eight synthetic fixtures. For each case, derive the owner and invariant, then identify synchronous/asynchronous boundaries, projection, translation, and forbidden writes. The starter does not contain the completed decision.
One case is deliberately misleading: databases are separate but a foreign write still exists. Another replaces an event with a request and requires a tradeoff decision rather than automatic rejection. Fixture checks prove fixture integrity only; the completed artifact still needs row-level evidence.
15. Transfer: rebuild the boundary in a second domain
The transfer case changes module names, shared identifiers, and interaction style. Do not copy the first-case conclusion. Start again from the invariant, assign the owner, define published language, and independently choose a projection or request/event contract.
The result is defensible when it shows that shared identity does not imply a shared domain model, physical separation does not automatically establish ownership, and every authoritative write passes through an owner-controlled path. Any unsupported boundary remains OPEN.
Assessment
There is no answer key in this lesson pack; answer it yourself and mark honestly. "Needs review" sends the lesson to your review queue.
Q1 retrieval
What distinguishes shared identity from shared domain meaning?
Q2 diagnosis
Two modules write the same inventory table. What must be established before accepting the boundary?
Q3 application
A consumer needs low-latency reads of owner data. Name a boundary-preserving option.
Q4 tradeoff
When can a synchronous request be valid instead of an event?
Q5 transfer
In a new domain, databases are separate but a consumer has UPDATE access to the owner store. Is ownership proven?
Assessment rubric
A passing response must:
- assign one explicit owner to each authoritative invariant;
- distinguish shared identity from shared domain meaning;
- reject direct foreign writes even when databases are physically separate;
- choose synchronous request, event, or projection based on the boundary need rather than a universal pattern;
- identify translation responsibility where published language differs from the consumer model;
- keep unsupported ownership or write-path claims OPEN;
- rebuild the transfer case from the changed domain evidence.
References & freshness
Case studies
Lesson visuals
Lab files
This lab uses only deterministic synthetic module metadata. No network, production database, credential, customer record, or external service is required.
For each fixture:
- identify the authoritative aggregate/data owner and invariant owner;
- determine whether an owner-controlled write path exists;
- identify shared identity versus shared domain meaning;
- record the published language and any translation boundary;
- classify consumer projection, synchronous boundary, asynchronous boundary, and schema ownership;
- reject foreign direct writes even when stores are physically separate;
- keep unsupported conclusions OPEN.
run_checks.py verifies fixture integrity only. Implement your own reasoning in starter.py and build the contract row by row.
Lesson media & slides
Teaching files (download)