This post is about architectural concepts for web applications – self-contained systems (SCS) and resource-oriented client architecture (ROCA) – and their implementation with Spring Boot, Spring MVC, Spring Security, Thymeleaf, Bootstrap, jQuery, nginx and Redis. Self-contained systems aim at building software systems without ending up in a big monolith and provide an answer to the question ‘How micro should a micro service be?’. The resource-oriented client architecture (ROCA) provides some rules for implementing web applications that comply with how the web works – not working against the web.
Two years ago I built a ROCA prototype using Spring MVC, Spring Hateoas, Thymeleaf, Bootstrap and jQuery, and since Spring Boot appeared since then I decided to update the code. But then I did a lot more than just updating it to Spring Boot.
Let’s start with the concepts. By the way, if you read it and think ‘hey, there’s nothing brand new in here’, that’s okay. People built systems and web applications like this probably since the beginning of the web. Giving it names may be the new thing.
Self-Contained Systems (SCS)
When building a big system, a bigger application, a portal, something to support your business case that has a user interface, you first have to decide how many things you want to build. In the past it often happened to be one thing – the often criticized monolith. It’s a common understanding now that monoliths cause trouble and should be avoided, some of the problems seen are complicated, long builds, bad maintainability, lock-in to specific technologies, bad changeability and therefore longer time-to-market and many more. Currently microservices are the talk of the town, but they don’t come without a cost. If you really have microservices ‘doing one thing’ implemented in approximately 100 lines of code (as stated by some people), you’ll have a lot of them, and network traffic, resilience, more complex implementation compared to just calling methods, monitoring, coordination all have to be handled.
Self-contained systems as described and promoted by Stefan Tilkov are not that small. A bigger system is made up of several such self-contained systems, and it’s up to our common sense to cut those systems, but they do more than one thing. Stefan Tilkov lists the following characteristics for self-contained systems:
- Autonomous web application. Each self-contained system is a complete web application that handles its use cases.
- Owned by one team. The self-contained system is small enough to be owned and developed by one team.
- No sync remote calls. To minimize dependencies to other systems a self-contained system doesn’t do sync remote calls. Integration is done via links in the browser, see below. Async remote calls are allowed.
- Service API optional. If needed, a self-contained system may expose functionality via REST endpoints.
- Includes data and logic. A self-contained system stores and handles its data, no other system may access the data directly. Logic, data and UI are not separated in different applications. A self-contained system may duplicate data from other systems. Let’s say a self-contained system for ‘contracts’ needs to display the customer’s name on each page, but the customer belongs to the self-contained system for ‘customers’. One option would be to store the customer’s id and name in ‘contracts’ data repository. If the customer’s name changes, the ‘customers’ system sends out an async message with that info, and everybody listening (for example the ‘contracts’ system) updates the duplicated data in its repository. The other option would be to include an HTML snippet from the ‘customers’ system in each ‘contracts’ page.
- No shared UI. Each self-contained system has its own UI, there is no such thing like a service layer and a common UI layer above it.
- No or pull-based code sharing only. Each team has the full responsibility for its self-contained system, which includes the choice of programming languages, frameworks and libraries. Code sharing should be pull-based, so if the team decides that using some common code is helpful, it may use it, but it’s not forced to use something. Of course there needs to be some kind of macro architecture everybody has to follow to make integration possible, in our (pure) case it’s just the use of HTTP/HTML to integrate applications via links in the browser, the integration of some messaging system to receive and send the async updates and the support of a dedicated Single Sign On mechanism. Of course it may make sense to restrict the usage of certain technologies in a company to concentrate knowledge in some technologies.
One of the main advantages of this approach is the flexibility. When after some years the technology stack used in the first self-contained systems is outdated, it’s no problem to build new systems in another stack without the need to update the existing ones. At every customer I see the situation that people would like to change / update technologies, but they cannot, because that would mean that a lot of existing applications need to be touched, and since there’s no business value in that, new applications / new use cases are also built in outdated technologies.
To be more clear on this one: even if you use the most up-to-date technology today, it will be outdated in five years, so it’s absolutely necessary for an evolvable system that its architecture does not tie the overall system to certain frameworks or, even worse, certain products.
Resource-oriented client architecture (ROCA)
So now you know the rules, but that doesn’t mean you can instantly imagine how such an application would look like. At least I couldn’t. I learned that there are two important aspects:
RESTful communication is stateless, so we have no session state. We have meaningful bookmarkable URIs for each resource and sub-resource, and a resource ideally represents an object from our domain, or a list of objects from our domain. I say ideally, because that’s not a must. In a lot of use cases, a resource made for a web frontend cannot be mapped 1-on-1 to domain objects, but if it does, our life gets easier. To interact with those resources we use the four HTTP methods GET, POST, PUT and DELETE. So if our domain happens to be a movie database, usage could be:
- GET on /movies for displaying all movies
- POST on /movies for adding a movie
- GET on /movies/42 for displaying the movie with id 42
- PUT on /movies/42 for updating the movie with id 42
- DELETE on /movies/42 for deleting the movie with id 42
A GET returns HTML markup (possibly through a template engine), PUT and DELETE are tunneled through a POST, and POST, PUT and DELETE return a redirect URI to follow the POST/REDIRECT/GET pattern.
Some more sentences about the statelessness, because it has so many implications: Most of the developers are used to do stateful web development, especially when using JSF. It’s easy to store another, and another, and another thing in the session, and suddenly you get pages that work just under specific circumstances, and it’s hard to keep track of all the objects in the session and why they landed there in the first place. Maintainability and testability suffer more and more. For operations things get more complicated with a session, because we either need sticky sessions or session replication, and when rebooting or deploying an application, all users get thrown out the hard way. Implementing a web application in a stateless manner means that all information must be reproducible from the request, that may be the URL, get parameters, hidden inputs, headers. It doesn’t mean you’re not allowed to use caches, you may even use the session as cache, and with ROCA you can use the standard browser cache as well, but it also means that updates to resources get persisted maybe a little bit more often than with stateful web development. The benefits you gain are scalability, zero-downtime-deployments, perfect bookmarkability which includes taking a site directly from one device to the other, no hassle caused by an expired session and more.
An implementation: the movie database
The code for the movie database together with installation instructions can be found on Github. The following diagram desribes the architecture of the overall system.
We have two self-contained systems, one responsible for movies (movie-database-movies), one for actors (movie-database-actors). In addition we have two more applications that serve cross cutting concerns, one for monitoring (movie-database-monitoring) and one for the navigation header (movie-database-navigation). The project is completed by two libraries, movie-database-security for the single sign on (SSO) functionality and the very small movie-database-commons for common functionality. Initial page after signing in looks like this:
It should be easy to install the complete system with the given installation instructions, however, I would like to point you to several details in the implementation.
Integrating self-contained systems
I differentiate here between a non-direct integration via a navigation header / bar and direct integration between two self-contained systems.
If you want to integrate several self-contained systems seamlessly, you’ll have some common components displayed on every page. I chose to restrict it to a navigation header containing links to the movies- and the actors-system, a search field and a log out button. It makes a lot of sense to let the navigation content be served by an own application, because you want to be able to add navigation points to the list without rebuilding every application. That’s what the application movie-database-navigation is for. It delivers pure HTML and is dynamic, you may, for example, specify to which URL the content of the search field shall be posted. When integrating such HTML snippets you roughly have three options:
- Load the HTML snippet on the server’s side in the application and include it in the HTML page before sending the reponse to the browser.
- A proxy builds the page using Edge Side Includes (ESI).
I chose the second option. One reason was a flickering with option one that I couldn’t get rid of. Now getting the navigation snippet is actually a sync remote call, which isn’t allowed according to the SCS characteristics, but I take it here as an exception of the rule. To make the application more resilient I included a static build-time fallback navigation which will be used when the dynamic navigation is not reachable. This has two advantages: whenever the navigation application is down, people can continue to work with a less dynamic, maybe outdated navigation instead of getting 500ers, and while developing we don’t need to start the navigation application to work on just ‘our’ self-contained system.
Direct integration of two self-contained systems
Actors play roles in movies, so I implemented the possibility to display actors that played in a movie from a movie’s resource. It’s just a direct link into the actor’s resource with a search criteria that restricts the result to that movie. I’m using Bootstrap and jQuery to render the content in a modal window. The Thymeleaf template is here, the construction of the link is here. This is a screenshot of a movie resource:
Okay, linking is easy, but what if you’re working in system A and need to create something in system B for your use case, jumping back immediately afterwards? I added the possibility the add an actor to a movie (in my implementation you have to create a new one, but it’s easy to think of an implementation where you may choose existing ones in addition). After creating the actor you jump back to the movie’s page, that’s done by adding a return url when jumping into the actors system. You can see the return url in this screenshot:
When pressing the ‘cancel’ or the ‘save’ button on the ‘add actor’ page, there’s no redirect to the actor but to the return url (see ActorController, methods
Single Sign On / Security
As soon as you have more than one application facing the user you need single sign on (SSO) if you want the system to appear as one thing. I chose a simple approach with an unique SSO-Id persisted in a central repository (Redis). This unique id is saved in a cookie at the client. Of course this only works if the client browser sees the same host for all self-contained systems, but this is usually a requirement anyway. I’m using nginx as a reverse proxy to let the whole system be served under the same host. I implemented the SSO security mechanism in a library using Spring Security (thanks to Rajo Gajic for help), so that everybody can pull it in who thinks it’s helpful. Of course you could implement the mechanism yourself if you want, but for Spring applications it’s convenient to just use it.
Another approach would be to implement an authentication application and redirect everything regarding authentication to it. The dependency is not as strict as to a library, and non-Spring applications could use it, too. The disadvantage is that you add a single point of failure.
Building links and resources
When implementing an application in ROCA style, you have to think in resources. Resources have an unique identifier (an URL), they contain data that’s displayed on the page, and they contain links to other resources. Links consist of the link itself and a relation with the semantic meaning of the link. I’m using Thymeleaf templates to convert objects to HTML, so to bring structure to my implementation I use a Resource class that may contain data and any number of links, and an object of this class is delivered to the templating mechanism. In the template, links are referenced like this, identified by the relation:
For building links the LinkBuilder class is used. This is a small set of classes heavily influenced by Spring HATEOAS (in fact I used Spring HATEOAS before in this project, but I realized that I only could use a very small subset of its functionality, so I chose to duplicate it). Using these classes will bring more structure to building your links and resources. This is the main part of movie-database-commons.
Monitoring with Spring Boot Admin
The more applications you run, the more crucial monitoring becomes. When using Spring Boot Actuator Spring Boot applications expose a lot of interesting monitoring data, from simple health checks to endpoint metrics, from used memory to thread dumps, from environment variables and properties to deployed Spring beans. With the Jolokia endpoint you even can do all available JMX operations. Spring Boot Admin is an open source project by codecentric that provides a user interface to all the data with a lot of extra functionality, for example downloading the log file etc. Here’s a screenshot of the overview page for monitoring the movie-database:
Head over to Spring Boot Admin’s page to see more screenshots! And if you look into the code of movie-database-monitoring, you’ll see that there’s very little to do to start monitoring your own applications.
To have an unique look and feel you have to use the same CSS. That’s easy if you just use Bootstrap, like I did, you just add a webjar dependency and include it into HTML’s head. But even if you have your own CSS, which will be much more likely when doing a real project, you should handle it the same. Build webjars with the CSS and include the dependency in a nice, versioned manner to your project. When developing the CSS further it’s crucial to be as downward compatible as possible.
A few more words on development, build and operations
Let’s say we have a bigger system with ten self-contained systems. How do we actually develop and operate it?
Since we minimized the dependencies between the systems, we’re probably able to do a lot of work without relying on other systems, that’s good. But of course, there’ll be time when integration needs to be tested, and I would argue it’s too late to do it just on integration stages. And we have our own dependencies in our self-contained system, at least the SSO token repository, the messaging system, probably some kind of database etc. It should be easy and convenient to build up a development environment with that infrastructure and the other self-contained systems, if you need them, and it should be built up the same way in test and production. The solution to this is the ‘Docker based runtime environment for developers’ my colleagues at centerdevice introduced in this blogpost (unfortunately just in German). Docker images for each self-contained system, the cross-cutting applications and the infrastructure make it possible to set up environments in very short time, whether it’s the development environment or the production environment.
Long post, probably my longest until now, so I hope you didn’t just scroll down to the conclusion and skipped the other parts ;-). I hope the terms ‘self-contained system’ and ‘resource-oriented client architecture’ are clear now, and I hope my sample system has shed some light onto possible implementations.
Spring Boot, of course, is just an implementation detail of the self-contained systems, but a very helpful one. While implementing with Spring Boot is nice, the usage here shows once again that operations is where Spring Boot shines – the monitoring capabilities are very convincing, and having a fat jar that bundles application and server is convenient for operations as well. But of course, there’s no constraint to implement every self-contained system in Spring Boot.
If you didn’t do it by now I encourage you to look at the code and let it run, I’m very much interested in feedback!
There’s a follow-up blog post handling a few more interesting topics:
- Adding a non-ROCA self-contained system written in AngularJS.
- Explaining bounded contexts with an example.
- Doing data duplication between self-contained systems.