Perhaps you recognize yourself in this story. You and a couple of colleagues have been working for a couple of months on a new and exciting project. The start has been great and the first released version is a great success. Now that the concept is proven to work, many more features are requested and the codebase is growing day-by-day. However, progress is slowing down because it takes more and more time to go through the code. Strange bugs keep popping up and these are harder to fix because it seems to take more time to understand the code.
This is a typical problem in software development and can be somewhat mitigated by good architecture. Perhaps you are lucky and the architecture has been chosen at the start (and also seemed to be a good fit). However, for most (agile) projects, the architecture kind of ‘emerges’ during the development process. Which is typically fine, since one cannot always predict what the best choices are for a specific project.
In this article, I propose a certain style of development that you could call ‘component based development’. It isn’t exactly a new idea, but in UI5 development, it doesn’t seem to be that common (except perhaps the Fiori Launchpad, which consist of many components). Though recently the idea seemed to get some traction with added support in recent versions. There are also some articles regarding using components, mainly focused on the routing part.
Splitting the app
The basic idea of component based development is to split your app into various parts that have a clear functionality. The dependencies between these parts should be a as small as possible. You could also call these parts ‘subapplications’, but let’s stick with the term ‘component’.
Here are some advantages of using components:
- Can be run and tested separately and therefore are easier to develop.
- Are easier to reuse in other applications.
- Make it easier to work together with a large team or even multiple teams by dividing the responsibilities over components.
In a way, you can view component based development as having multiple applications bundled into one package. Of course, the best separation is actually having multiple applications, but in some cases this is not desirable. For example, when the user expects to have one application and does not want to switch to various apps all the time. And yes, in a way, the Fiori Launchpad can be viewed as a component based app, but it also isn’t always desirable to work with the launchpad (for example, when developing a mobile app that should work offline).
What is a component?
We need to make an agreement of what technically is a component in UI5. A component should implement a clearly defined API. In other words, a component should stick to a certain standard, or else it will be impossible to build something generic or share the component easily across projects. In UI5, using a UI Component seems to be a good fit. You see here that the term ‘component’ didn’t come out of the blue.
There should be some strict rules for a component:
- It covers a clear functionality of the application.
- It can be run by itself.
- It doesn’t know anything about the other components (this one is important!).
- The only dependencies are a limited set of ‘core’ modules (more on that later) and a limited set of shared resources such as UI5 libraries, custom controls and perhaps some utility libraries (like moment.js or mobx-state-tree).
Other parts of the application
Core modules deliver generic functions to the components and should offer an abstraction on external API’s (such as when calling an endpoint to load data). This adds the benefit of easier testing, since you just need to swap out the core module with a testing module to mock the behaviour.
Examples of core modules:
- Eventbus module (to send events between components)
- Logging module (to centralize logs and send it batchwise to the server)
- Backend API module (to have one single module responsible for getting data from the backend with centralized error handling)
The shell component is the component that the users start with. It should in principle only be responsible for the centralized routing and the placement of the various components in the root view. There should be no startup logic in the root-component, because the rule is that components should be able to run by itself and should not be dependent on the root-component. For that kind of logic we have the apploader.
Startup logic can be placed in a separate ‘apploader’ module. It does not contain any UI, that is what components are for. The apploader is responsible for registering the component namespaces and starting up the component. Any component can be started by the apploader, not just the shell component. So, when testing a separate component, the component should be started through the apploader.
Perhaps your project doesn’t seem to need an apploader, but it is useful to implement it in any case, because you will have a logical place for startup logic when it is needed.
End-to-end testing is often a pain, because if you want to test a certain subpart of the application, you first need to click to several screens to get to the important part. By just testing the various components, you are just testing what you want to test.
Once you have a component based architecture setup, testing a component should be a lot easier. You can mock all the core module dependencies and therefore just test the component. You can use uiveri5, opa5 or any other testing framework. Perhaps you do need a separate .html file to start the tests, but since the startup logic is in the apploader it should not be much more than a wrapper around it.
Migration to a component based architecture
You don’t need to start out using a component based architecture. It is possible, though perhaps difficult, to migrate from a monolith to components. In this case, you need to form an opinion on what exactly the various components are in your app. A good starting point is looking at the home screen and dividing the screen up in several rectangles. Take the ‘shopping cart’ example app, for example. You can easily see what the various components could be. Note that even a small button in the title bar can be seen as a component – responsible for opening up a dialog (which of course is defined within that component).
Start the journey
Migrating to a component based architecture is a journey you take with your team. It opens up the conversation regarding the various part that exist in the application. Instead of being implicit, these parts will be explicitly split out from the monolith. Split out one component at a time and, when done well, it will increase the modularity and therefore the maintainability of the project with every step.
There is not one clear recipe for implementing a component based architecture and it will always depend on the type of project and the preferences of the team, but I hope this article gives some inspiration for starting your own journey toward a more modular and more maintainable codebase. I work in a team where we have applied the above principles to an existing monolithic app and we are happy with it, because it gives us an agreed structure to work with, and of course the advantages listed in this article.
Thanks for reading and I love to hear your thoughts!
More reading (and watching)
General development principles:
(this talk by Nicholas Zakas was the main inspiration for this architecture)
Component based architecture in UI5
(some similar ideas, but components are called modules and reusable UI5 controls are called components)