In my article about Microservices Architecture, it highlights a big issue with how many think about and design microservices. In most microservice designs, the UI layer isn’t considered as a part of the service.
With this design, you end up coupling the UI layer with all the services.
By coupling the services to a single monolithic UI, you’ve essentially eliminated the largest benefit for using a microservice design:
The ability for teams to be autonomous and deploy their work independently of other teams.
In other words, a microservice architecture enables loose coupling between teams.
However, teams will conflict at the UI layer when UI concerns aren’t addressed.
Instead, we want to use what the SOA world has been doing for years: give each team the ability to own their UI.
Micro UIs is an approach where each team or service will create the UI parts that they need for their features and functionality.
Eventually, teams will have to co-ordinate their efforts when integrating their work.
Especially on user-facing web pages, we will have the need to display functionality from multiple teams at the same time. This is un-avoidable.
However, the amount of coupling is minimal.
The approach is to create a collection of individual components per service. These components are then composed together in the UI layer.
The Trouble With Legacy
Many developers have to maintain existing legacy systems. On top of that, they have to continue to add new functionality too!
These systems may have grown out-of-control due to a lack of design & architecture, planning, technical strategy or skillset of the team throughout the years.
In any event, adding new functionality into the system can be very hard.
There are little to no automated tests: how can you be sure you didn’t break any existing code?
Should you change the code that already exists?
Where do you even put the new code?
Chances are, those are difficult questions to answer since most of these legacy systems are very hard to reason about.
Using Micro UIs To Extend Legacy Applications
I’ve been using a technique that comes right out of the SOA world to enhance legacy systems super easy.
It does depend on the kind of change you need to add.
If you need to add, let’s say, a new section of a screen or a new web page altogether then this might work for you.
Here’s one example of where I’ve done this successfully.
I was working on a system that had a combination of:
- User-facing VB.NET web application
- Internal VB.NET web application
- Public-facing product marketing web application
- .NET Framework web app used to render initial web pages
- 100% custom-built JS framework with 100% custom-built components (using JQuery)
- .NET Web API that would interact with the custom-built JS framework to return JSON, HTML and everything in-between
- Various shared libraries between them
- One massive shared database behind everything
There are other systems that are missing. But this should give you an idea.
Oh ya, there were no automated tests. Anywhere.
New Features Requested!
As you can imagine, some existing features had logic spread across all of these systems and much of it in the database too.
The custom-built JS framework was also incredibly difficult to work with. What should have taken days to build was taking weeks and even up-to months of work.
I decided that a new approach to building new features was worth trying.
Forming A Strategy
In my mind, there were many issues to address. Relevant to this article, here are the solutions to the most pressing problems:
- Stop using the custom-built JS framework because it’s dragging everything down with it
- Enable automated testing for all new features
- Encapsulated business/domain logic in a common area that can be shared by all the different parts of the system
Slowly, over time, I started applying domain-driven design approaches and techniques to improve things. This involved a lot of learning and having to train other developers.
Here’s a rough sketch of how new features are designed:
Of course, that’s step one in a massive process of improving things.
This is all excluding a new mobile application and API that I’ve been leading 😅.
How To Integrate?
Some existing features needed to be enhanced. Using this design, we can, as much as possible, direct all new code into the domain project. Existing features will just call into the domain project and use it as needed.
By using refactoring techniques, we have been able to extract existing logic into the domain layer and improve existing functionality and add tests to them!
But what about times when you have entirely new features or products to build?
This is where micro UIs come in.
Instead of integrating directly into the existing legacy codebase, we can build as much as possible into:
- The domain project (which includes using DDD approaches and testable code)
- Dedicated UI components that interact directly with the domain logic
Here’s a diagram of what this logically looks like:
In practice, it technically looks like this:
In this case, we tried to minimize adding more infrastructure and complexity.
The data flow of each feature is:
- Vue.js components are on a shared CDN.
- These components send HTTP messages to the existing .NET MVC application.
- The MVC application is split up into feature folders. Each feature is essentially a “mini-app” like structure.
- Each HTTP handler simply passes the request through to a domain level command or query handler.
About Bounded Contexts
In this case, the core of each of the new features was placed into the same .NET library. This is working with a small team that manages all the systems.
However, it’s worth noting that this is essentially an SOA-like approach, logically.
Some of these new features can potentially be bounded contexts. If so, then they are perfect candidates for becoming a microservice (if ever warranted) or a module in a modular monolith.
The diagram above looks very similar to the one at the beginning of this article about microservices that own their own UI. It’s logically the same thing.
There’s No Recipe
Every team is different. Every situation is different.
Right now, I’m helping this team separate some of these concerns into separate modules acting as bounded contexts.
But, with these large legacy systems, it’s a long process of iterative improvements step-by-step!
You have to work with what you have and face the reality of:
- Infrastructural constraints
- Technological constraints
- Business constraints
- Developer skill constraints
This takes some thinking and design skills.
There’s no doubt though that everyone, especially the business folk, sees the benefit.
- Being able to build new features that took weeks/months in days is a huge win
- Able to apply automated testing to domain logic produces more predictable behaviours and allows safe refactoring
- Able to isolate changes to a cohesive area means less chance of errors breaking unrelated parts of the software
- Ability to integrate new technologies like Vue.js into ancient technologies enables a richer user experience and selling point when hiring new developers
Are you working on a legacy application that is really hard to deal with?
Is the business wanting to build new features but, so far, the existing code and design are causing tons of drag?
The techniques in this article might help.
Sometimes, it can be immensely helpful and time-saving to have someone with expertise to help you build a strategy around these kinds of changes and approaches.
If you could use help, then you can get in touch with me.
Till next time!