Course outline

Software Architecture / Foundations: promises, structure, and ownership / Lesson 6

Protect invariants at the data owner

Use authoritative ownership and atomic updates to explain why checking capacity is different from reserving it.

About 4 min readContent reviewed
On this page

Lesson goals

  • Identify an invariant and the boundary that must enforce it.
  • Reason about races, transaction scope, and read-model limitations.

Write the rule that must always hold

For a workshop with capacity ten, the count of active reservations must never exceed ten. This invariant belongs to the authoritative reservation owner. A UI that disables a button is useful guidance but cannot enforce it against another client or concurrent request. Distinguish stored facts from derived views: remaining seats may be computed from capacity and active reservations, while a dashboard cache can lag. Define which state transitions count as active, including pending holds and expired reservations, before choosing a storage representation.

A read followed by a write can admit two callers

If two callers both read one remaining seat and then create a reservation, each local check can pass while the combined result violates capacity. The defect is in the check-and-update boundary, not in the arithmetic. A transaction can protect the operation only when its isolation, locking, or conditional-write behavior actually prevents the race. Do not assume that placing statements between begin and commit provides every desired guarantee. Database-specific behavior and conflict handling need an explicit design and an isolated test in the chosen engine.

text An intentionally unsafe interleaving
A reads remaining = 1
B reads remaining = 1
A inserts active reservation
B inserts active reservation
Result: two reservations competed for one seat

This is a reasoning trace, not executable SQL or a measured engine result. It shows why both successful reads do not establish a safe combined update.

Make the decision and update one protected operation

One possible design conditionally decrements an authoritative remaining counter when it is positive and records the reservation in the same transaction. Another serializes changes through a workshop aggregate or uses an appropriate unique allocation model. Each has costs, including contention, conflict retries, and recovery requirements. The conditional SQL fragment illustrates intent rather than prescribing a universal schema. It must be paired with reservation insertion and tested for rollback behavior; decrementing alone can lose capacity when the rest of the workflow fails.

sql An illustrative conditional allocation fragment
UPDATE workshop_capacity
SET remaining = remaining - 1
WHERE workshop_id = :workshop_id AND remaining > 0;

A successful allocation requires checking the affected-row result and completing the associated reservation transaction. Parameter syntax and concurrency guarantees depend on the database and driver; this fragment is non-runnable.

Keep one owner for a business write

If reporting and booking both write reservation state directly, they can enforce different rules and make ownership ambiguous. Offer a deliberate command interface or controlled shared transaction boundary instead of letting every consumer mutate tables. Read replicas and projections can serve queries, but their lag must not authorize the final seat allocation. Ownership is a responsibility rule, not necessarily a separate database server. A modular application can maintain separate logical owners in one database when access paths and invariants are clear.

Verify the invariant after conflicts and recovery

Test concurrent attempts, duplicate requests, cancellations, and failures between related writes in an isolated engine environment. The expected result is the invariant and a coherent caller outcome, not merely an absence of exceptions. Reconciliation should identify mismatches without automatically rewriting records from an untrusted projection. Keep a documented correction process for the authoritative owner. This course supplies conceptual traces and fragments, so it claims no database concurrency execution; actual guarantees remain subject to the chosen engine, isolation level, schema, and implementation.

Common errors and fixes

The last seat is reserved twice.

Why it happens
Capacity was checked outside the protected update boundary.
How to fix it
Choose an engine-supported atomic allocation strategy and handle conflicts.
Prevent it next time
Test a controlled concurrent last-seat scenario.

A dashboard cache authorizes a reservation.

Why it happens
A derived read model was treated as authoritative state.
How to fix it
Route the command to the reservation owner for validation.
Prevent it next time
Label query freshness and write ownership explicitly.

A counter decrements but no reservation exists.

Why it happens
Related writes were not atomic or recoverable.
How to fix it
Place related changes in the intended transaction or design reconciliation.
Prevent it next time
Exercise failures between each related write.

Check your understanding

1. Can a unique reservation identifier prevent overbooking?

Show worked solution

It prevents duplicate identifiers, but two different identifiers can still consume the last seat. The constraint must model the actual capacity invariant, through conditional allocation, locking, allocated seat identity, or another verified strategy. Uniqueness solves only the rule expressed by the unique key.

2. Why should a failed reservation transaction restore a decremented counter?

Show worked solution

The counter and reservation describe the same allocation. Keeping only the decrement makes available capacity disappear without a corresponding booking. In a single transaction, rollback should remove both effects; in a split workflow, a durable state machine and repair process must account for the partial state. Test the chosen design rather than assuming eventual correction.

Key takeaways

  • The authoritative write boundary must protect the business invariant.
  • A stale query can inform display without being safe for allocation.

Official references