Advanced · Distribute

Find Service Boundaries Before Adding Spring Cloud

Use business ownership, data authority, and change coupling to decide whether a split earns its distributed cost.

Primary source: Spring Cloud 2024 Reference Documentation

50 min

Lesson 1 of 4

1d streak

The Architectural Question

Premature splits convert in-process method calls into network hops that nobody owns, and the failure modes arrive before the benefits.

What this guide proves

  • Give each service authority over its own data
  • Keep synchronous dependency depth shallow
  • Prefer platform-native discovery on Kubernetes unless a registry solves a real need

Microservice Decomposition & Bounded Contexts

Decomposing an application into microservices should be driven by business domain capabilities and Domain-Driven Design (DDD) Bounded Contexts, not technical layers. A common anti-pattern is creating an "Entity Service" or "Database Microservice", which creates a distributed monolith with all the operational complexity of microservices and none of the benefits.

A microservice boundary is legitimate only if it provides:

1. Autonomous Deployability: A microservice team can build, test, and deploy without coordinating deployments with other service teams.

2. Shared-Nothing Persistence: Every microservice owns its private database. Microservices NEVER share database tables or schemas directly.

The Shared Database Trap

If Service A and Service B query the same PostgreSQL database tables directly, they are not microservices. A schema change in Service A will silently break Service B at runtime without warning.

Spring Cloud Release Train Compatibility & Starter Hierarchy

Spring Cloud releases use alphabetical release train codenames. For Spring Boot 3.5.x, align with Spring Cloud 2025.0.x (codename Northfields). For reference, 2024.0.x is Moorgate (for Boot 3.4.x), and 2023.0.x is Leyton (for Boot 3.2.x/3.3.x). Unlike standard independent libraries, Spring Cloud dependencies must align strictly with your Spring Boot version using the Spring Cloud Dependency Management BOM.

pom.xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2025.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Distributed Data Management: Sagas vs Two-Phase Commit

In a microservices architecture with private databases, atomic multi-service transactions (XA / 2PC) do not scale across network boundaries and cloud availability zones.

Instead, systems embrace eventual consistency using the Saga pattern: a sequence of local transactions where each step updates data within a single service and publishes an event or message to trigger the next local transaction. If a step fails, compensating transactions undo preceding steps.

Contract Testing and Service Boundary Verification

Verify service boundaries and prevent breaking API changes between independent services using consumer-driven contract testing (Spring Cloud Contract or Pact):

snippet.bash
# Run consumer-driven contract verification against stub runner
./mvnw clean test -Dtest=OrderContractVerificationTest
# Verifies producer API response matches consumer expectations exactly

Active Recall Checkpoint

Why must two microservices never share a single database schema?

Hint: Consider team independence and deployment isolation.

Show answer & explanation

Sharing a database couples the services at the persistence layer, destroying autonomous deployability and causing breaking changes whenever one service alters a table column.

Runtime Architecture & Flow

Dependency direction

Boundaries follow ownership

A microservice boundary buys independent ownership and deployment by introducing network, consistency, and operational failure modes.

Give each service authority over its own data

B1

Keep synchronous dependency depth shallow

B2

Prefer platform-native discovery on Kubernetes unless a registry solves a real need

B3

Read these as design constraints. The arrows belong in the implementation only after the dependency rule is chosen.

Working Implementation

Code Walkthrough

Write an architecture decision record for keeping or splitting catalog and ordering.

ServiceBoundariesLab.java
package com.example.microservices;

import org.springframework.stereotype.Service;

import java.util.UUID;

@Service
public class ServiceBoundariesLab {

    public record SagaCommand(UUID sagaId, String customerId, int amountCents, String status) {}

    // Each microservice owns its private database schema; transactions coordinate via Saga steps
    public SagaCommand startOrderSaga(String customerId, int amountCents) {
        return new SagaCommand(UUID.randomUUID(), customerId, amountCents, "PENDING_PAYMENT");
    }

    public SagaCommand compensateOrder(SagaCommand state) {
        // Compensating step executes if downstream inventory or payment fails
        return new SagaCommand(state.sagaId(), state.customerId(), state.amountCents(), "CANCELLED_COMPENSATED");
    }
}
Microservices must never share database tables; shared databases create tightly coupled distributed monoliths.
Distributed transactions are coordinated via Sagas with local transactions and compensating actions.
Align Spring Cloud dependencies strictly with the target release train BOM (e.g. 2024.0 Leyton).

Synthesis & Production Verification

Production Acceptance Criteria

  • Assign data authority per service with no shared tables
  • Map synchronous dependencies and cap their depth
  • Record split-versus-keep reasoning in an ADR others can challenge

Architectural & Technical Review

Core Interview Questions

Q01Why is the shared database pattern considered an anti-pattern in microservices architecture?Show answer

Sharing database tables tightly couples services at the schema level, destroying autonomous deployability. A column change by one service breaks dependent services at runtime and introduces database locking contention.

Q02How does the Saga pattern manage distributed transactions without two-phase commit (XA)?Show answer

A Saga executes a sequence of local database transactions across services. Each local transaction updates state and publishes an event; if a step fails, the saga triggers compensating transactions to roll back previous changes.

Q03How does Spring Cloud release train BOM management prevent dependency version conflicts?Show answer

Importing the spring-cloud-dependencies BOM ensures that compatible versions of Spring Cloud Gateway, Config, Eureka, and OpenFeign are aligned with the host Spring Boot version automatically.

Production Pitfalls

Critical Anti-Patterns

  • Pitfall: Creating fine-grained entity microservices that share a single database schema — Creates an operational distributed monolith with high latency, complex deployments, and zero autonomy.
  • Pitfall: Attempting distributed two-phase commit (XA) across cloud availability zones — Introduces blocking coordinator locks and high latency, crashing availability during network partitions.

Primary Documentation & Specifications

Supplementary Module Resources