Overview
Chapter 03: AI-Assisted System Architecture & C4 Modeling
Playbook Track: 04 – AI Coding & Software Engineering (AI-DLC & Autonomous Developer Workflows)
Target Audience: Year 1 Computer Science & Software Engineering Students Core Tooling Stack: Gemini 2.5 Pro (Architecture Reasoning), C4 Model (Simon Brown), Mermaid JS, Architecture Decision Records (ADR), Python 3.11+
Delivery Status: 🔍 Ready for Review (Tier 1 Markdown)
1. The Big Picture & Real-World Analogy
Building a City vs. Building a Shanty Town
Imagine an architect designing a new city:
- The Shanty Town: There is no zoning. A noisy, smoke-belching steel refinery is built right next to a kindergarten playground. Power lines are tangled like spaghetti between rooftops. If someone wants to repair a water pipe on Main Street, they accidentally cut off the electrical power to the entire hospital!
- The Well-Designed City: The city is split into clear zones: residential areas, commercial districts, and industrial parks. Underground utility conduits have standardized connection points. If the water department needs to fix a pipe in Sector 4, only Sector 4 is affected, while the rest of the city functions normally.
In software engineering, without strict architecture, an AI coding agent acts like the shanty-town builder:
- It will import raw database connections straight into HTML web pages.
- It will create circular dependencies (Module A imports Module B, which imports Module A).
- It will cram 4,000 lines of spaghetti code into a single file!
The C4 Model is like zooming in on Google Maps:
- Level 1 (Context): The satellite view of the entire continent (Who uses the system and external payment providers).
- Level 2 (Containers): The city boundaries (Deployable apps, databases, and message queues).
- Level 3 (Components): The city blocks and buildings (Internal services, controllers, and repositories).
- Level 4 (Code): The architectural floor plan of an individual room (Classes, functions, and interfaces).
2. Engineering Jargon Demystifier Table
| Industry Term | What It Actually Means | Freshman Student Analogy |
|---|---|---|
| C4 Model | A hierarchical way to visualize software architecture at 4 levels: Context, Containers, Components, Code. | Zooming in on Google Maps from World -> Country -> City -> Street address. |
| Container | A standalone deployable software unit (e.g. a FastAPI web server, a PostgreSQL database, a Redis cache). | A physical shipping container or a dedicated building in a campus. |
| Component | A modular piece of code inside a container (e.g. PaymentController, UserRepository). |
A department inside a building (e.g. the Billing Office vs the Admissions Office). |
| ADR (Architecture Decision Record) | A short text document explaining why a technical choice was made, what alternatives were considered, and what consequences follow. | A lab notebook entry explaining why you picked a binary search tree instead of a linked list. |
| Coupling | How much one software module depends on another. High coupling means changing Module A breaks Module B. | Having your phone permanently soldered to its charger cable instead of using a detachable USB-C cable. |
| Cohesion | How closely related all the code inside a single module is. High cohesion means a module does one thing well. | A tool chest where all the wrenches are organized in one drawer. |
| Acyclic Dependency (DAG) | A dependency rule where imports must flow in one direction without any loops ($A \to B \to C$, never $C \to A$). | A family tree: your parents can have children, but children cannot be parents to their own ancestors! |
| God Class / God File | An anti-pattern where one gigantic file (3,000+ lines) does everything in the application. | A single Swiss Army knife that has 500 attachments and is too heavy to hold. |
3. The 5-Minute Micro-Lab: The Circular Dependency Detector
Circular imports crash Python programs with ImportError: cannot import name ... partially initialized module. Run this script to see how a graph cycle detector prevents circular architecture:
"""
Micro-Lab: Circular Dependency Detector (DAG Checker)
PB-04 Chapter 3 Micro-Lab (Zero External Dependencies)
"""
def detect_circular_dependencies(dependency_graph: dict) -> list:
cycles = []
visited = set()
rec_stack = []
def dfs(node: str, path: list):
visited.add(node)
rec_stack.append(node)
for neighbor in dependency_graph.get(node, []):
if neighbor not in visited:
dfs(neighbor, path + [neighbor])
elif neighbor in rec_stack:
cycle_start = rec_stack.index(neighbor)
cycle = rec_stack[cycle_start:] + [neighbor]
cycles.append(" -> ".join(cycle))
rec_stack.pop()
for module in dependency_graph:
if module not in visited:
dfs(module, [module])
return cycles
if __name__ == "__main__":
# Clean Architecture: Web -> Service -> Database (DAG: strictly one direction)
clean_graph = {
"web_controller": ["auth_service", "billing_service"],
"auth_service": ["database_repo"],
"billing_service": ["database_repo"],
"database_repo": []
}
# Spaghetti Architecture: Web -> Service -> DB -> Web (CIRCULAR IMPORT ERROR!)
spaghetti_graph = {
"web_controller": ["auth_service"],
"auth_service": ["database_repo"],
"database_repo": ["web_controller"] # Disastrous cycle!
}
print("=== Testing Clean Architecture ===")
cycles1 = detect_circular_dependencies(clean_graph)
print(f"Cycles detected: {cycles1} -> Architecture is Acyclic: {len(cycles1) == 0}")
print("\n=== Testing Spaghetti Architecture ===")
cycles2 = detect_circular_dependencies(spaghetti_graph)
print(f"Cycles detected: {cycles2} -> Architecture is Acyclic: {len(cycles2) == 0}")
4. System Architecture & C4 Container Topology
When software systems are engineered by human developers, architectural boundaries often degrade gradually over months through organizational drift. But when autonomous AI coding agents are loosed upon a codebase without rigid architectural boundaries, architectural collapse occurs in minutes.
LLMs lack innate spatial or structural discipline. Given an arbitrary task, an agent will take the path of least resistance: it will import database connection pools directly into frontend UI templates, create cyclical imports between utility modules, mutate global singletons, and pack 3,000 lines of business logic into a single "God Class."
To achieve stable, scalable autonomous software development, the Software Architect Agent must establish and enforce an Agent-Friendly Architecture. This architecture is structured around the C4 Model (Context, Containers, Components, Code) and formal Architecture Decision Records (ADRs).
+---------------------------------------------------------------------------------------------------+
| AGENT-FRIENDLY C4 CONTAINER TOPOLOGY |
+---------------------------------------------------------------------------------------------------+
| |
| +--------------------------+ |
| | EXTERNAL CLIENT / WEB | |
| | - Stripe / GitHub Hook | |
| +--------------------------+ |
| | |
| | HTTPS / TLS 1.3 (Signed Webhook) |
| v |
| +-------------------------------------------------------------------------------------------+ |
| | CONTAINER 1: API GATEWAY & INGESTION (FastAPI / Stateless) | |
| | - Route Dispatcher | |
| | - Signature & Replay Verifier | |
| +-------------------------------------------------------------------------------------------+ |
| | | |
| | Atomic Lock & Check | ACID Transaction |
| v v |
| +--------------------------+ +------------------------------------------------+ |
| | CONTAINER 2: CACHE | | CONTAINER 3: PRIMARY TRANSACTIONAL DB | |
| | - Redis Cluster | | - PostgreSQL 16 (Relational Ledger) | |
| | - 24h Idempotency TTL | | - Transactional Outbox Event Table | |
| +--------------------------+ +------------------------------------------------+ |
| | |
| | Asynchronous CDC Poll |
| v |
| +------------------------------------------------+ |
| | CONTAINER 4: OUTBOX WORKER DISPATCHER | |
| | - Event Publisher & Dead Letter Queue (DLQ) | |
| +------------------------------------------------+ |
| |
+---------------------------------------------------------------------------------------------------+
The 4 Levels of the C4 Model for AI Coding Agents
graph TD
C1["Level 1: System Context<br/>(Who uses the system & external dependencies)"] --> C2["Level 2: Container Model<br/>(Deployable applications, databases, microservices)"]
C2 --> C3["Level 3: Component Model<br/>(Internal modules, ports, adapters, controllers)"]
C3 --> C4["Level 4: Code & AST Symbols<br/>(Classes, methods, interfaces, types)"]
style C1 fill:#e3f2fd,stroke:#1565c0,stroke-width:2px;
style C2 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
style C3 fill:#fff3e0,stroke:#ef6c00,stroke-width:2px;
style C4 fill:#fce4ec,stroke:#c2185b,stroke-width:2px;
- Context Level: Defines external actors (e.g. Third-party Webhook providers, Admin Users) and system boundaries. Prevents agents from attempting to re-implement third-party vendor responsibilities.
- Container Level: Defines discrete, deployable runtimes (FastAPI service, Redis cluster, PostgreSQL database, Async worker). Dictates network protocols and serialization boundaries.
- Component Level: Defines the internal hexagonal structure within a container (Controllers, Domain Services, Repositories). Binds agent tasks to isolated directories.
- Code Level: Maps to Tree-sitter AST symbol graphs. Developers agents only touch a targeted leaf method without reading or modifying unrelated components.
5. Freshman Survival Guide: 3 Traps to Avoid
Trap 1: The Circular Import Trap
- The Mistake: Writing code where
User.pyimportsOrder.py, andOrder.pyimportsUser.py. - Why it fails: Python will throw an
ImportErrorat startup. Circular dependencies create tightly coupled code that cannot be tested in isolation. - Fix: Enforce the Dependency Inversion Principle: create a third shared interface or models module that both depend on, ensuring dependencies flow in only one direction.
Trap 2: The "God File" Anti-Pattern
- The Mistake: Letting an AI coding agent dump database models, HTTP endpoints, business calculations, and HTML templates into a single
main.pyorviews.py. - Why it fails: When a file grows beyond 500 lines, AI agents lose track of dependencies, overwrite working functions, and burn enormous context tokens.
- Fix: Follow the Single Responsibility Principle: limit files to 150-300 lines with clear, isolated responsibilities (e.g.
routes/,services/,repositories/).
Trap 3: Leaking Database Logic into the UI
- The Mistake: Writing raw SQL queries or database ORM operations directly inside frontend templates or API route handlers.
- Why it fails: If the database schema changes, you have to rewrite every webpage and API route in the entire application.
- Fix: Use Hexagonal Architecture (Ports and Adapters): domain logic interacts only with repository interfaces; the database is a pluggable adapter.
6. Naive vs. Production Contrasts
The table below contrasts standard monolithic code organization with Agent-Optimized Modular C4 Architecture:
| Architecture Dimension | Naive Monolithic Spaghetti (Anti-Pattern) | Agent-Optimized Modular C4 Architecture (Production Standard) |
|---|---|---|
| Module File Size | 1,500 - 4,000 line "God Files" (views.py, models.py). |
Strict 150 - 300 line single-responsibility components with clear interfaces. |
| Dependency Graphs | Complex tangled web; circular imports (A imports B imports A). |
Strictly acyclic DAG enforced by compile-time cycle detection linters. |
| Coupling Factor | High afferent & efferent coupling; touching one function breaks five modules. | Hexagonal Architecture (Ports & Adapters); domain core has 0 external dependencies. |
| Agent Context Overhead | Entire 50,000-line repository must be packed into LLM context window. | Agent ingests only the target component contract and its immediate port interfaces (< 6K tokens). |
| Cross-Agent Merge Conflicts | High thrashing; multiple agents edit routes.py and models.py simultaneously. |
Isolated domain packages; agents work on independent branches touching disjoint directory trees. |
| State Mutations | Global shared state, in-memory mutable singletons. | Stateless compute, immutable domain value objects, explicit transactional boundaries. |
| Decision Traceability | Undocumented ad-hoc changes based on conversational prompts. | Formal Architecture Decision Records (ADRs) tracking trade-offs, status, and compliance rules. |
7. Frontier Model Configurations & C4 Schema Specifications
Generating enterprise architecture requires deep causal reasoning. Gemini 2.5 Pro with low temperature is deployed as the Chief Software Architect Agent.
System Architect Agent Calibration
SYSTEM_ARCHITECT_AGENT_CONFIG = {
"model": "gemini-2.5-pro",
"temperature": 0.15,
"top_p": 0.90,
"max_output_tokens": 8192,
"system_instruction": """You are the Principal Distributed Systems Architect in an AI-DLC organization.
Your responsibility:
1. Decompose requirements into modular, acyclic C4 Container and Component models.
2. Ensure strict separation of concerns using Hexagonal Architecture (Ports and Adapters).
3. Evaluate trade-offs across consistency, latency, operational cost, and blast radius.
4. Emit formal Architecture Decision Records (ADRs) with unambiguous compliance rules for coding agents.
5. Never permit cyclical dependencies or god-classes."""
}
C4 Container & Relationship Schema (JSON Schema Draft 2020-12)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "C4ContainerArchitectureSchema",
"type": "object",
"properties": {
"system_name": {"type": "string"},
"containers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"},
"container_type": {
"type": "string",
"enum": ["Web Application", "API Microservice", "Background Worker", "Relational Database", "In-Memory Cache", "Event Message Bus"]
},
"technology": {"type": "string"},
"description": {"type": "string"}
},
"required": ["id", "name", "container_type", "technology", "description"]
}
},
"relationships": {
"type": "array",
"items": {
"type": "object",
"properties": {
"source_id": {"type": "string"},
"target_id": {"type": "string"},
"description": {"type": "string"},
"protocol": {"type": "string"}
},
"required": ["source_id", "target_id", "description", "protocol"]
}
}
},
"required": ["system_name", "containers", "relationships"]
}
4. Quantitative Trade-Off Matrix: Architectural Topologies for AI Agents
Selecting an architecture topology directly determines how effectively AI coding agents can navigate, edit, and test the codebase:
| Architectural Topology | Agent Context Efficiency | Cyclical Import Risk | Merge Conflict Frequency | Test Isolation & Speed | Autonomous AI-DLC Feasibility |
|---|---|---|---|---|---|
| Classical Monolith (MVC) | Very Low (Sprawling context) | High (Tangled models) | Extremely High (Shared files) | Slow (Coupled test suite) | 18% (Frequent agent thrashing) |
| Modular Hexagonal Monolith | Exceptional (< 8K tokens) | Zero (Acyclic enforcement) | Very Low (Disjoint domains) | Fast (< 5s in-memory ports) | 88% (Ideal sweet spot) |
| Event-Driven Microservices | High (Small per-service repo) | Low (Network boundaries) | Very Low (Independent repos) | Complex (Requires mock bus) | 72% (High integration burden) |
| Serverless Functions (FaaS) | Moderate (Scattered functions) | Low | Low | Moderate | 64% (Cold starts, vendor lock-in) |
5. The 10 Operational Failure Modes in AI Architecture Design
1. The Cyclical Dependency Death Spiral
- Mechanism: Agent A modifies
UserServiceto importBillingServicefor invoices; Agent B modifiesBillingServiceto importUserServicefor emails. The Python runtime crashes upon boot withImportError: cannot import name 'X' from partially initialized module. - Defense Mechanism: Compile-time Directed Acyclic Graph (DAG) cycle detection. The
C4ArchitectureCompilerruns Depth-First Search (DFS) on container and module import graphs before any patch is accepted.
2. The God Component Aggregation Trap
- Mechanism: When tasked with multiple features, the LLM places all business logic into a single class (e.g.
SystemManagerorOrderProcessor), accumulating 3,000+ lines. - Defense Mechanism: Coupling Metric Guardrail. Compute afferent/efferent coupling (fan-in / fan-out). If any component's fan-in exceeds 5 or line count exceeds 350 lines, reject the architecture and require decomposition.
3. Leaky Infrastructure Abstractions
- Mechanism: SQL queries, Redis commands, or S3 upload SDKs are written directly inside business domain services, making unit testing impossible without live network infrastructure.
- Defense Mechanism: Enforce Hexagonal Architecture (Ports and Adapters). Domain services may only interact with abstract interfaces (
RepositoryPort); concrete database adapters reside in isolated infrastructure directories.
4. Cross-Domain Database Joins
- Mechanism: In a multi-tenant or microservice architecture, an agent writes raw SQL joins spanning across bounded context table boundaries (e.g. Joining
auth_usersdirectly withbilling_invoices). - Defense Mechanism: Schema Boundary Isolation. Each domain bounded context maintains its own database schema or repository abstraction; cross-domain data access must proceed via defined domain service methods.
5. Unindexed Query & N+1 Query Cascade
- Mechanism: Agent writes object-relational mapping (ORM) loops that query foreign keys inside an iteration, causing hundreds of sequential database roundtrips.
- Defense Mechanism: Query Linter Hook. Require eager-loading semantics (
select_related,prefetch_related) or explicit batch query assertions in verification specs.
6. Split-Brain Distributed State
- Mechanism: Mutating data across two independent datastores (e.g. PostgreSQL and DynamoDB) without distributed transaction orchestration, leaving systems permanently out of sync on network partitions.
- Defense Mechanism: Enforce the Transactional Outbox Pattern (ADR-003). Persist both business data and outbox events in a single ACID transaction; dispatch events asynchronously via dedicated workers.
7. Phantom Message Queue Deadlocks
- Mechanism: Agent publishes messages to an event bus with synchronous blocking waiting for a response, creating distributed thread pool starvation.
- Defense Mechanism: Strict asynchronous fire-and-forget or callback pattern enforcement in ADR compliance rules.
8. Hardcoded Environmental Secrets & Endpoints
- Mechanism: Agent hardcodes
http://localhost:8000or API test keys directly in module constants rather than environment configurations. - Defense Mechanism: Configuration Injection Linter. All hostnames and credentials must be injected via Pydantic
BaseSettingsreading from environment variables.
9. Missing Circuit Breaker & Fallback Policy
- Mechanism: External vendor API calls (Stripe, Twilio) are executed without timeouts or retries, causing incoming webhook request workers to hang indefinitely during vendor outages.
- Defense Mechanism: Resilience Contract. All external HTTP clients must configure explicit connection timeouts (max 3s) and circuit-breaker thresholds.
10. Ungoverned State Machine Invalidation
- Mechanism: Entity transitions from
REFUNDEDback toPENDINGbecause the agent failed to model a formal finite state machine (FSM). - Defense Mechanism: Enforce state machine transitions using explicit Enums and valid transition tables; reject illegal state jumps at the domain model level.
10. Mandatory Hands-On Lab: C4 Architecture Compiler & ADR Engine
Lab Objective
In this hands-on lab, you will act as the Chief Software Architect. You will:
- Define a robust, distributed C4 Container Architecture for a Mission-Critical Payment Webhook Gateway using
C4ArchitectureCompiler. - Introduce a deliberate cyclical dependency between components and observe the DFS Cycle Detection engine uncover and report the illegal loop.
- Compute Afferent (fan-in) and Efferent (fan-out) coupling metrics to ensure balanced component responsibilities.
- Evaluate architectural trade-offs using the
ADRTradeOffEngineand compile ADR-003: Transactional Outbox with Synchronous Idempotency Cache. - Render clean, syntax-verified Mermaid C4 diagrams ready for documentation.
Lab Step-by-Step Instructions
Step 1: Initialize the C4 Architecture Compiler
Instantiate C4ArchitectureCompiler("Payment Webhook Gateway") and register 4 containers: API Gateway, Redis Cache, PostgreSQL Database, and Outbox Worker.
Step 2: Establish Valid Acyclic Container Relationships
Wire the relationships: API Gateway -> Redis Cache, API Gateway -> PostgreSQL, Outbox Worker -> PostgreSQL. Run detect_dependency_cycles() and verify that zero cycles exist.
Step 3: Test Cycle Detection on Dangerous Feedback Loop
Inject a cyclical callback: API Gateway -> Redis Cache -> Outbox Worker -> API Gateway. Run cycle detection and verify that the cycle is isolated and reported.
Step 4: Calculate Coupling Metrics
Execute calculate_coupling_metrics(). Verify that API Gateway has fan-out=2, fan-in=0, and PostgreSQL has fan-in=2.
Step 5: Compile and Export ADR-003
Invoke ADRTradeOffEngine.evaluate_persistence_strategy("payment financial"). Verify that status is ACCEPTED and that agentic compliance invariants are formally rendered in Markdown.
11. Mandatory Recommended Answer & Executable Solution
The following complete, zero-dependency Python 3.11+ program implements the C4ArchitectureCompiler and ADRTradeOffEngine, complete with an automated self-test verification suite.
"""
test_ch03_engine.py
Zero-dependency Python 3.11+ engine for Chapter 3:
C4ArchitectureCompiler & ADRTradeOffEngine
"""
import json
from dataclasses import dataclass, field, asdict
from enum import Enum
from typing import List, Dict, Any, Set, Optional
class ContainerType(str, Enum):
WEB_APP = "Web Application"
API_SERVICE = "API Microservice"
BACKGROUND_WORKER = "Background Worker"
DATABASE = "Relational Database"
CACHE = "In-Memory Cache"
MESSAGE_BUS = "Event Message Bus"
@dataclass
class C4Container:
id: str
name: str
container_type: ContainerType
technology: str
description: str
@dataclass
class C4Relationship:
source_id: str
target_id: str
description: str
protocol: str # HTTPS/REST, gRPC, Redis Protocol, AMQP, PostgreSQL Wire
@dataclass
class ADRDecision:
adr_id: str
title: str
status: str # PROPOSED, ACCEPTED, SUPERSEDED
context: str
decision: str
consequences_positive: List[str]
consequences_negative: List[str]
compliance_rules: List[str]
class C4ArchitectureCompiler:
"""Compiles, validates, and renders C4 Container architectures optimized for AI coding agents."""
def __init__(self, system_name: str):
self.system_name = system_name
self.containers: Dict[str, C4Container] = {}
self.relationships: List[C4Relationship] = []
def add_container(self, container: C4Container) -> None:
self.containers[container.id] = container
def add_relationship(self, rel: C4Relationship) -> None:
if rel.source_id not in self.containers or rel.target_id not in self.containers:
raise ValueError(f"Relationship contains unknown container: {rel.source_id} -> {rel.target_id}")
self.relationships.append(rel)
def detect_dependency_cycles(self) -> List[List[str]]:
"""Detects cyclical dependencies between containers using Depth-First Search."""
adj: Dict[str, List[str]] = {cid: [] for cid in self.containers}
for rel in self.relationships:
adj[rel.source_id].append(rel.target_id)
visited: Set[str] = set()
rec_stack: Set[str] = set()
cycles: List[List[str]] = []
def dfs(node: str, path: List[str]):
visited.add(node)
rec_stack.add(node)
path.append(node)
for neighbor in adj.get(node, []):
if neighbor not in visited:
dfs(neighbor, path.copy())
elif neighbor in rec_stack:
# Cycle found
cycle_start_idx = path.index(neighbor)
cycles.append(path[cycle_start_idx:] + [neighbor])
rec_stack.remove(node)
for node in self.containers:
if node not in visited:
dfs(node, [])
return cycles
def calculate_coupling_metrics(self) -> Dict[str, Dict[str, int]]:
"""Calculates Afferent (fan-in) and Efferent (fan-out) coupling per container."""
metrics = {cid: {"fan_in": 0, "fan_out": 0} for cid in self.containers}
for rel in self.relationships:
metrics[rel.source_id]["fan_out"] += 1
metrics[rel.target_id]["fan_in"] += 1
return metrics
def generate_mermaid_container_diagram(self) -> str:
"""Generates clean, syntax-compliant Mermaid C4 diagrams."""
lines = ["graph TB", f" subgraph SystemBoundary[\"{self.system_name} Boundary\"]"]
for cid, c in self.containers.items():
clean_tech = c.technology.replace('"', "'")
clean_desc = c.description.replace('"', "'")
lines.append(f" {cid}[\"<b>{c.name}</b><br/>[{c.container_type.value}]<br/><i>{clean_tech}</i><br/>{clean_desc}\"]")
lines.append(" end")
for rel in self.relationships:
clean_desc = rel.description.replace('"', "'")
lines.append(f" {rel.source_id} -->|{clean_desc} ({rel.protocol})| {rel.target_id}")
return "\n".join(lines)
class ADRTradeOffEngine:
"""Evaluates architectural trade-offs and generates formal Architecture Decision Records (ADRs)."""
@staticmethod
def evaluate_persistence_strategy(use_case: str) -> ADRDecision:
if "financial" in use_case.lower() or "payment" in use_case.lower():
return ADRDecision(
adr_id="ADR-003",
title="Transactional Outbox with Synchronous Idempotency Cache for Payment Webhooks",
status="ACCEPTED",
context=(
"The payment webhook ingestion system requires guaranteed zero duplicate financial debits "
"under burst traffic, with sub-50ms idempotency cache checks and strict ACID ledger durability."
),
decision=(
"Implement a dual-tier persistence model: Redis Cluster for atomic distributed lock and "
"idempotency key evaluation (TTL 24h), combined with PostgreSQL Transactional Outbox pattern "
"to ensure atomic state changes and decoupled asynchronous audit streaming."
),
consequences_positive=[
"Eliminates race conditions on concurrent webhook duplicate deliveries.",
"Provides sub-20ms latency for replayed webhook acknowledgments.",
"Guarantees at-least-once delivery for asynchronous financial audit consumers."
],
consequences_negative=[
"Requires managing dual datastore operational dependencies (PostgreSQL + Redis).",
"Outbox publisher worker must handle poison-pill message dead-lettering."
],
compliance_rules=[
"Every mutating SQL query must occur inside an explicit database transaction.",
"Redis lock release must use Lua script with ownership token verification."
]
)
else:
return ADRDecision(
adr_id="ADR-001",
title="Stateless JSON REST API with Single Relational Datastore",
status="ACCEPTED",
context="Standard resource management with low concurrency and moderate throughput.",
decision="Deploy stateless FastAPI services directly backed by PostgreSQL.",
consequences_positive=["Simple deployment topology", "Low operational footprint"],
consequences_negative=["Limited horizontal scalability on heavy write bursts"],
compliance_rules=["All tables must include created_at and updated_at UTC timestamps."]
)
@staticmethod
def render_adr_markdown(adr: ADRDecision) -> str:
lines = [
f"# {adr.adr_id}: {adr.title}",
"",
f"**Status**: `{adr.status}`",
"",
"## Context & Problem Statement",
adr.context,
"",
"## Decision Outcome",
adr.decision,
"",
"## Consequences & Trade-Off Analysis",
"### Positive Consequences",
*[f"- {p}" for p in adr.consequences_positive],
"",
"### Negative Consequences & Risks",
*[f"- {n}" for n in adr.consequences_negative],
"",
"## Agentic Compliance Invariants",
*[f"- `{r}`" for r in adr.compliance_rules]
]
return "\n".join(lines)
# ==========================================
# Self-Test Verification Suite
# ==========================================
if __name__ == "__main__":
import unittest
class TestC4ArchitectureEngine(unittest.TestCase):
def setUp(self):
self.compiler = C4ArchitectureCompiler("Payment Webhook Gateway")
self.compiler.add_container(C4Container(
id="c_gw", name="API Gateway", container_type=ContainerType.API_SERVICE,
technology="FastAPI / Envoy", description="Ingests external webhook payloads."
))
self.compiler.add_container(C4Container(
id="c_cache", name="Idempotency Cache", container_type=ContainerType.CACHE,
technology="Redis Cluster", description="Stores 24-hour idempotency tokens."
))
self.compiler.add_container(C4Container(
id="c_db", name="Primary Ledger DB", container_type=ContainerType.DATABASE,
technology="PostgreSQL 16", description="Transactional financial ledger."
))
self.compiler.add_container(C4Container(
id="c_worker", name="Outbox Dispatcher", container_type=ContainerType.BACKGROUND_WORKER,
technology="Python asyncio", description="Polls outbox table and streams events."
))
def test_acyclic_architecture_validation(self):
self.compiler.add_relationship(C4Relationship("c_gw", "c_cache", "Validates idempotency", "Redis Protocol"))
self.compiler.add_relationship(C4Relationship("c_gw", "c_db", "Persists transaction & outbox", "PostgreSQL Wire"))
self.compiler.add_relationship(C4Relationship("c_worker", "c_db", "Polls unpublished events", "PostgreSQL Wire"))
cycles = self.compiler.detect_dependency_cycles()
self.assertEqual(len(cycles), 0, f"Expected no cycles, found: {cycles}")
def test_cycle_detection_flags_loop(self):
self.compiler.add_relationship(C4Relationship("c_gw", "c_cache", "Reads cache", "Redis Protocol"))
self.compiler.add_relationship(C4Relationship("c_cache", "c_worker", "Triggers worker", "PubSub"))
self.compiler.add_relationship(C4Relationship("c_worker", "c_gw", "Calls internal webhook", "HTTPS"))
cycles = self.compiler.detect_dependency_cycles()
self.assertTrue(len(cycles) > 0, "Expected cycle to be detected")
self.assertIn("c_gw", cycles[0])
def test_coupling_metrics_calculation(self):
self.compiler.add_relationship(C4Relationship("c_gw", "c_cache", "Checks cache", "Redis"))
self.compiler.add_relationship(C4Relationship("c_gw", "c_db", "Writes ledger", "SQL"))
self.compiler.add_relationship(C4Relationship("c_worker", "c_db", "Reads outbox", "SQL"))
metrics = self.compiler.calculate_coupling_metrics()
self.assertEqual(metrics["c_gw"]["fan_out"], 2)
self.assertEqual(metrics["c_gw"]["fan_in"], 0)
self.assertEqual(metrics["c_db"]["fan_in"], 2)
def test_mermaid_c4_generation(self):
self.compiler.add_relationship(C4Relationship("c_gw", "c_cache", "Validates token", "Redis"))
mermaid = self.compiler.generate_mermaid_container_diagram()
self.assertIn("graph TB", mermaid)
self.assertIn("API Gateway", mermaid)
self.assertIn("Redis Cluster", mermaid)
self.assertIn("c_gw -->|Validates token (Redis)| c_cache", mermaid)
def test_adr_trade_off_evaluation(self):
adr = ADRTradeOffEngine.evaluate_persistence_strategy("Mission-critical financial payment processing")
self.assertEqual(adr.adr_id, "ADR-003")
self.assertEqual(adr.status, "ACCEPTED")
self.assertTrue(len(adr.consequences_positive) >= 2)
self.assertTrue(len(adr.compliance_rules) >= 1)
md = ADRTradeOffEngine.render_adr_markdown(adr)
self.assertIn("# ADR-003: Transactional Outbox", md)
self.assertIn("## Agentic Compliance Invariants", md)
suite = unittest.TestLoader().loadTestsFromTestCase(TestC4ArchitectureEngine)
runner = unittest.TextTestRunner(verbosity=2)
test_result = runner.run(suite)
if not test_result.wasSuccessful():
exit(1)
print("\n[PASS] All Chapter 3 Unit Tests Passed Successfully (100% Conformance).")
12. Summary & Next Steps
This chapter established architectural rigor for Playbook 04:
- Decomposed system structure using the C4 Model (Context, Containers, Components, Code).
- Prevented agent thrashing by enforcing Modular Hexagonal Architecture with low coupling.
- Implemented graph-theoretic DFS Cycle Detection to eliminate cyclical import deadlocks.
- Standardized formal Architecture Decision Records (ADRs) with explicit agent compliance rules.
- Delivered and verified the zero-dependency Python 3.11+ C4ArchitectureCompiler & ADRTradeOffEngine.
Upcoming Chapters in Playbook 04:
- Chapter 04: Interface Design, API Contracts & Schema Synthesis.
- Chapter 05: Agentic Coding, Context Gathering & Tree-sitter AST Mechanics.
- Chapter 06: Autonomous Verification, Testing & Self-Healing Code Loops.
- Chapter 07: Automated CI/CD, GitOps & Agentic Review Workflows.