↓Skip to main content
  1. Posts/
  2. The Code Citadel - Introduction/

The Code Citadel - The City Watch

·18 mins·

In the previous chronicle, we learned to build for catastrophe. We installed Circuit Breakers to contain fires, Bulkheads to quarantine plagues, and Timeouts to prevent eternal sieges. Marcus, the on-call engineer, watched the Image Resizing Service fail twice - first bringing down the entire platform for 47 minutes, then contained to just 5 minutes of degraded functionality. The Royal Travel Service now handles 100,000 bookings per day with 99.9% uptime. But resilience patterns only work if we can see the danger coming. In the distributed city, visibility is not a luxury - it is survival.


Prologue: The Fog of the Alleys #

The confusion of tracking transactions through dark alleys

In the Golden Age of the Monolith (The Castle), the Sheriff had an easy job. The Castle was a single building. If a crime occurred - say, a NullPointerException in the UserCheckout module - it happened in the Great Hall. The Sheriff simply walked downstairs, looked at the Central Log (a single file on a single disk), and saw exactly who stabbed whom. The stack trace was a smoking gun, lying right next to the body. Master Clerk Edmund could debug any issue in minutes.

But we have torn down the Castle. We have built the Distributed City. Now, a transaction is not a conversation in a room; it is a journey through dark, winding streets. A user clicks “Buy.” The request enters the City Gate (API Gateway), travels to the Order District, sends a messenger to the Inventory District, who rides to the Payment Guild, who queries the Shipping Wharf.

One night, the transaction fails. The user sees a generic error: “Something went wrong.” The Sheriff looks at the Order District logs. “Sent message to Inventory,” it says. Success. He looks at the Inventory logs. “Received order. Reserved stock.” Success. He looks at the Payment logs. “Payment processed.” Success. Yet the user has no goods, and the money is gone. Somewhere in the dark alleys between the districts, the messenger was shanked, or fell into a canal, or simply vanished. In the Microservices City, the default state is Blindness. You cannot govern what you cannot see.

Marcus’s 2 AM Debugging Nightmare:

It was 2 AM. Marcus, the on-call engineer who had battled the Image Resizing Service fires, was awakened by an alert: “Booking failure rate: 15%”

Lady Catherine had complained: “I paid 200 gold for a journey to Rome, but I have no booking confirmation. Where is my money?”

Marcus started investigating. In the Keep days under Master Clerk Edmund, this would have been simple - one log file, one database, one stack trace. But now, with 35 services spread across the City:

Step 1: Check API Gateway logs

  • Found the request: POST /bookings at 01:47:23
  • Response: 200 OK
  • “So the request succeeded?” Marcus thought

Step 2: Check Booking Service logs

  • SSH into booking-service-1: No matching request
  • SSH into booking-service-2: No matching request
  • SSH into booking-service-3: Found it! “Booking created: booking-7f3a”
  • “So the booking was created?” Marcus thought

Step 3: Check Payment Service logs

  • SSH into payment-service-1: Found it! “Payment processed: 200 gold”
  • “So payment succeeded?” Marcus thought

Step 4: Check Notification Service logs

  • SSH into notification-service-1: No matching request
  • SSH into notification-service-2: Found it! “Email failed: SMTP timeout”
  • “Ah! The email failed. But Lady Catherine said she has no booking…”

Step 5: Check database

  • Booking database: No booking-7f3a found
  • Payment database: Payment recorded
  • “Wait, the logs say booking was created, but it’s not in the database?”

After 2 hours of investigation, Marcus discovered:

  • Booking was created successfully
  • But a database replication lag meant it wasn’t visible immediately
  • The Notification Service tried to send email before replication completed
  • Email failed because it couldn’t find the booking
  • The saga compensation logic kicked in and deleted the booking
  • But the Payment Service didn’t receive the compensation event (network issue)
  • So the payment remained, but the booking was deleted

Marcus had to SSH into 15 different servers, grep through gigabytes of logs, correlate timestamps across different time zones, and piece together the story like a detective solving a murder mystery.

