Refactor 3: Monolithic Logic → Aggregates + Events + Strategies + Debuggable Graph¶
Concept Position¶
flowchart TD
family["Python Programming"] --> program["Python Object-Oriented Programming"]
program --> module["Module 04: Aggregates, Events, and Collaboration Boundaries"]
module --> concept["Refactor 3: Monolithic Logic → Aggregates + Events + Strategies + Debuggable Graph"]
concept --> capstone["Capstone pressure point"]
flowchart TD
problem["Start with the design or failure question"] --> example["Study the worked example and trade-offs"]
example --> boundary["Name the boundary this page is trying to protect"]
boundary --> proof["Carry that question into code review or the capstone"]
Read the first diagram as a placement map: this page is one concept inside its parent module, not a detached essay, and the capstone is the pressure test for whether the idea holds. Read the second diagram as the working rhythm for the page: name the problem, study the example, identify the boundary, then carry one review question forward.
Goal¶
Restructure the monitoring system so it is:
- aggregate-centered (consistency enforced in one place),
- event-capable (decoupled reactions),
- strategy-driven (easy to add rule kinds),
- and debuggable (inspectable object graph and projections).
This refactor turns a “working blob” into a maintainable service shape.
Where This Fits¶
Running example: a monitoring service that fetches metrics, evaluates rules, and emits alerts. In earlier modules we refactored toward a layered design (domain/application/infrastructure) with explicit roles. From M03 onward, we tighten data integrity and lifecycle semantics so the system stays correct under change.
1. Starting Smells¶
You are likely starting with one or more of: - orchestrator owns everything and enforces every invariant, - rule evaluation logic is a large conditional ladder, - side effects are mixed with domain mutations, - debugging requires stepping through a long loop.
We are going to separate responsibilities without losing correctness.
2. Target Architecture Snapshot¶
Target structure:
domain/:types.py(semantic values)rules.py(typestate)policy.py(aggregate root)-
events.py(domain events) -
application/: services.py(orchestrators)translate.py(DTO → domain)-
ports.py(interfaces) -
infrastructure/: - adapters (metric fetcher, storage)
- event bus wiring
- projections/read models
Plus tests for invariants, transitions, and wiring.
3. Refactor Steps¶
Step 1 — Introduce an aggregate root¶
- Create
MonitoringPolicythat owns active/retired rules. - Move cross-object invariant checks into root methods.
Step 2 — Introduce strategies for evaluation¶
- Define
RuleStrategyprotocol. - Implement at least one strategy (threshold).
- Update orchestrator to call strategy, not
if/elif.
Step 3 — Emit domain events from aggregate operations¶
- On activate/retire, return events (
RuleActivated,RuleRetired).
Step 4 — Add an in-process event bus and one projection¶
- Implement a tiny bus (M04C35).
- Add a projection updated on events.
Step 5 — Add debug views¶
policy.debug_view()and/or a projection debug snapshot.
Refactor in thin slices and keep tests green.
4. Tests That Prove the Refactor Is Correct¶
- Aggregate invariant tests (duplicates, illegal retire, etc.).
- Strategy tests (pure evaluation behavior).
- Event emission tests (events only on success).
- Bus/projection tests (handler wiring, deterministic updates).
- End-to-end: orchestrator cycle produces expected alerts and debug view is coherent.
The key: tests should validate contracts, not internal implementation details.
5. “Done” Definition¶
You are done when:
- invariants are enforced in one aggregate root,
- adding a new rule type requires adding a strategy (not editing a ladder),
- reactions to state changes are handled via events/handlers,
- you can generate a debug view without stepping through the whole loop.
Practical Guidelines¶
- Refactor by introducing new abstractions (aggregate/strategy/event) while keeping behavior unchanged.
- Keep the domain pure; wire infrastructure at the composition root.
- Use events to decouple side effects and projections from core mutations.
- Invest in debug views early; they pay back immediately in maintainability.
Exercises for Mastery¶
- Implement the aggregate root and move at least two invariants into it with tests.
- Extract one rule evaluation path into a strategy and delete an
if/elifbranch from the orchestrator. - Add a projection updated from events and assert it in an integration test.