Composite UIs for Microservices - Client Composition

Posts in this series:

In the last post, we looked at possible areas of composition:

  • Client
  • Server
  • Database

In this post, we'll look at compositional options in the browser. From the outset, I can at least confirm that browser-side composition:

  1. Does not require you to use a SPA framework
  2. SPA frameworks generally do not have compositional tools built in

I've yet to see a case in building compositional UIs that hasn't required me to build some custom means of combining things together. Partially because it's still a rare problem to have, and partially because it's highly contextual to the UI you're trying to build.

With that in mind, what are some of the decision points we need to look at? The first question I look at is:

Does the component I'm building own the data it's displaying

We could have a few basic compositional situations:

  • A widget that owns its data (like a login/account/cart widget)
  • A widget that composes data (like a product details widget)
  • One that does both (product recommendations widget)

It all comes back to "who owns the data"? At this point we can look at two major challenges:

  1. How do we compose client-side widgets?
  2. How do we compose the data the widgets use?

The first question is answered somewhat by many client-side SPA frameworks, especially those that build around component-oriented concepts. The second is a broader question, and naturally has more considerations.

Composing widgets

Component composition is really nothing new in user interfaces, as far back as there have been any remotely complex UI is the need to compose multiple pieces together to form a coherent experience.

What has changed is the delivery pipelines of our components. In a monolithic application that still breaks down the UI into components:

Widgets

All of the individual components along with any server-side pieces would (typically) live inside the same repository. In a microservices world, however, each individual widget has its own delivery pipeline and lifecycle. The widgets themselves depend on the technology, but it would typically include more than just rendering. We would need any behaviors/dependencies to be packaged and included as well.

In the past, packaging and bundling front-end assets was problematic at best. With Webpack, we can bundle everything that is needed for a single widget together, and alongside NPM, a means to distribute:

Webpack

The ability to create modules and packages allows us to package internal components into a consumable format for the front end, but we still have some problems we need to tackle:

  • Dealing with CDNs
  • Release pipeline for the host application
  • Shared assets

In general, I keep interaction between components to a minimum. If a component needs to talk with another component, in the browser, those components should likely belong in the same service. The exception to this rule would be a cross-cutting component, like one dealing with notifications/toaster popups.

The release pipeline is the more challenging above - when my widget team releases a new version to NPM, how I do then push that change out to production? What I'd likely not want to do is have my host application take direct dependencies on specific component package versions, and introduce a bottleneck.

Whatever approach we take, we want to err on the side of eliminating bottlenecks and coupling, in whatever its form. This may be in the form of an auto-updating-and-deploying shell application that just pushes out whenever a new package is detected.

Component Data Sources

Components can have a mix of data sources, where the data can be wholly, partially, or not owned by the service owning the component. Additionally, the data (or model) composition can happen in a couple of places, client and server. We could compose our data for the model on the client:

Client aggregation

Or the server:

Server aggregation

Or we could do a mix, where we make one call to our service API that gathers one level of data, and we then go to a series of other backend APIs to get more detailed information.

I tend to prefer server-side composition, mainly to reduce the number of calls to the server. By composing at the API level, we might trade off the ease of composition with JSON (unless using a loosely typed language on the server). Some API gateway products allow for some composition, just keep in mind that this composition still needs to be owned by a service, even if that service is entirely front-end related.

No matter how (or if) the data is composed, the front-end component and potential API Gateway need to own the model's SLA. If a coupled downstream endpoint is down, we need to explicitly design how our component should behave (perhaps rendering nothing).

In the next post, we'll dig more in to server-side composition.