By 4 AM, he had manually refunded Lady Catherine and filed a bug report. He was exhausted and frustrated.

“In the Keep”, he muttered, “Edmund could have solved this in 5 minutes.”


The Watchmen: Aggregated Logging #

Isolated watchmen with separate notebooks

In a medieval city, crime flourished in the shadows. To combat this, the burghers established the Town Watch. However, a Watchman in the Tanners’ District cares only about tanners. He writes in his local notebook: “All quiet.” A Watchman in the Docks writes: “All quiet.” If a thief strikes the Tanner and runs to the Docks, neither Watchman sees the pattern. The crime is fragmented.

In software, this is the Siloed Log Problem. If you have 50 services, you have 50 log files scattered across 50 servers. To debug a failure, you have to SSH into fifty different machines, grep for timestamps, and pray.

The Watchtower (Log Aggregation) #

The central watchtower aggregating all logs

We must build a Central Watchtower (ELK Stack, Splunk, Datadog). Every local Watchman (Service) must be stripped of his notebook. Instead, he must shout his reports into a pneumatic tube that runs directly to the Tower. Inside the Tower, the High Constable does not look at servers; he looks at a Dashboard. He sees the logs from the Order Service intermingled with the logs from the Payment Service in real-time. He can see the fire starting in the East Ward before the West Ward even smells the smoke.

The Royal Travel Service’s Centralized Logging Implementation:

After Marcus’s 2 AM nightmare, the CTO called a meeting: “We cannot continue like this. Our engineers spend more time SSHing into servers than actually fixing bugs.”

The problems were clear:

  • 35 services × 3 instances each = 105 log files
  • Each service logged in a different format
  • Timestamps were in different time zones
  • No way to correlate logs across services
  • Engineers needed SSH access to production servers (security risk)
  • Logs rotated every 24 hours (old logs were lost)

The team deployed the ELK Stack (Elasticsearch, Logstash, Kibana). Every service now logs to stdout in JSON format, collected by Fluentd and shipped to Elasticsearch. Engineers query logs through Kibana dashboard.

The Next 2 AM Alert:

Two weeks later, Marcus got another alert: “Booking failure rate: 12%”

This time, his investigation was different:

Step 1: Open Kibana dashboard Step 2: Search for the traveler: traveler_id: "traveler-456" Step 3: See all logs from all services, in chronological order:

01:52:10 [API Gateway] Request received: POST /bookings
01:52:11 [Booking Service] Creating booking: booking-8g4b
01:52:12 [Payment Service] Processing payment: 200 gold
01:52:12 [Payment Service] Payment successful
01:52:13 [Booking Service] Booking created: booking-8g4b
01:52:14 [Notification Service] Sending email to traveler-456
01:52:15 [Notification Service] ERROR: SMTP timeout
01:52:16 [Saga Orchestrator] Booking saga failed at step 4
01:52:17 [Saga Orchestrator] Triggering compensation
01:52:18 [Booking Service] Deleting booking: booking-8g4b
01:52:19 [Payment Service] ERROR: Compensation event not received

Total investigation time: 5 minutes

Marcus immediately saw the problem: The Payment Service wasn’t receiving compensation events. He checked the message queue and found it was full (a configuration issue). He increased the queue size, reprocessed the failed events, and went back to sleep.

The Impact:

  • Mean time to detect (MTTD): Reduced from 2 hours to 5 minutes
  • Mean time to resolve (MTTR): Reduced from 4 hours to 30 minutes
  • Engineer happiness: Significantly improved
  • No more SSH access needed to production servers

The Hue and Cry: Distributed Tracing #

The hue and cry tracking a criminal across districts

But seeing the logs is not enough. You see a thousand shouts of “Error!” but you don’t know if they are related. In medieval English law, there was a mechanism for tracking a fleeing criminal across jurisdictional boundaries: the Hue and Cry.

