All Articles Claude Code

Claude Code for Microservices Architecture Review

Microservices are great in theory. You decompose your monolith into independent services, each with clear responsibilities. Teams move fast. Deployment is independent. Life is good.

Microservices are great in theory. You decompose your monolith into independent services, each with clear responsibilities. Teams move fast. Deployment is independent. Life is good.

Then reality hits. Six months later, you’ve got circular dependencies between services. You’re sharing a database across three services that should be independent. Your API calls create cascade failures. You’ve accidentally rebuilt a distributed monolith without any of the benefits. The architecture has drifted from what you intended into a tangle of implicit dependencies.

This is where Claude Code’s architecture analysis capabilities shine. Instead of paying consultants five figures to spend a week reviewing your system design, you can run an automated architecture audit that identifies coupling patterns, detects circular dependencies, maps service boundaries, and generates architectural decision records. In hours, not weeks. And the analysis is based on your actual code, not someone’s assumptions about how you work.

In this article, we’re building a comprehensive microservices architecture review system that runs at the system design layer. This isn’t code review — it’s topology review. We’re not looking at implementations; we’re looking at how services relate to each other, where the boundaries are fuzzy, and where your architecture is working against you.

Why Architecture Matters (And Why It’s Hard to Review)

Most companies invest heavily in code review. Every pull request gets eyeballed. Linters run. Tests pass. But architecture reviews? Those happen once a year at some conference, if at all.

The problem is scale and abstraction. A single service might have great code but participate in terrible coupling patterns. You can’t see the coupling from inside one service. You need to step back and look at the entire system topology. When you’re managing dozens or hundreds of services—as large companies do—the complexity compounds exponentially. Each service has dependencies, those dependencies have dependencies, and pretty soon you’ve got a graph so complex that no single person can hold it all in their head.

Architecture review is fundamentally different from code review. When you review code, you’re looking at a specific file or function. You can understand it in isolation. But when you review architecture, you need context spanning the entire system. You need to understand not just what each service does, but how all the services interact, which ones are on critical paths, which ones are bottlenecks, where failures cascade.

This is where humans struggle. Our cognitive capacity for graph analysis is limited. We can reason about small systems intuitively, but once you cross a threshold (maybe 10-15 services), the complexity overwhelms our ability to reason about it directly. We need tools that can see patterns at scale. Claude Code is exactly that tool.

Here are the patterns we see everywhere:

  • Circular dependencies: Service A calls Service B, which calls Service C, which calls Service A. This creates distributed transactions and failure cascades. A timeout in any part of the cycle can break the entire cycle.
  • Shared databases: Three services reading/writing to the same database. Changes to one break the others. No clear data ownership. This violates the fundamental principle of microservices—each service should own its data.
  • Synchronous chains: Service A must call Service B which must call Service C before returning. One slow service kills the whole chain. If you’ve got a chain of five synchronous calls, you’re taking the latency of the slowest one and multiplying it by five.
  • God services: One service called by everything. It becomes a bottleneck and a single point of failure. If it goes down, half your system breaks.
  • Invisible coupling: Services call each other via poorly-documented contracts. Changes break downstream consumers silently. Someone “refactors” a response format and breaks six services that depend on it.

The challenge is that these patterns are invisible until you analyze the whole system. A single service’s code looks fine. But when you step back and see how 30 services interact, the problems become obvious. Claude Code can automate this analysis by reading through all your service codebases, extracting dependencies, building a graph, and running algorithms to find problems. This gives you visibility that’s normally invisible until disaster strikes.

The Architecture Review Pipeline

Here’s our approach—a systematic process that scales from 5 services to 500. The pipeline takes your entire codebase and transforms it into architectural insights, moving from raw data to actionable findings in discrete phases.

  1. Service Inventory: Identify all services and their boundaries
  2. Dependency Mapping: Build a directed graph of service-to-service calls
  3. Pattern Detection: Find circular dependencies, cascade chains, shared databases
  4. Risk Analysis: Score each pattern for architectural risk
  5. Data Ownership Audit: Check database boundaries and shared data anti-patterns
  6. Communication Pattern Review: Identify synchronous vs. async and optimize
  7. ADR Generation: Create architecture decision records documenting findings

