This is a recent follow-up pattern to my series on Composite UIs in Microservices, which explores various strategies for composing at the edges. Other posts in this series:
- A primer
- Composition options
- Client composition
- Server composition
- Data composition
- Vertical Slice APIs
When looking at a client-side composition, the next logical question is "how do my client-side components communicate with services?". Typically, there are two main approaches:
In many of my applications and systems I deal with, I don't necessarily always have a single page that composes together. Instead of something like this:
Where I have a single page that composes many widgets (think Amazon), I instead of large numbers of pages that are wholly owned by specific services. There's then some sort of menu to choose between them:
In this application, I have a single shell that composes multiple services UIs, but each service UI stretches all the way from the backend to the frontend.
This service widget needs to talk to the front end, so my natural inclination was to look at "backend for frontend". But this picture looks like:
It's still API Gateways, just slightly segregated down per application.
API Gateways are great in situations where you have highly segregated applications and APIs with highly separate release pipelines. But in our typical backend scenarios, we control the UI and API deployments, so any kind of composition in the API layer is complete overkill.
For these situation, we introduce vertical slice APIs
Vertical Slice APIs
When we have whole pages or sections of the application wholly owned by a specific service, it's advantageous to go ahead and couple a specific API to that page or screen. It's cheaper and simpler for the UI to "know" exactly what API and request/response to call. So we start with a group of users, using a single system, that composes service-specific UI components together:
And a second system with a different set of users that also composes service-specific UI components together:
Both systems consume bother services, but how should we build the APIs for each? With an API gateway, we'd have a single pinch point that both systems need to go through for both services. With BFF, we'd have two API gateways that then mediate for both services.
Vertical slice APIs are different. Instead of mediating through API gateways, we create purpose-built APIs for each system:
Each service UI communicates to its own system-specific service API. That service API is intended only for that service - and because our service boundaries are logical not physical, the service boundary extends all the way to the UI.
With this approach, if I need to change the UI, I can change the API without worrying about any other potential consumers. If I need to change the API, I've only got one API consumer to worry about. It's vertical slice architecture now extended to the APIs we create for composite UIs.
There are some downsides to this approach - mainly it results in more APIs. But like vertical slice application architecture, this intentional decoupling means we're much faster in building out screens and APIs.
Additionally, I really don't have to worry about things like Swagger, since my APIs are completely purpose-built for a single service. When we build features, the UI and API get designed and built together. For desktop applications, we'll go so far as to share the API model request/response objects as assemblies, so that we don't have to "generate" any kind of client SDK or response types.
When we see a natural coupling of front end and back end, we can build along these vertical slices to ensure that we can add and modify code without worrying about affecting any other consumer.