Taming Microservices Chaos: Smart UI Integration with Server-Side Includes

This article explores an alternative approach to microservices architecture using Self-Contained Systems (SCS) and server-side includes (SSI) for UI-level integration. It addresses common challenges in microservices implementations and presents a practical solution for creating maintainable, scalable systems while avoiding the pitfalls of over-fragmentation. The piece covers essential aspects of UI composition patterns, session management, and common implementation challenges, providing a comprehensive guide for architects and developers looking to simplify their distributed systems architecture.

6 Minutes reading time

Behold the masterpiece that AI hallucinated while reading this post:

"How Little Services Learned to Play Nice Together: A Tale of Self-Contained Systems"

(after I fed it way too many marketing blogs and memes)

Created using DALL-E 3

AI-Generated: How Little Services Learned to Play Nice Together: A Tale of Self-Contained Systems

Microservices everywhere

Today, Microservices are a common software modeling technique. This style is based on the principles behind Domain-driven Design, where every bounded context is equivalent to a service. If we also take the Twelve-Factor App manifesto into consideration, we able to create scalable and cloud ready systems.

But this design can also lead to problems. If we take the Microservice design too far(Nanoservices), we are quickly getting way to much services we can handle and maintain. There is also the risk that we are somehow violating the original bounded context of our services, leading to lack of responsibility and functional encapsulation. Maybe the Microservices could degenerate to some kind of remote data access object. If we do not respect the principle of independently deployable units, we would be quickly creating a distributed, complex and very slow monolith.

Rethinking the architecture

Well, what can we do? The problem can be the vast amount of services in our architecture, which also increases the number of interfaces we have to support and maintain. The solution might be to create fewer services, and not more! We create a system for every bounded context(see my A Domain-driven Design Example to get in touch with Domain-driven Design), and include everything in this system, from database to application logic and even the user interface. This architecture style is called a Self-Contained System, and it is in fact the opposite of Nanoservices. SCS means Self-Contained System, not Self-Contained Service. So it can include one-to-many services, but as as argued above, it is a good idea to keep the number of services small, even inside a SCS. Every SCS provides a user interface, which can be anything, from RESTful APIs to a full blown HTML frontend(i will come to this later).

Now, what is a SCS-Architecture? Basically it looks as follows:

scsarchitecture

Every SCS represents a bounded context with its own ubiquitous language. The main goal is to keep the SCS independent of each other to reduce coupling, deployment complexity and make them exchangeable. Every SCS is developed and maintained by exactly one team to reduce development and project management friction.

Every SCS needs some kind of interface for its clients. It can be either a machine readable API, maybe RESTful and Hypermedia driven, or it can be a real user interface, perhaps based on HTML5 and JavaScript.

The interesting point here is that a SCS is in fact a standalone application. It contains everything, from user interface to data access logic, just everything that is needed to fulfill the requirements of its bounded context.

User interface integration patterns

Now, we have a small set of SCS that form our solution. How can a real user work with these components? How does the user login? How does the user navigate? How does the user perform some use cases? The answer is simple. Instead of composing the systems at API level, we compose them at user interface level. We need to build a Portal!

userinterfaceaggregation

But how to we build a portal? Years ago we had a thing called Java Portlet API. Java Portlets became not very popular, mostly due to the fact that they required a heavy weight Portal Server and most of the time they became a user interface nightmare, as the Portlet API was very limited in its abilities.

Today, I wouldn’t use a Portal Server anymore. The portal for our SCS would be a simple application, delivering some HTML templates and adding the composition logic. User interface composition can basically be done at two points:

  • Server side (HTTPd, NGINX)

  • Client side (Browser, JavaScript)

In the example above, we would use Server Side Includes or SSI to perform page composition. SSI is available for Apache HTTPd and NGINX. The Portal HTML templates comes with the layout framework and special SSI directives for the parts that need to he included from our SCS. The SSI processor now processes the SSI directives before the final HTML code is sent to the client.

After the final HTML is rendered in the browser, we might decide to update some parts of the site. This can be done by using client side composition with some plain old AJAX. Using this technique, we can save server bandwidth and greatly increase user experience. Even if the user has disabled JavaScript in the browser, this wouldn’t be an issue. In this case we can always switch back to server side page composition.

HTML5 Microdata can be easily used to implement common navigation use cases. As mentioned above, every SCS is self contained, and they share nothing. In fact they are not even aware of each other! So, now does navigation work? Well, to every link we add some HTML5 Microdata. This Microdata is interpreted either on server or client side and the final link is generated. See Building Portals with JavaFX, HTML5 and some Microdata to take a look at how this can be done.

Common issues

Session State Management

State management is a little bit more complex than in traditional web applications. The current user state (Logged in, etc.) must somehow be preserved. This can be done using Cookies and URL rewriting. The state authority is in our case the portal application. The session state identifier can be propagated using Cookies or other Information we put into the HTTP Header, as only the HTTP Request Headers are propagated properly using SSI. As mentioned above, links can be annotated with Microdata. If Cookies are disabled, the portal application can use the Microdata to URLEncode the required session identifier into every link. It could even use SSL Session Information to generate session identifier if Cookies are completely disabled.

Global stylesheets

It would be bad if every included SCS would look different. To make them look and feel the same, we have to establish a user interface style-guide, and maybe a pattern library can is used by all included SCS. This library could also provide a set of common stylesheets. Such pattern library can be included and distributed using the WebJars technology.

Search

A (full text) search across multiple SCS can be really hard to implement and maintain. The best thing you can do is to avoid such use cases. It this is not an option, try to isolate the whole search logic into its own SCS and use domain events to sync the data state between the origin SCS and the search SCS.

Other display technologies (Android, iOS)

This can be a real showstopper. Every mobile device has its own usability requirements, and maybe one common pattern library does not fitt well. Maybe you decide to implement the mobile app using native technologies, and not HTML5 hybrid technology. HTML can easily be integrated using the technique described above. For native mobile apps, we would still need to use the (REST) API of our SCS to make everything work. Books could be written about this topic, and maybe I will write someday something in this Blog. Till then, I will leave this open.

Conclusion

I really like the idea of SCS, and it can really help to get out of the "too-many-services-hell". Using user interface composition, we have the best of both worlds, functional decomposition for better focused product development and a single user interface to make our customers happy!

Git revision: 2e692ad