Let’s build this step by step. The architecture review system works by reading through service repositories, extracting metadata, building dependency graphs, and running analysis algorithms. It’s sophisticated work, but Claude Code can do it systematically.

Phase 1: Service Inventory and Metadata

First, we need to know what services exist. Rather than relying on documentation (which is always out of date), we scan the actual codebase for service definitions. This discovery phase is crucial because it establishes ground truth. If it’s not in code, it doesn’t exist as far as our analysis is concerned.

The service inventory isn’t just a list. For each service, we extract metadata that becomes critical for later analysis: what language it’s written in, what type of service it is (API server, worker process, scheduler), who owns it, how critical it is to the system. A scheduled cleanup job is fundamentally different from the payment processing service, and our analysis needs to understand those differences.

interface ServiceMetadata {
  serviceName: string;
  repository: string;
  type: "api" | "worker" | "scheduler" | "frontend";
  mainLanguage: string;
  baseUrl: string;
  owner: string;
  deploymentCluster: string;
  criticality: "core" | "supporting" | "experimental";
  description: string;
  apiSchemaPath?: string;
}

interface ServiceRegistry {
  services: Map<string, ServiceMetadata>;
  lastUpdated: Date;
  totalServiceCount: number;
}

async function buildServiceInventory(
  repoRoots: string[],
): Promise<ServiceRegistry> {
  const services = new Map<string, ServiceMetadata>();

  for (const root of repoRoots) {
    const metadata = await discoverServiceMetadata(root);
    if (metadata) {
      services.set(metadata.serviceName, metadata);
    }
  }

  return {
    services,
    lastUpdated: new Date(),
    totalServiceCount: services.size,
  };
}

This phase gives us a complete inventory of services. We’re not just listing them; we’re extracting critical metadata: language, type, owner, criticality level, API schema location. This metadata becomes the foundation for all downstream analysis. Why extract criticality? Because removing a circular dependency is different if it involves the payment service versus the analytics service. A synchronous chain is tolerable in a support dashboard but unacceptable in the payment flow. Understanding which services are critical helps us prioritize fixes and communicate risk appropriately to stakeholders.

Phase 2: Dependency Mapping

Now we scan each service’s code and find every external service call. We’re building a directed graph where nodes are services and edges are dependencies. This graph is the foundation of everything that follows. Every pattern we detect, every risk we assess, every recommendation we make comes back to this graph.

The dependency extraction needs to be smart. We’re not just looking for HTTP calls. Services communicate via many mechanisms: direct HTTP/REST calls, gRPC, message queues, database reads that implicitly depend on another service’s schema, file-based integration points. Each communication type has different implications for coupling and failure modes.

interface ServiceDependency {
  fromService: string;
  toService: string;
  callCount: number;
  callPatterns: string[]; // HTTP, gRPC, message queue, etc.
  isAsync: boolean;
  isRequired: boolean; // Cascading failure if this service is down?
  averageLatency?: number;
}

interface DependencyGraph {
  edges: Map<string, ServiceDependency[]>;
  nodes: Set<string>;
}

The dependency mapping engine uses regex patterns and AST analysis to find HTTP calls, message queue subscriptions, gRPC connections, and other inter-service communication. It walks the codebase looking for patterns like:

  • axios.get('https://payment-service/charges')
  • queue.subscribe('order.created', handler)
  • grpcClient.callUserService()

By scanning all services, we build a complete map of who talks to whom, how they talk (sync vs async), and how often. The key insight: async is less risky than sync. An HTTP call that blocks the request thread is a coupling problem. A message queue subscription is decoupled — the producer doesn’t wait for the consumer. This distinction becomes critical when we’re assessing risk.

Phase 3: Pattern Detection and Risk Scoring

Now we have the graph. We can run algorithms to find problematic patterns: circular dependencies, cascading chains, bottlenecks. This is where the real analysis happens. We’re not just identifying patterns; we’re scoring them for risk.