If a Merchant is robbed in the Market, he is legally obliged to scream “Stop Thief!” (The Hue). Every citizen who hears the Hue must drop their tools and join the chase. Crucially, they must continue the cry. The shout travels from the Market, down the High Street, over the Bridge, and into the Forest. If the Sheriff finds a man running in the Forest, he knows he is the thief because the Cry followed him there. The sound connects the crime scene to the capture location.

Marcus’s Correlation Problem:

Even with centralized logging, Marcus faced a new problem. During peak hours, the Royal Travel Service processed 1,000 bookings per minute. That’s 1,000 concurrent sagas, each touching 6-8 services.

When a booking failed, Marcus would see:

01:52:15 [Notification Service] ERROR: SMTP timeout
01:52:15 [Notification Service] ERROR: SMTP timeout
01:52:15 [Notification Service] ERROR: SMTP timeout
... (50 more errors)

Which error belonged to which booking? Which traveler was affected? The logs were centralized, but not correlated.

The Trace ID (The Correlation ID) #

A unique trace ID following a request through services

In microservices, we implement the Hue and Cry using Distributed Tracing (e.g., Jaeger, Zipkin, OpenTelemetry).

The team deployed Jaeger for distributed tracing. The API Gateway generates a trace ID for every request and passes it through all service calls.

The Visualization:

Now, when Marcus investigated a failed booking for Lady Catherine’s York to Rome journey, he could see a visual trace:

API Gateway (2ms)
  └─> Booking Service (150ms)
      ├─> Payment Service (80ms)
      │   └─> Database (75ms)
      ├─> Inn Service (45ms)
      │   └─> Cache (2ms) - The Golden Lion, The Silver Stag
      └─> Notification Service (TIMEOUT - 5000ms)
          └─> SMTP Server (TIMEOUT)

Insights Gained:

  1. Latency Breakdown: Marcus could see exactly where time was spent

    • “The Notification Service is taking 5 seconds? That’s our problem!”
  2. Failure Points: He could see exactly where requests failed

    • “The SMTP server is timing out. Let’s check if it’s down.”
  3. Dependency Mapping: He could see which services called which

    • “I didn’t know the Booking Service called the Analytics Service. That’s unnecessary!”
  4. Performance Optimization: He could identify slow services

    • “The Payment Service database query takes 75ms. Can we optimize it?”

Without the Hue and Cry (Trace ID), the timeout is just an isolated event. With it, we see the causal chain. We see exactly where the thief hid.

The Business Impact:

One day, the King complained: “Booking a journey takes 8 seconds. Your competitor does it in 3 seconds!”

Marcus used distributed tracing to analyze Lady Catherine’s typical York to Rome booking:

Total: 8000ms
├─ API Gateway: 5ms
├─ Booking Service: 7950ms
│  ├─ Payment Service: 100ms
│  ├─ Inn Service: 50ms (The Golden Lion, The Silver Stag)
│  ├─ Pricing Service: 200ms (200 gold calculation)
│  ├─ Weather Service: 3000ms (!!!)
│  ├─ Analytics Service: 2000ms (!!!)
│  └─ Notification Service: 100ms

The problem was clear: Weather Service (3 seconds) and Analytics Service (2 seconds) were blocking the critical path.

The Fix:

  • Made Weather Service call asynchronous (send weather info later)
  • Made Analytics Service call asynchronous (update analytics in background)
  • New total time: 500ms

The King sent a letter: “Booking now takes 0.5 seconds. Excellent work!”


The Ale-Conner: Contract Testing #

The ale-conner inspecting quality and standards

Visibility helps us fix things when they break. But how do we prevent them from breaking? In the City, the Guilds are independent. The Frontend Guild relies on the data provided by the Backend Guild. One day, the Backend Guild decides to change the format of the “Date” field from DD-MM-YYYY to YYYY-MM-DD. They deploy the change. Instantly, the Frontend crashes. The City economy collapses. This is an Integration Failure.

