Microservices. The good, the bad and the ugly – 2° Part
But as always, there’s a downside. When you start going down the microservices road – for whatever you may find it beneficial – you need to realize that this is all brand new. Just think of it: if you’re not working for Netflix or Amazon, who do you know who actually already does this already? Who has actively deployed services into production, with a full load? Who can you ask?
You need to be aware that you really need to dig in and get your hands dirty. You will have to do a lot of research yourself. There are no standards yet. You will realize that any choices you now make in techniques, protocols, frameworks, and tools is probably temporarily. When you are learning on a daily basis, newer and better options become available or necessary, and your choices will alter. So if you’re looking for a ready-made IKEA construction kit for implementing microservices the right way, you might want to stay away from microservices for the next five to seven years. Just wait for the big vendors, they will jump in soon enough, as there’s money to make.
From a design perspective you will have to start to think differently. Designing small components is not as easy as it appears. What makes a good size component? Yes, it has a single business purpose, undoubtedly, but how do you define the boundaries of your component? When do you decide to split a working, operational component into two or more separate components? We’ve come across a number of these challenges over the past year. Although we have split up existing components, there are no hard rules on when to actually do this. We decided mainly based on gut feeling, usually when we didn’t understand a components structure anymore, or when we realized it was implementing multiple business functions. It gets even harder when you are chipping off components from large systems. There’s usually a lot of wiring to cut, and in the meantime you will need to guarantee that the system doesn’t break. Also, you will need a fair amount of domain knowledge to componentize your existing systems.
Basically we found that components are not as stable as we first thought. Occasionally we merge components, but it appeared far more common to break components into smaller ones to provide reuse and shorter time-to-market. As an example, we pulled out a Q&A component from our Product component that now supplies questionnaires for other purposes than just about products. And more recently we created a new component that only deals with validating and storing filled-in questionnaires.
There’s lots of technical questions you will need to answer too. What’s the architecture of a component? Is an application a component as well? Or, a less visible one, if you are thinking of using REST as your communication protocol – and you probably are – is: how does REST actually work? What does it mean that a service interface is RESTful? What return codes do you use when? How do we implement error handling in consumers if something isn’t handled as intended by one of our services? REST is not as easy as it appears. You will need to invest a lot of time and effort to find out your preferred way of dealing with your service interfaces. We figured out that to make sure that services are more or less called in a uniform way, we’d better create a small framework that does the requests, and also that deals with responses and errors. This way communication is handled similar with every request, and if we need to change the underlying protocol or framework (JAX-RS in our case), we only need to change it in one location.
That automatically brings me to the next issue. Yes, microservices deliver on the promise that you can find the best technology for every components. We recognize that in our projects. Some of our components are using Hibernate to persist, some are using a MongoDB connector, some rely on additional frameworks, such as Dozer for mapping stuff, or use some PDF-generating framework. But with additional frameworks comes the need for additional knowledge. At this company, we will easily grow to over a hundred small components and maybe even more. If even only a quarter of these use some specific framework, we will end up with twenty-five to thirty different frameworks( did I mention we do Java?). We will need to know about all of these. And even worse, all of these frameworks (unless they’re dead) version too.
Then there’s the need to standardize some of the code you are writing. Freedom of technology is all good, but if every component is literally implemented differently, you will end up with an almost unmaintainable code base, especially since it is likely that no-one oversees all the code that is being written. I strongly suggest to make sure there’s coherence over your components on aspects of your code base that you could and probably should unify. Think of your UI components (grids, buttons, pop-ups, error boxes), validation (of domain models), talking to databases or how responses from services are formulated. Also, although I strongly oppose having a shared domain model (please don’t go that route), there’s elements in your domain models you might need to share. We share a number of enumerations and value objects for instance, such as CustomerId or IBAN.
If you’re a bit like us this generic code ends up in a set of libraries – a framework if you will – that is reused by your components. We have learned that with every new release of this home-grown framework we end up refactoring some of the code of our components. I rewrote the interface of our validation framework last week, which was necessary to get rid of some state it kept – components need to be stateless for clear reasons of scalability – and I’m a bit reluctant to merge it back into the trunk when I get back to work after the weekend. Most of our components use it, and their code might not compile. I guess what I’m saying here is that it’s good to have a home-grown framework. With some discipline it will help keep your code somewhat cleaner and a tad more uniform, but you will have to reason about committing to it and releasing new versions of it.
So what about the really nasty parts of microservices? Let’s start with deployment pipelines. One of the promises of microservices is that they can and should be individually deployed and released. If you are currently used to having a single build and deployment pipeline for the one system you are building or extending, you might be in for a treat. With microservices you are creating individual pipelines for individual components.
Releasing the first version of a component is not that hard. We started with a simple Jenkins pipeline, but are currently investigating TeamCity. We have four different environments. One for development, one for testing, one for acceptance and of course the production environment. Now we are slowly getting in the process of releasing our components, most of them with their own database, we start to realize that we can not do this without the support and collaboration of the operations team. Our expectations are that we will slowly evolve into a continuous delivery mode, with operations incorporated in the team. Right now we already have enough trouble getting operations on-board. They are now used to quarterly releases of the whole landscape, and certainly have no wish for individually deploying components.
Another that worries me is versioning. If it is already hard enough to version a few collaborating applications, what about a hundred applications and components each relying on a bunch of other ones for delivering the required services? Of course, your services are all independent, as the microservices paradigm promises. But it’s only together that your services become the system. How do you avoid to turn your pretty microservices architecture into versioning hell – which seems a direct descendant of DLL hell?
To be honest, at this point in time, I couldn’t advice you well on this topic, but a fair warning is probably wise. We started out with a simple three digit numbering scheme (such as 1.2.5). The rightmost number changes when minor bugs have been solved. The number in the middle raises when minor additional functionality has been added to a component, and the leftmost number changes when we deploy a new version of a component with breaking changes to its interface. Not that we strongly promote regularly changing interfaces, but it does happen.
Next to that, we test our services during the build, both using coded tests and tests we assemble in SoapUI. And we document the requirements in smart use cases and domain models of our applications and components using UML. I’m quite sure in the future we need to take more precautions to keep our landscape sane, such as adding Swagger to document our coded services, but it’s still to early in the game to tell.
The hockey stick model
Early in the game? Yes, we’ve only been on the road to microservices for about a year. And I still haven’t figured out whether we are on a stairway to heaven or on a highway to hell. I do suppose, as with it’s historical predecessors, we will end up somewhere in the middle, although I really do believe that we do have the technology to get this paradigm working – and with we I don’t just mean Netflix, Amazon or some hipster mobile company, but the regular mid-size companies you and I work for.
But will it be worthwhile? Shorten time-to-market? Deliver all the goodies the paradigm promises us? To be honest, I don’t know yet – despite or maybe even because of all the hype surrounding microservices. What I did notice is that, given the complexity of everything surrounding microservices, it takes quite a while before you get your first services up and running, and we are only just passing this point. Some weeks ago, I was having a beer with Sam Newman, author of Building Microservices. Sam confirmed my observations from his own examples and referred to this pattern as the hockey stick model.
There’s a lot to take care of before you are ready to release you first service. Think of infrastructure, sorting out how to do REST properly, setting up you deployment pipelines, and foremost change the way you think about developing software. But as soon as the first service is up and running, more and more follow faster and faster.
So, if there’s one thing I’ve learned over the past year, it is to be patient – a word which definitively did not appear in my dictionary yet. Don’t try to enforce all kinds of (company) standards if they just don’t exist yet. Figure it out on the fly. Allow yourself to learn. Take it step by step. Simply try to do stuff a little bit better than the day before. And as always, have fun!