Capstone Architecture Guide¶
Page Maps¶
graph LR
family["Python Programming"]
program["Python Object-Oriented Programming"]
section["Capstone"]
page["Capstone Architecture Guide"]
capstone["Capstone evidence"]
family --> program --> section --> page
page -.applies in.-> capstone
flowchart LR
owner["Name the authoritative owner"] --> support["Name the supporting boundaries"]
support --> derived["Name the derived views"]
derived --> pressure["Apply runtime or persistence pressure"]
pressure --> proof["Check that ownership still did not move"]
Use this guide when the real question is not "what does the code do" but "why is this responsibility here instead of somewhere else?" The capstone is small, but its shape is strict. That strictness is the lesson.
The architectural claim¶
The monitoring system is built so that domain truth remains visible under change:
- the aggregate decides whether rules may be added, activated, evaluated, or retired
- policy objects vary how evaluation works without taking ownership away from the aggregate
- emitted events describe what happened after the domain acts
- projections and read models derive views from those events
- runtime code coordinates effects and persistence around the domain
- the unit of work protects rollback and persistence boundaries without becoming business logic
If that ownership split drifts, the system may still run, but the course stops teaching good OOP.
The six boundaries that matter¶
| Boundary | Main file | Owns | Does not own |
|---|---|---|---|
| application surface | application.py |
learner-facing commands and scenario-level composition | lifecycle truth or evaluation semantics |
| aggregate | model.py |
rule lifecycle, invariants, alert creation, and domain events | adapters, publication, or persistence flow |
| evaluation seam | policies.py |
interchangeable rule-evaluation behavior | authority over when rules may exist or change state |
| derived views | read_models.py and projections.py |
active-rule indexes, incident history, and open-incident views | the source of truth |
| runtime coordination | runtime.py |
sourcing samples, publishing alerts, applying projections, and committing work | rule truth |
| persistence boundary | repository.py |
snapshots, rollback, and commit mechanics | hidden domain policy |
Read the architecture in the right order¶
model.pypolicies.pyevents.pyread_models.pyandprojections.pyruntime.pyrepository.pytests/test_policy_lifecycle.pyandtests/test_runtime.py
That order keeps the authoritative owner visible before you introduce coordination and rollback pressure.
One design question per boundary¶
| Boundary | Ask this question |
|---|---|
| aggregate | Which state changes would be dangerous if another layer could perform them directly? |
| evaluation seam | Which variation can change without rewriting the aggregate? |
| events | What happened that downstream readers may know without becoming authoritative? |
| projections | Which views are helpful to read but dangerous to trust as source state? |
| runtime | Which effects must happen around the domain without redefining it? |
| repository and unit of work | Which failures should roll back the saved state without moving policy into storage code? |
The architecture under real pressure¶
The runtime tests make the design pressure visible:
test_runtime_coordinates_activation_projection_and_alert_publicationshows that runtime code can activate rules, run evaluations, publish alerts, and update projections without becoming the rule owner.test_runtime_retirement_updates_read_modelsshows that a domain retirement event is what clears derived views, not a projection deciding to mutate the aggregate.test_runtime_rolls_back_projection_updates_when_sink_failsshows why rollback belongs with the unit of work and runtime coordination, not inside the aggregate.
The lifecycle tests make the aggregate pressure visible:
- the policy emits registration, activation, and retirement events
- duplicate active metric/window signatures are blocked in the aggregate
- timezone-aware UTC timestamps are created at the domain event layer
- retirement clears open incidents through downstream event handling
Dependency direction that keeps the design honest¶
| Surface | May depend on | Should not depend on |
|---|---|---|
application.py |
runtime, domain-facing command objects, and snapshots | projection internals as a source of truth |
model.py |
value types, domain events, and evaluation abstractions | runtime sinks, CLI surfaces, or repository details |
policies.py |
rules and samples | projection or persistence mechanics |
runtime.py |
repository, evaluator, sink, active-rule index, and incident ledger | projection state as authority for domain decisions |
read_models.py and projections.py |
emitted events | aggregate mutation paths |
repository.py |
aggregate snapshots and rollback coordination | business rules hidden from review |
Drift signals to catch early¶
runtime.pystarts deciding whether a rule is legal instead ofmodel.py- a projection must be consulted before you know whether the aggregate may change
- a new evaluation mode forces edits in both
model.pyandruntime.py - repository code becomes the only place where a domain rule is still visible
- tests prove behavior, but you can no longer point to a clear owner in the code
Change placement rules¶
| If the change is... | Start here | Why |
|---|---|---|
| new lifecycle restriction | model.py |
lifecycle authority belongs to the aggregate |
| new evaluation mode | policies.py |
replaceable behavior should stay at the evaluation seam |
| new derived view | read_models.py or projections.py |
projections must stay downstream of events |
| new source or sink | runtime.py |
coordination belongs outside the domain |
| stronger rollback or persistence policy | repository.py |
storage pressure should adapt to the model |
| clearer public scenario command | application.py |
the learner-facing surface should stay explicit |
A fast architecture review loop¶
- Name the current responsibility.
- Point to the authoritative file.
- Point to the nearest supporting file.
- Point to the derived view, if any.
- Point to the test or bundle that would fail first if ownership moved.
If you cannot do all five, the architecture answer is still blurry.
Exit check¶
Leave this page only when you can say:
The domain owner is _, the support boundary is , the derived view is _, and the first proof route I would check is .