In the Middle Ages, this was the domain of the Assize of Bread and Ale. The King knew that bakers would cheat. They would make loaves smaller as the price of grain rose. To prevent chaos, the City appointed an Ale-Conner (Ale-Taster). The Ale-Conner was a man of terrible authority. He would visit the brewery, inspect the measures, and taste the ale. Legend says he would pour a puddle of ale on a wooden bench and sit in it wearing leather breeches. If his breeches stuck to the bench after thirty minutes, the ale was too sugary (badly brewed) or too thick. The batch was condemned.

The Royal Travel Service’s Integration Disaster:

The Booking Service and Payment Service were developed by different teams. They communicated via REST API.

The Contract:

{
  "booking_id": "string",
  "amount": "number",
  "currency": "string"
}

One day, the Payment team decided to change the API to support multiple currencies:

New Format:

{
  "booking_id": "string",
  "amount": {
    "value": "number",
    "currency": "string"
  }
}

They thought: “This is a better design. Let’s deploy it.”

They deployed on Friday afternoon. Within 5 minutes:

  • All bookings started failing
  • Booking Service was sending old format
  • Payment Service expected new format
  • Payment Service returned: 400 Bad Request
  • Travelers couldn’t book anything
  • Platform was effectively down

The Payment team had to rollback immediately. But the damage was done:

  • 30 minutes of downtime
  • 500 failed bookings
  • Angry travelers
  • Angry King

The Pact (Consumer-Driven Contracts) #

Comparing contract expectations with actual output

In software, we cannot rely on manual tasting. We use Consumer-Driven Contract Testing (e.g., Pact).

  1. The Consumer (The Drinker): The Frontend Team writes a “Contract.”
    • “I expect the ‘User’ object to have a field called ‘id’ (number) and ’name’ (string).”
  2. The Seal: This Contract is given to the Ale-Conner (The CI/CD Pipeline).
  3. The Test: Before the Backend Team is allowed to deploy (open their shop), the Ale-Conner runs a test against their code.
    • “Does your output match the Frontend’s Contract?”
  4. The Judgement: If the Backend has changed ‘id’ to a string, the test fails. The deployment is rejected.
A deployment rejected for contract violation

The Royal Travel Service’s Contract Testing Implementation:

The team implemented Pact for contract testing:

Booking Service (Consumer) defines the contract:

// booking-service/tests/payment-contract.test.js
describe('Payment Service Contract', () => {
  it('should accept payment in old format', async () => {
    const expectedRequest = {
      booking_id: "booking-123",
      amount: 200,
      currency: "gold"
    };
    
    const expectedResponse = {
      payment_id: "payment-456",
      status: "success"
    };
    
    // This contract is published to Pact Broker
    await pact.addInteraction({
      state: 'payment can be processed',
      uponReceiving: 'a payment request',
      withRequest: {
        method: 'POST',
        path: '/payments',
        body: expectedRequest
      },
      willRespondWith: {
        status: 200,
        body: expectedResponse
      }
    });
  });
});

Payment Service (Provider) verifies the contract:

// payment-service/tests/contract-verification.test.js
describe('Payment Service Contract Verification', () => {
  it('should satisfy all consumer contracts', async () => {
    // Fetch contracts from Pact Broker
    // Run Payment Service
    // Replay all contract requests
    // Verify responses match expectations
    
    await pact.verifyProvider({
      provider: 'payment-service',
      pactUrls: ['http://pact-broker/pacts/booking-service/payment-service']
    });
  });
});

The CI/CD Pipeline:

Now, when the Payment team tried to deploy their breaking change:

  1. Payment Service CI runs contract verification tests
  2. Tests fetch contracts from Pact Broker
  3. Tests replay Booking Service’s expected requests
  4. Payment Service returns 400 Bad Request (new format expected)
  5. Tests fail! Contract violated!
  6. Deployment blocked!

The Payment team sees:

❌ Contract Verification Failed
Consumer: booking-service
Provider: payment-service
Failure: Expected response 200, got 400
The booking-service expects the old payment format.
You cannot deploy this change without coordinating with the booking-service team.