For circular dependencies, we use depth-first search to detect cycles in the graph. When we find a cycle, we score it based on multiple dimensions: whether all calls in the cycle are synchronous (highest risk), whether core services are involved (payment, auth = higher risk), how many services are in the cycle (longer cycles harder to fix).

A cycle involving payment and auth services that are all synchronous is critical—failures propagate, timeouts cascade, and the system can deadlock. A cycle in experimental services with async messaging is lower risk—the producer doesn’t wait for the consumer, so failures don’t cascade the same way.

For synchronous chains, we find long paths of synchronous calls. User request → Service A → Service B → Service C → Response is a problem because if any service is slow, the whole chain blocks. The latency adds up. If each call adds 50ms, a five-hop chain adds 250ms just to inter-service communication. Add database latency on top and you’ve got timeouts.

async function detectPatterns(
  graph: DependencyGraph,
): Promise<ArchitectureIssue[]> {
  const issues: ArchitectureIssue[] = [];

  // Detect circular dependencies
  const cycles = findCycles(graph);
  for (const cycle of cycles) {
    issues.push({
      type: "circular-dependency",
      severity: calculateCycleSeverity(cycle),
      services: cycle,
      description: `Circular dependency detected: ${cycle.join(" -> ")}`,
    });
  }

  // Detect long synchronous chains
  const chains = findLongSynchronousChains(graph, 3); // 3+ hops
  for (const chain of chains) {
    issues.push({
      type: "synchronous-chain",
      severity: calculateChainSeverity(chain),
      services: chain,
      description: `Long synchronous chain: ${chain.join(" -> ")}`,
    });
  }

  return issues;
}

For shared databases, we scan .env files and configuration to find which services connect to which databases. Multiple services on the same database violates microservices principles and creates tight coupling at the data layer. When two services share a database, they’re sharing schema, which means they’re coupled at a fundamental level. One service can’t change its table structure without potentially breaking the other. This pattern often emerges accidentally—someone needed shared data, found a “temporary” solution, and never refactored.

Phase 4: Communication Pattern Optimization

We analyze how services communicate and suggest improvements. If you’re making sync HTTP calls to a critical service frequently, that’s a bottleneck. Consider caching to reduce call frequency or async messaging to decouple the caller from the callee. If it’s infrequent but blocking, move to async queues so the caller doesn’t wait.

This phase generates specific recommendations, not generic advice. We’re not saying “use async instead of sync” (which is too broad). We’re saying “this specific call happens 100 times per minute and blocks the request thread—here’s why caching would help and how to implement it.” This specificity is what makes recommendations actionable.

This phase generates specific recommendations:

  • Reduce coupling by converting sync to async messaging
  • Add caching to reduce call frequency (reducing calls by 90% means 90% less latency)
  • Batch requests instead of one-by-one calls (turning 100 individual calls into 1 batched call)
  • Use eventual consistency instead of distributed transactions (removing the need for the cycle entirely)

Phase 5: Architecture Decision Record Generation

The final piece: automatic documentation. Generate ADRs (Architecture Decision Records) that capture findings and decisions. An ADR documents why a decision was made, not just that it was made. This becomes institutional memory.

interface ArchitectureADR {
  title: string;
  status: "proposed" | "accepted" | "deprecated";
  context: string;
  decision: string;
  consequences: string;
  alternatives: string[];
  relatedIssues: ArchitectureIssue[];
  generatedAt: Date;
}

async function generateADRs(
  issues: ArchitectureIssue[],
): Promise<ArchitectureADR[]> {
  const adrs: ArchitectureADR[] = [];

  for (const issue of issues) {
    const adr: ArchitectureADR = {
      title: `Address ${issue.type}: ${issue.services.join(" -> ")}`,
      status: "proposed",
      context: `${issue.description}. This creates tight coupling and failure cascade risks.`,
      decision: `Refactor to reduce coupling: ${generateFix(issue)}`,
      consequences: `Services become more independent, reducing failure blast radius.`,
      alternatives: generateAlternatives(issue),
      relatedIssues: [issue],
      generatedAt: new Date(),
    };
    adrs.push(adr);
  }

  return adrs;
}

