Scaling microservices across teams is less about technical architecture and more about organizational design.

Let’s watch a real-world example. Imagine a company, "Acme Corp," that builds an e-commerce platform. Initially, it’s a monolith. As they grow, they break it down.

Initial State (Monolith): One big Java application. All teams work on it. Deployments are slow, risky.

First Iteration (Service Decomposition):

  • Auth Service: Handles user login, registration.
  • Product Catalog Service: Manages product information.
  • Order Service: Processes customer orders.
  • Payment Service: Integrates with Stripe.

Each service is a separate deployment. Teams start to specialize.

The Problem: Scaling Across Teams

Acme Corp has 5 engineering teams now, each owning 1-2 services. They hit a wall.

  • Team A owns Auth and User Profile.
  • Team B owns Product Catalog and Inventory.
  • Team C owns Order and Shipping.
  • Team D owns Payment and Fraud Detection.
  • Team E (Platform Team) manages infrastructure, CI/CD, shared libraries.

Suddenly, a new feature requires changes in Product Catalog (Team B), Order (Team C), and Payment (Team D). This cross-team dependency becomes a bottleneck. Deployments stall. Communication overhead explodes.

The Solution: Domain-Driven Design & Conway’s Law

The key isn’t more services; it’s aligning services with bounded contexts and letting teams own those contexts end-to-end. This is where Domain-Driven Design (DDD) and Conway’s Law meet. Conway’s Law states: "Organizations which design systems … are constrained to produce designs which are copies of the communication structures of these organizations."

If your teams are structured around technical layers (UI team, backend team, database team), your services will reflect that, leading to inter-team dependencies. If your teams are structured around business capabilities, your services will too.

Acme Corp’s Re-architecture (Organizational & Technical)

They don’t just split services further. They re-organize teams around business domains.

  1. The "Customer" Domain Team:

    • Owns Auth Service, User Profile Service, Customer Preferences Service.
    • Handles everything related to a customer’s identity and interaction.
    • Their services form a cohesive unit, interacting with each other via well-defined APIs.
  2. The "Commerce" Domain Team:

    • Owns Product Catalog Service, Inventory Service, Search Service, Pricing Service.
    • Manages what customers see and buy.
  3. The "Order Fulfillment" Domain Team:

    • Owns Order Service, Shipping Service, Returns Service.
    • Handles the lifecycle of an order after it’s placed.
  4. The "Payments" Domain Team:

    • Owns Payment Service, Fraud Detection Service, Billing Service.
    • Manages all financial transactions.
  5. The Platform Team:

    • Still exists, but focuses on enabling domain teams with infrastructure, observability, CI/CD, and shared libraries. They provide tools, not dictate service boundaries.

How This Scales:

  • Autonomy: Each domain team can develop, deploy, and scale their services independently. A change to pricing (Commerce team) doesn’t require coordination with the Shipping team.
  • Clear Ownership: Issues within a domain are immediately routed to the responsible team.
  • Reduced Communication Overhead: Instead of coordinating across 3 teams for a new feature, you might only need to coordinate with 1 or 2 teams within the same domain if it spans two contexts.
  • Strategic Alignment: Teams are closer to the business value they deliver, fostering better product decisions.

Show Me The Code (Configuration, Not Code)

This isn’t about specific code, but how teams manage their domains. Imagine a Kafka topic configuration for the Order Fulfillment team:

# Kafka Topic Configuration for Order Service
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaTopic
metadata:
  name: orders-placed
  namespace: order-fulfillment # Topic lives in the domain's namespace
spec:
  partitions: 12 # Scaled based on expected order volume
  replicas: 3
  configs:
    - name: cleanup.policy
      value: delete
    - name: segment.bytes
      value: 1073741824 # 1GB segments
    - name: retention.ms
      value: 604800000 # 7 days retention

The Order Service publishes orders-placed events. The Shipping Service (within the same Order Fulfillment domain) consumes them. If the Commerce team needs to know when an order is placed, they would subscribe to this topic, but the ownership of the orders-placed topic and its lifecycle remains with the Order Fulfillment team.

The Mental Model:

Think of domains as small, independent companies within Acme Corp. Each company has its own CEO (Product Owner), engineers, and P&L (responsibility for their services). They can hire, fire, and innovate within their domain. The central "Acme Corp" provides shared services like HR, legal, and IT infrastructure.

The core idea is to minimize synchronous communication and shared mutable state between teams. Asynchronous communication (events, message queues) and well-defined, independent APIs are the technical manifestations of this organizational alignment.

The Counterintuitive Bit:

Many teams, when struggling with microservices, reach for more complex inter-service communication patterns like GraphQL federation or sophisticated API gateways to manage cross-service queries. The truly effective solution is often to invert this: restructure the teams and service boundaries such that these complex cross-service operations become intra-service operations within a single, well-defined domain team’s responsibility. The goal is to make the team boundary align with the domain boundary, making the services within that domain talk to each other as if they were one system, but exposing a clean, stable API to the outside world.

The next hurdle you’ll face is managing shared data and distributed transactions across these now highly autonomous domains.

Want structured learning?

Take the full Microservices course →