The Coordinated Migration:

The Payment team now had to coordinate:

  1. Phase 1: Payment Service supports BOTH old and new formats
  2. Phase 2: Deploy Payment Service (backward compatible)
  3. Phase 3: Booking Service updates to new format
  4. Phase 4: Deploy Booking Service
  5. Phase 5: Payment Service removes support for old format

This took 2 weeks instead of 1 day, but there was zero downtime and zero broken bookings.

The Ale-Conner prevents the Backend Guild from breaking the Frontend Guild’s tools. It enforces the Royal Standard of weights and measures, ensuring that a “Pint” in the North District is the same as a “Pint” in the South.


The Court of Piepowder: Governance #

The swift market court delivering immediate justice

Finally, a city needs a way to resolve disputes quickly. In medieval markets, traders came from all over Europe. They could not wait months for a Royal Court to hear a case. So, the markets held the Court of Piepowder (from the French pieds poudrés - “dusty feet”). This court had summary jurisdiction over the market. It settled disputes immediately, while the dust was still on the merchant’s boots.

In our architecture, this is Automated Governance. We do not wait for a quarterly architecture review board to tell us our code is bad. We use linters, static analysis, and automated policy checks (Open Policy Agent) in the pipeline.

Automated checks enforcing policies at the gate
  • “Did you leave a secret key in the code?” Guilty. (Build Fails).
  • “Is your container running as root?” Guilty. (Build Fails).

The Royal Travel Service’s Governance Challenges:

With 50 services and 30 engineers, maintaining consistency became difficult:

Problems:

  • Some services logged in JSON, others in plain text
  • Some services used PostgreSQL, others MongoDB, others MySQL
  • Some services had health checks, others didn’t
  • Some services had proper error handling, others returned stack traces to users
  • Some services had tests, others didn’t
  • Security vulnerabilities were discovered months after deployment

The CTO declared: “We need automated governance. We cannot manually review every change.”

The Royal Travel Service’s Automated Governance:

The team implemented automated checks in the CI/CD pipeline:

1. Code Quality (SonarQube):

✓ Code coverage > 80%
✓ No critical security vulnerabilities
✓ No code smells > 100
✓ Technical debt < 5 days

2. Security Scanning (Snyk):

✓ No high-severity vulnerabilities in dependencies
✓ No secrets in code (API keys, passwords)
✓ No SQL injection vulnerabilities
✓ No XSS vulnerabilities

3. Container Security (Trivy):

✓ Base image is up-to-date
✓ No critical CVEs in image
✓ Container doesn't run as root
✓ No unnecessary packages installed

4. API Standards (Spectral):

✓ OpenAPI spec is valid
✓ All endpoints have descriptions
✓ All responses have examples
✓ Error responses follow standard format

5. Architecture Compliance (Custom Rules):

✓ Service has /health endpoint
✓ Service has /metrics endpoint
✓ Service logs in JSON format
✓ Service includes trace ID in logs
✓ Service has circuit breakers on external calls
✓ Service has timeouts configured
✓ Service has retry logic with backoff

The Enforcement:

When a developer tried to deploy code that violated these rules:

❌ Deployment Blocked

Violations:
1. Code coverage: 65% (required: 80%)
2. Security: High-severity vulnerability in jackson-databind
3. Container: Running as root user
4. API: Missing description for POST /bookings endpoint
5. Architecture: No circuit breaker on external API call

Fix these issues before deploying.

The Impact:

Before Automated Governance:

  • Security vulnerabilities: 15 per quarter
  • Production incidents: 8 per month
  • Code quality: Inconsistent
  • Time to review changes: 2 days (manual review)

After Automated Governance:

  • Security vulnerabilities: 2 per quarter (85% reduction)
  • Production incidents: 2 per month (75% reduction)
  • Code quality: Consistent across all services
  • Time to review changes: 10 minutes (automated)