An ADR documents the current problem (context), what you’re deciding to do about it (decision), what will change as a result (consequences), and what alternatives you considered. ADRs become your architecture’s institutional memory. Six months later when someone asks “why is payment calling user?”, you can point to the ADR explaining the decision and the trade-offs involved. This is critical for large teams where architectural knowledge is spread across many people.

Putting It All Together

Here’s the complete pipeline orchestrated. This is where all the phases connect into a coherent system that goes from raw code to actionable architectural insights.

async function runArchitectureReview(config: {
  serviceRoots: string[];
  outputDir: string;
}) {
  console.log("🏗️  Starting microservices architecture review...\n");

  // Phase 1: Inventory
  console.log("📋 Phase 1: Building service inventory...");
  const services = await buildServiceInventory(config.serviceRoots);
  console.log(`✓ Found ${services.totalServiceCount} services\n`);

  // Phase 2: Dependencies
  console.log("🔗 Phase 2: Mapping dependencies...");
  const graph = await buildDependencyGraph(services.services);
  console.log(
    `✓ Found ${graph.nodes.size} services with ${Array.from(graph.edges.values()).reduce((sum, edges) => sum + edges.length, 0)} dependencies\n`,
  );

  // Phase 3: Pattern detection
  console.log("🔍 Phase 3: Detecting architectural issues...");
  const issues = await detectPatterns(graph);
  const critical = issues.filter((i) => i.severity === "critical");
  console.log(`✓ Found ${critical.length} critical issues\n`);

  // Phase 4: Generate ADRs
  console.log("📝 Phase 4: Generating architecture decisions...");
  const adrs = await generateADRs(issues);

  // Output results
  for (const adr of adrs) {
    const markdown = formatADRAsMarkdown(adr);
    const filename = adr.title
      .toLowerCase()
      .replace(/\s+/g, "-")
      .replace(/[^a-z0-9-]/g, "");

    fs.writeFileSync(path.join(config.outputDir, `${filename}.md`), markdown);
    console.log(`✓ Generated ADR: ${adr.title}`);
  }

  // Summary report
  console.log("\n=== ARCHITECTURE REVIEW SUMMARY ===");
  console.log(`Services: ${services.totalServiceCount}`);
  console.log(
    `Dependencies: ${Array.from(graph.edges.values()).reduce((sum, edges) => sum + edges.length, 0)}`,
  );
  console.log(`Issues Found: ${issues.length}`);
  console.log(`Critical: ${critical.length}`);
}

Run this once against your entire microservices landscape. Out comes a complete analysis: what services exist, how they’re connected, where the problems are, and what you should do about it. The entire analysis is automated and repeatable—you can run it weekly to detect architectural drift.

Real-World Example: Order, Payment, and Inventory

Let’s say you’ve got order, payment, and inventory services. Order calls Payment (sync HTTP). Payment calls Inventory to check if items exist (sync HTTP). Inventory calls Order to prevent overselling (sync HTTP).

Your analysis would find:

  • Circular dependency: Order → Payment → Inventory → Order
  • Synchronous chain: User request → Order → Payment → Inventory (4 hops)
  • Risk score: 85 (critical)

The recommendation: move Inventory → Order call to async. Payment publishes an “order-updated” event. Order subscribes. No cycle. Request returns immediately. Order processes asynchronously. The Inventory service doesn’t wait for confirmation—it publishes and moves on. This breaks the cycle and removes the synchronous chain.

One ADR documents this decision. Six months later, someone reads it and understands why the architecture is the way it is. No more wondering why services are connected in seemingly weird ways.

Why This Matters

Microservices architecture is deceptively hard. You can have excellent code quality but terrible system design. You need tools to see the big picture. Claude Code gives you that perspective automatically. You’re no longer dependent on hiring expensive consultants or relying on the architectural knowledge of a few senior engineers.

Instead of hiring architects to review your system once per year, you run automated analysis continuously. You catch problems early. You document decisions systematically. You make architecture evolution intentional rather than accidental. When a new pattern emerges, you can add detection for it and it applies to future reviews automatically.

