This is the seventh article in our series, where we design a simple order solution for a hypothetical company called Simply Order. The company expects high traffic and needs a resilient, scalable, and distributed order system.
In the previous articles:
- Simply Order (Part 1) Distributed Transactions in Microservices: Why 2PC Doesn’t Fit and How Sagas Help
- Simply Order (Part 2) — Designing and Implementing the Saga Workflow with Temporal
- Simply Order (Part 3) — Linking It All Together: Connecting Services and Watching Temporal in Action
- Simply Order (Part 4) — Reliable Events with the Outbox Pattern (Concepts)
- Simply Order (Part 5) — Hands-On: Building the Outbox Pattern for Reliable Event
- Simply Order (Part 6) – Making APIs Idempotent: Because Users Double-Click and Networks Lie
In previous lessons, we built core services like Order, Payment, and Inventory, focusing on making our distributed transactions reliable using Sagas, the Outbox pattern, and Idempotency.
Now imagine the business comes with a very simple requirement:
On the Order Details page, show the order with its items, and for each item show the current stock status so customers know if they can re‑order.
The Problem
From a microservices perspective, our system looks like this:
- Order Service owns orders and items by their SKU.
- Inventory Service owns item details along with stock and availability per item.
Each service has its own bounded context, but users need to see their orders with the corresponding item details.
Solution 1: Direct client-to-microservice communication
This is the simplest and most naïve solution: the frontend talks directly to the services:
- Call
GET /orders/{id} fromthe Order service. - For each item in order, call
GET /inventory/{sku}from the Inventory service
This leads to several problems:
- The frontend suddenly “knows” about internal service boundaries.
- Latency explodes; a single order fetch could trigger dozens of calls based on the number of items, known as the N+1 Problem (1 order + N items).
- The frontend becomes tightly coupled with internal service APIs, so any change in service APIs requires changes in all frontend clients (webpages, mobile apps, third parties, etc.).
In short, this approach is unsuitable for a continuously evolving business and APIs.
Solution 2: API Composition
In this approach we move the complexity of aggregating responses from different services into a separate service often called the Aggregator/Composer Service:
- The frontend sends requests to the Composer Service.
- The Composer Service calls multiple microservices (Order, Inventory, etc.).
- It collects, transforms, and merges data into a unified result, returning it to the client.
With this approach, we can overcome the problems of the naive approach:
- Internal service boundaries are hidden from the client API.
- The aggregator service decouples the frontend from internal APIs, so even with evolving APIs, we can preserve our contract with clients.
- With a smart implementation, we can optimize the N+1 problem.
Simple custom implementation
We build a thin service called Composer or Aggregator that, for each view of our data, provides a REST endpoint which aggregates all necessary endpoints and returns the corresponding result.
Some minor drawbacks of this approach:
- We have to build separate endpoints for each view of our data. For example, if some pages need different details, we may need to create different endpoints to handle those.
- It can get more sophisticated with retries, parallelism, mapping, and error handling.
API Composition with GraphQL
As mentioned in GraphQL official Page
GraphQL is an open‑source query language for APIs and a server‑side runtime. It provides a strongly‑typed schema to define relationships between data, making APIs more flexible and predictable. And it isn’t tied to a specific database or storage engine — it works with your existing code and data, making it easier to evolve APIs over time.
GraphQL serves as a unified data layer across multiple services. This way you simplify API management and reduce dependencies between teams. It enables efficient data fetching while keeping the API surface flexible and maintainable.
GraphQL is built around a few core components that work together to let clients request exactly the data they need from a single endpoint. Here’s a simple introduction to each main part:
- Language: A query language letting clients request exactly the data they need.
- Schema: Defines available data types, fields, and operations (queries, mutations, subscriptions).
- Server: Processes queries, validates them against the schema, and returns results.
- Resolvers: Functions that fetch data for each field in the schema.
- Data Sources: Databases, APIs, or services where the actual data lives.
Together, these components allow GraphQL to unify data from multiple sources into a single, flexible API.
- Rather than custom endpoints for each composite view, GraphQL exposes a type-based schema that lets clients specify exactly what data they want.
- Resolvers act as the composition code—each field in the schema calls the appropriate microservice, then GraphQL merges all results for the client.
GraphQL can be implemented with different languages and frameworks. Spring Boot provides first-class support for GraphQL, making it easy to build type-safe, efficient APIs in Java.
Warp Up
In this article we moved from naive client‑to‑microservice calls to a proper composition layer, and then saw how GraphQL naturally fits as a unified API across our Order and Inventory services. Instead of creating custom endpoints for every page, a strongly‑typed schema and resolvers let clients ask for exactly the data they need from a single endpoint, without leaking internal boundaries.
In the next article, we’ll take this one step further and implement this GraphQL composition layer using spring‑graphql on top of our existing microservices. We’ll design the schema around the “Order Details” use case, wire resolvers to Order and Inventory, and see how to handle cross‑service calls, errors, and performance concerns in a clean, type‑safe way


Top comments (0)