Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
One of the most challenging aspects of distributed applications like microservices is UI Composition. How to combine all the data from various services for your UI. I’m going over a few different solutions and their pros and cons for UI Composition and ViewModel Composition.
Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.
When you’re working within a monolith and a single database, we don’t have the issue of compositing data. We get all the data we need from the single source database.
As an example, here’s a typical e-commerce website with a listing of products.
The client could be a browser, and the app could be doing server-side rendering, or the client could be a JS/SPA frontend, and our App is an HTTP API.
Either way, our app can get all the data from a single database and compose and return the data as needed. It can get the product names, images, and prices from the same database.
The challenge is when multiple services are involved, each with its own database. Each service owns its own set of business capabilities and the data behind it. For example, if we had a Catalog service that owns all the product names and images. The Sales service owns the product price. How do you compose all that data together from two different services?
There are a few different options. The first is doing UI Composition.
UI Composition is about having a logical boundary own UI components that are composed to make your UI. A component (or set of components) would be responsible for getting the relevant data it requires.
The catalog service would own a Brand and Type filtering component. It would also own a Product display component to show the image and product name.
However, The price is owned by the Sales service and it would own a component for displaying the price.
With some pseudo-code, this is what the UI might look like.
We’re iterating over a list of products and then displaying the image, and name and also using the product price component.
This can work in a lot of situations however, in a grid/list, as you might have guessed, we just turned this into an N+1 problem. For each product, our price component is going to have to make an HTTP request to the sales service to get the price for that individual product.
This is not ideal at all. As mentioned, this can work in different situations, but not when we have a lot of components, nested components, or N+1 situations.
Another option is using ViewModel composition. Typically this is done with a Backend-for-Frontend (BFF), which will call the relevant services and compose the data to be returned to the client.
The BFF would call the catalog service to get all the products to display.
Then it would make a single call to Sales requesting the price for only the products it received from the catalog. We’ve removed the N+1 issue as we’re now requesting all the prices in a single request so that we can compose all the relevant data and return it to the client.
Here’s an example of what the composed data being returned to our client would look like.
You’re not limited to doing UI Composition or ViewModel composition. You can do both. Use the method that makes the most sense in a given situation.
The biggest issue people run into with ViewModel composition is needing the ability to sort, page, and filter a listing of items.
In the example of our product listing, if we were sorting by name, it would work totally fine. However, if we’re sorting by price, our current flow would not work. We were making our initial call to the Catalog service, then getting the prices for all the items. Now we would have to reverse that flow so that got all the SKUs from the Sales service are sorted by price, then call the catalog service to get the rest of the information.
While this isn’t overly complicated in this exact example, if you have more than two services and/or you want to sort and filter. For example, you want to filter by product type and sort by price.
Event Carried State Transfer
If you have all the data required in a single service, then you don’t have to worry about these types of sorting/paging/filtering issues with multiple services.
This is why event-carried state transfer is popular to distribute data as a local cache to other services.
Before I explain how it works, I must say that I’m not a fan of distributing data to other services. If you’re doing so with reference data, such as my example for query purposes (not commands), it can work. However, I do not recommend distributing transactional data around. Check out my post Event Carried State Transfer: Keep a local cache!
The way event-carried transfer works is by publishing events of state changes so other services can consume these events and keep a local cache copy of the data from another service. Again this should be non-volatile reference data.
When a catalog item is updated, let’s say the name changed, a ProductChanged event would be published.
The sales service would consume this message, which contains the ProductId (SKU) and new product name. It updates its local database of this catalog information. It’s essentially a local cache of catalog data.
With this local cache, the sales service has all the data required to compose the product listing and perform any filtering, paging, and sorting.
Developer-level members of my YouTube channel or Patreon get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.
- Do you need a Distributed Transaction? Maybe not!
- Event Carried State Transfer: Keep a local cache!
- UI composition – the blind spot of distributed systems
- Designing a UI for Microservices