The best part? This scales. As you add more services, the analysis gets more valuable. The pattern detection catches issues that would be invisible in a design review. You go from annual architecture reviews (if you’re lucky) to continuous analysis. You move from reactive problem-solving (fixing issues after they cause outages) to proactive problem-prevention (detecting issues before they matter).

Architecture quality becomes measurable and trackable. You can show concrete progress on metrics: circular dependencies eliminated, data ownership clarified, resilience patterns added. That’s powerful for making the business case for architectural work.

Teams understand why services are structured the way they are because the ADRs explain the reasoning. New team members can read the ADRs and understand the current state of the system without relying on oral histories or outdated documentation. This is especially valuable as teams scale and distributed knowledge becomes a liability.

Common Pitfalls in Microservices Architecture

As teams adopt microservices, they often hit the same problems repeatedly. Understanding these pitfalls helps you recognize them in your own system and use the review tool to detect them.

The Gradual Degradation Problem: Your microservices architecture doesn’t go bad overnight. It degrades gradually. One new developer doesn’t know about the payment service and adds a direct database link. Someone finds a “temporary” workaround that never gets cleaned up. Six months later you’ve got invisible coupling everywhere. The architecture review catches this by comparing current state to intended state.

This pattern is especially insidious because no single change looks bad. Each individual decision—”we’ll just use this database connection here,” “we’ll call this one service synchronously instead of queuing”—feels reasonable at the time. But over months, the accumulation transforms a well-architected system into a tangled mess. The review tool provides the meta-level perspective that humans can’t maintain. It says, “You started with clear service boundaries. Now 30% of your service calls are synchronous and tightly coupled. That’s the problem.”

The Scaling Bottleneck: As traffic grows, certain services become bottlenecks. If you’ve got a synchronous call chain where every user request hits five services, and you’re getting 1000 requests per second, you’re making 5000 inter-service calls per second. The latency adds up. The review tool identifies these chains and prioritizes fixing the ones that matter most.

In real systems, bottlenecks are rarely obvious until they cause production outages. You might have a recommender service that’s called from three different places. Each call is “fast enough” individually—50ms, 100ms, whatever. But multiply that across your request volume and suddenly the recommender service is consuming 80% of your CPU while the rest of the system is idle. The review tool shows you this pattern: “Recommender service is in the critical path for 40% of user requests. This is a bottleneck. Consider caching, pre-computing recommendations, or splitting the feature.”

The Silent Contract Violation: Service A changes its response format without telling Service B. Service B breaks, but doesn’t immediately fail—it just returns wrong data. The issue propagates downstream until someone notices. The review tool can’t catch this directly, but it can identify problematic contracts and recommend documentation and versioning strategies.

These are the failures that keep on-call engineers up at night because the symptom (wrong data in user-facing features) is disconnected from the root cause (a service changed its API response). The review tool helps prevent these by identifying which services are consumers of which other services, and flagging contracts that aren’t versioned or documented. It says, “Service A has 7 downstream consumers. If it changes its API, all 7 must update. This is high-risk coupling. Consider API versioning or event-based communication instead.”

Advanced: Real-Time Monitoring and Continuous Governance

You can extend this to monitor architecture health continuously. After each major change, run the analysis. If new circular dependencies are introduced, alert the team. If a service suddenly becomes a god service, trigger a review. This transforms architecture from something you think about once a year to something that’s continuously monitored and improved. You’re basically running health checks on your architecture.

Imagine running this analysis on every merge to your main branch. Each merge gets an architectural scorecard: “Cyclomatic coupling increased by 3%, new circular dependencies detected (1), average service call latency increased by 12ms. These are within acceptable ranges.” Or: “Critical circular dependency introduced in checkout flow. This must be resolved before deployment. Generated ADR recommending event-based resolution.”

This creates governance without bureaucracy. It’s not a person saying “you can’t merge this.” It’s an objective analysis showing data about the changes. Teams see the data and make informed decisions. Some teams set gates: “Don’t merge if new critical circular dependencies are detected.” Others use it as advisory: “This introduces some coupling, but we accept it because the performance gain is worth it. Here’s the ADR documenting that decision.”

