CO6 Week 11 of 12
Bounded modules and numbers before boxes
By Thursday the codebase is a modular monolith whose boundaries are enforced by failing tests, the payment gateway sits behind an anti‑corruption layer, and each team has a capacity plan for 50× load with its arithmetic shown.
Lecture topics
- Bounded contexts, anti-corruption layers, and modular monoliths.
- Capacity estimation, caching, sharding, and operational metrics.
Skill focus
Draw bounded contexts and an anti-corruption layer for Waypoint.
Thread: Monday's practical writes module dependency rules as tests and watches them fail on a planted violation → Tuesday's skill block builds the payment and sensor ACLs → Wednesday's lecture explains bounded contexts and anti‑corruption layers, then derives architecture from capacity numbers → Thursday's skill block produces the 50× capacity plan that feeds the Week 12 dossier.
Practical — P11: architecture rules and dependency testing
Tooling: ArchUnit (com.tngtech.archunit:archunit-junit5, a current 1.x release) reads compiled bytecode. If the lab cannot add it, use the no‑library fallback below, which scans import lines and is weaker (it misses fully qualified names).
| Min | Step | In class | Your task |
|---|---|---|---|
| 0–10 | Build check | — | Build green. |
| 10–20 | Primer | A boundary that is not tested will erode. Show the target layout: one top‑level package per context (operations, booking, pricing, payment, notification), each with a public api sub‑package and package‑private internals; infrastructure and app outside. |
— |
| 20–40 | Step 1: review modules | Move classes into contexts; draw the package dependency graph from Week 5's diagram. | Move and redraw. |
| 40–60 | Step 2: define rules | Write the rules as sentences first: R1 domain code never depends on infrastructure. R2 contexts call each other only through ..api... R3 no cycles between contexts. R4 only app names infrastructure classes. |
Agree 4–6 rules. |
| 60–90 | Step 3: implement the tests | Code below. Plant one violation per rule on a branch to prove each test can fail, then revert. | Implement; record each planted failure message. |
| 90–100 | Recap + evidence | Q: "Which rule would have caught the Week 5 starter BookingService?" |
Commit W11-P11; save the evidence log. |
@AnalyzeClasses(packages = "edu.klef.aoop.parking")
class ArchitectureTest {
@ArchTest static final ArchRule domainIsPure = noClasses().that().resideInAPackage("..domain..")
.should().dependOnClassesThat().resideInAPackage("..infrastructure..");
@ArchTest static final ArchRule noCyclesBetweenContexts = slices()
.matching("edu.klef.aoop.parking.(*)..").should().beFreeOfCycles();
}
// Fallback without libraries (weaker: checks import lines only)
@Test void domainDoesNotImportInfrastructure() throws IOException {
try (Stream<Path> files = Files.walk(Path.of("src/main/java/edu/klef/aoop/parking"))) {
List<Path> bad = files.filter(p -> p.toString().contains("domain") && p.toString().endsWith(".java"))
.filter(p -> read(p).contains("import edu.klef.aoop.parking.infrastructure"))
.toList();
assertEquals(List.of(), bad);
}
}
Acceptance checks: ≥ 4 rules, each shown to fail on a planted violation; main branch green; each context's tables carry its prefix (booking_, pricing_, billing_) and no context reads another's tables.
Deliverable (P): commit W11-P11, docs/week11/architecture-rules.md with each rule, its test, and the planted‑failure message.
Skill A — S21: anti‑corruption layers for bounded‑context integration
| Min | Step | In class | Your task |
|---|---|---|---|
| 0–10 | Do‑now | "The vendor sends amounts as \"120.50\" rupees in a string. Where should that be converted?" |
Answer. |
| 10–20 | Primer | Two external models (fictional vendors): payment gateway "PayFast" `{ "txn_ref", "amt": "120.50", "state": "SUCCESS | FAILED |
| 20–40 | Step 1: compare models | Table: their term → our term (amt → Money; state → PaymentOutcome; bay → SpotId via lookup; occ → SpotStatus). |
Fill the translation table. |
| 40–60 | Step 2: design the ACL | Translator lives in infrastructure, implements our port, and is the only class that imports vendor types. Amount: new BigDecimal(amt).movePointRight(2).longValueExact() → paise; PENDING → Unknown (Week 7). |
Write the ACL class diagram. |
| 60–90 | Step 3: build and test | Tests for valid input, unknown state, amount with 3 decimals (reject), unknown bay code (reject and alert). | Implement PayFastGatewayAdapter and SensorEventTranslator; add an architecture rule: only these classes may import vendor packages. |
| 90–100 | Recap + present | One team shows how a vendor format change touches only the ACL. | Present. |
Deliverable (S21): context map (UML package diagram of the week), both ACL classes with ≥ 6 translation tests, the new architecture rule.
Lecture — L21 + L22: bounded contexts, ACLs, modular monolith; capacity, caching, sharding, SLOs
| Min | Step | In class | Your task |
|---|---|---|---|
| 0–10 | Do‑now | "What does 'spot' mean to the sensor team, and to the booking team?" | Answer. |
| 10–15 | Hook | Sensor team: a bay with a device, battery and occupancy flag. Booking team: an ID you hold for a time window. Same word, different models. | — |
| 15–30 | Concept A: contexts and boundaries | Bounded context = where a vocabulary is consistent. Our contexts: Parking Operations, Booking, Pricing, Payment/Billing, Notification, Customer. Context map relationships: customer/supplier (Booking uses Pricing), anti‑corruption layer (our side translates an external model so it cannot leak in). Modular monolith: one deployable, hard internal boundaries, each module owns its schema. Extract a module into a service only when a measured trigger is met (for example, it needs to scale separately, deploy on a different cadence, or its failure must be isolated); state the number that triggers it. | Draw the context map. |
| 30–50 | Apply A | Teams draw the context map for Smart Parking with relationship labels and mark where translation happens. | Draw. |
| 50–55 | Bridge | "The city wants 50 facilities, not 1. Which box breaks first?" | Guess. |
| 55–70 | Concept B: numbers first, then boxes | Worked estimate (table below). Consistency per operation: availability shown to drivers may be up to 5 s stale (cache); taking a spot must be strongly consistent (conditional update). Caching availability per facility; sharding bookings by facility_id; hotspot risk at a stadium facility. Observability: structured logs with requestId and bookingId; metrics on four decisions (allocation success rate, payment outcome mix, breaker state, outbox lag); SLOs such as 99.9% monthly availability (≈ 43 minutes of downtime in a 30‑day month) and p95 allocation latency under 200 ms. |
Work through the table. |
| 70–90 | Apply B | Teams redo the estimate with their own assumptions, then name the first component that must change at 50×. | Compute. |
| 90–100 | Recap | Q1: Define a bounded context in one sentence. Q2: What does an ACL protect? Q3: Which load dominates in our system, and why? | Answer. |
| Quantity | Assumption / arithmetic | 1× | 50× |
|---|---|---|---|
| Spots | 40 facilities × 500 spots | 20,000 | 1,000,000 |
| Sessions per day | 4 per spot per day | 80,000 | 4,000,000 |
| User requests per day | ≈ 11 per session (5 searches, book, entry, exit, pay, 2 notifications) | 880,000 | 44,000,000 |
| User requests per second, average | per day ÷ 86,400 | ≈ 10 | ≈ 509 |
| User requests per second, peak hour | 20% of daily traffic in 1 hour ÷ 3,600 | ≈ 49 | ≈ 2,444 |
| Sensor heartbeats per second | 1 per spot per minute ÷ 60 | ≈ 333 | ≈ 16,667 |
| Booking storage per year | 300 bytes × sessions × 365 | ≈ 8.8 GB | ≈ 438 GB |
Reading: sensor traffic, not drivers, dominates at 50×, so the first box to add is a buffered ingestion queue that stores only status changes. All numbers are illustrative assumptions for teaching; teams must state and defend their own.
Deliverable (L): the context map and the team's own capacity table.
Skill B — S22: capacity for 50× load, caching and sharding trade‑offs
| Min | Step | In class | Your task |
|---|---|---|---|
| 0–10 | Do‑now | "At 2,444 requests per second, how many availability reads hit the database if we cache each facility's count for 5 s?" | Estimate. |
| 10–20 | Primer | 50× means 50 times today's assumed load; every number must trace back to a stated assumption. | — |
| 20–40 | Step 1: workload | Teams fix their own assumptions (facilities, spots, turnover, peak share, request mix). | Fill the workload sheet. |
| 40–60 | Step 2: caching and sharding options | Cache availability (TTL vs staleness); shard bookings by facility vs by vehicle (which queries become cross‑shard?); read replica for reports. | List benefit and cost of each. |
| 60–90 | Step 3: scaling plan | Require: peak RPS, storage per year, the bottleneck, the components added and why, consistency per operation, and the 4 metrics with alert thresholds. | Write the one‑page plan. |
| 90–100 | Recap + present | Teams present one assumption and its effect on the plan. | Present. |
Deliverable (S22): docs/week11/capacity-plan.md (worksheet + plan), reused as one of the six dossier artefacts in Week 12.