The Court of Piepowder ensures that the laws of the City are enforced instantly, preventing the “Dusty-Footed” developers from leaving a mess for the townspeople to clean up.


Epilogue: The Enlightened City #

The complete distributed city with all systems in place

We have journeyed from the safety of the Keep to the sprawling complexity of the Metropolis. We have learned that:

  1. The Monolith is safe but stagnant.
  2. The Network is a muddy road full of bandits.
  3. Truth is a fragmented ledger of eventual consistency.
  4. Failure is a fire that must be contained by bulkheads.
  5. Governance is the watchful eye of the Ale-Conner and the shout of the Hue and Cry.

The Royal Travel Service’s Five-Year Journey:

Five years after Master Clerk Edmund and his 300 assistants were overwhelmed in the Keep, the travel service had transformed completely:

The Numbers:

  • Scale: 500,000 bookings per day (vs. 500 in the Keep)
  • Availability: 99.95% uptime (vs. 95% in the Keep)
  • Performance: 500ms average booking time (vs. 2 seconds in the Keep)
  • Coverage: Serving entire kingdom (vs. just the capital in the Keep)
  • Team: 50 engineers in 10 teams (vs. 5 clerks under Edmund)

The Architecture:

  • 50 microservices (vs. 1 monolith)
  • 150 service instances (vs. 3 monolith instances)
  • 20 databases (vs. 1 database)
  • 5 message queues (vs. 0)
  • 3 caches (vs. 0)

The Observability:

  • Centralized logging (ELK Stack)
  • Distributed tracing (Jaeger)
  • Metrics and dashboards (Prometheus + Grafana)
  • Alerting (PagerDuty)
  • Contract testing (Pact)
  • Automated governance (CI/CD pipeline)

The Resilience:

  • Circuit breakers on all external calls
  • Bulkheads for resource isolation
  • Aggressive timeouts
  • Health checks and load shedding
  • Saga pattern for distributed transactions
  • Idempotency for all state changes

The Trade-offs:

What Was Gained:

  • Massive scale (1000x more bookings)
  • High availability (even during failures)
  • Fast performance (4x faster)
  • Team autonomy (10 independent teams)
  • Technology diversity (right tool for each job)

What Was Lost:

  • Simplicity (50 services vs. 1)
  • Perfect consistency (eventual consistency)
  • Easy debugging (distributed tracing required)
  • Single deployment (complex orchestration)
  • Unified codebase (50 repositories)

The King’s Final Assessment:

The King summoned the CTO to the palace for a five-year review:

“Five years ago, I was skeptical when you announced you were tearing down the Keep to build a City. Master Clerk Edmund and his 300 assistants served us well, but they could not scale beyond 500 bookings per day. The transition was painful. There were outages. There were bugs. Lady Catherine’s bookings failed multiple times before you learned to handle distributed transactions.

But today, the Royal Travel Service is the backbone of the kingdom’s economy. My subjects book 500,000 journeys per day. The platform stays up even when services fail - Marcus showed me how the Image Resizing Service failed twice, yet the second time caused zero downtime. The service handles the Spring Festival without crashing. It serves travelers from the northern mountains to the southern seas.

Yes, the architecture is complex. Yes, there are 50 services instead of 1. Yes, Marcus needs sophisticated observability tools to debug issues at 2 AM. But this complexity is the price of scale.

You have built a City that can grow, that can adapt, that can survive partial failures. You have posted the Watch, so Marcus can see danger coming. You have built firebreaks, so fires don’t consume the entire city. You have established governance, so quality remains high even as the city grows.

This is the future of software architecture. Not because it’s simpler - it’s not. But because it’s the only way to build systems that can scale to serve an entire kingdom.

Well done.”

Building a microservices architecture is not about choosing a technology stack. It is about Urban Planning. It is about accepting that the chaos of the City is the price we pay for the scale of the Empire. We build walls not to hide, but to define boundaries. We build roads not just for speed, but for resilience. And we post the Watch, so that when the darkness comes - and it always comes - we are ready.

End of the Chronicle.