Real-time monitoring also catches architectural drift you’d never notice manually. A service that was communication-free with another service suddenly starts calling it. Why? Investigation reveals a new feature that created implicit coupling. Was this intentional? Should it be? The architectural analysis surfaces these decisions for review.

Over time, you accumulate data about your architecture’s health. You can show graphs: “Our cyclomatic coupling score dropped 15% after we introduced event-based communication. Our circular dependency count went from 7 to 2 after last quarter’s refactoring. Our average service call chains went from 6 hops to 3.” This data tells the story of your architecture’s evolution. It justifies architectural work: “The analysis shows this refactoring improved our architecture quality metrics by 18%. That’s concrete value.”

Handling Distributed Transactions and Data Consistency

One of the most complex patterns the review tool identifies is distributed transactions across services. When Order calls Payment which calls Inventory, and any one of them fails, what happens? Do you roll back the entire transaction? That’s a distributed transaction—potentially expensive, complex, and fragile. Most microservices teams avoid them by using eventual consistency patterns instead.

The review tool can identify which operations currently use distributed transactions and flag them as high-risk. It then suggests alternatives: sagas, event sourcing, or compensating transactions. A saga is a choreography of services communicating via events. Order publishes “order-created”, Payment subscribes and publishes “payment-authorized”, Inventory subscribes and publishes “inventory-reserved”. If any step fails, compensating events roll back previous steps. It’s eventually consistent but resilient.

The analysis might reveal that your payment flow is implemented as a distributed transaction across five services. That’s critical—if any service is slow or down, the entire payment fails. The recommendation is to convert to a saga: “Break the synchronous chain into asynchronous events. This makes the flow resilient to individual service failures and dramatically improves latency.”

This is beyond pattern detection—it’s architectural guidance. Claude Code doesn’t just tell you what’s wrong; it suggests concrete alternatives and shows how similar organizations have solved the same problem.

Team Adoption and Cultural Change

For architectural reviews to be effective, they need to be part of your culture. Teams should understand that the tool isn’t policing them—it’s helping them. A finding from Claude Code isn’t criticism; it’s data. The question isn’t “why did Claude find this?” but “what does this finding tell us about our design?”

Start with educational reviews. Show the findings to architects and senior engineers. Let them validate the findings and adjust detection rules if needed. Once people understand the tool’s value, it becomes something teams want to use rather than something imposed on them.

Consider running a review on your current system and sharing the results in a team meeting. “Here’s what our architecture looks like today. Here are the patterns Claude identified. Some are problems we should fix. Some are deliberate tradeoffs we’ve made. Let’s discuss which is which.” This conversation—informed by objective data—is immensely valuable. It aligns the team on what the architecture should be, what problems exist, and what the priorities are.

Over time, the review becomes something developers understand and anticipate. They know that a new synchronous call chain will be flagged. They know that a shared database will trigger an alert. So they design around those patterns from the start. The review tool becomes a teaching mechanism—developers learn what patterns matter and structure their code accordingly.

Some teams integrate the review into their definition of done. Before a major service or feature is released, it gets an architecture review. This ensures new work doesn’t introduce new architectural problems. It also surfaces decisions: “This feature introduces a circular dependency. We believe it’s worth the tradeoff because of X. Here’s the ADR documenting the decision.” That explicit decision-making is valuable institutional knowledge.

Conclusion

Microservices architecture review is one of the most impactful uses of Claude Code at the system level. It gives you visibility into the structure of your entire system, identifies problems that would take months to discover through experience, and generates documentation that becomes the source of truth for architectural decisions. Combined with ADR generation, you’ve got a system that not only tells you what’s wrong but explains why and what to do about it. And every decision is documented for future reference.

That’s the power of system-level analysis. It’s not about code; it’s about topology. It’s not about individual services; it’s about the interactions between them. And that’s where the real architectural problems hide.


-iNet

Free Discovery Call

Start With a Conversation, Not a Commitment

Every engagement begins with a free 30-minute discovery call. We'll map what's slowing your business down and tell you exactly what we'd fix first – no pitch deck, no obligation.