This blog is about Java (advanced Java topics like Reflection, Byte Code transformation, Code Generation), Maven, Web technologies, Raspberry Pi and IT in general.

Posts mit dem Label architecture werden angezeigt. Alle Posts anzeigen
Posts mit dem Label architecture werden angezeigt. Alle Posts anzeigen

Donnerstag, 18. Juni 2015

The Importance of structuring Microservices


It's very important to group microservices very well. Otherwise you will get a mess. Several microservices should form a larger part of the functionality.

Example: Image Functionality in a Microservice System
Lets approach the problem by example. Assume we want to build an image processing functionality for our system. Each microservice should do exactly one thing. So we will end up in the following microservices:

  • image scaling
  • image watermarking  
  • image storing
  • image retrieving 

All these image services are very tightly coupled - the storing microservice and the retrieving microservice uses the same database and the same files. Of course it should be possible to use each of them in another context or by itself. However in your own system you will consider it as an image-sub-system. Therefore this sub-system should have its own abstraction. Its own API - this API is an own microservice.

All non image related microservices don't know anything about the individual image microservices. They know only about the image API microservice. This API could group together the typical use cases. For example to resize an image and watermark it.
You don't want to have this code in any other place than in your image microservices. Otherwise your complete microservice system is very tightly interconnected. This is very bad. Imagine that all images will be scaled & watermarked and that you have performance issues because of high network traffic. One simple optimization is to merge the scaling microservice and the watermarking microservice together. Then you have to send the image over the network one time less. If you utilise an image API microservice then the code must be changed only once and only one microservice needs to be redeployed. On the other hand if all other microservices used the internal image microservices then you have to change the code several times and several microservices need to be redeployed.

Abstraction like in a monolith
This abstraction and grouping of functionality is nothing else like modules in monoliths. In a monolith all the image functionality would be in a package "image". This package would have following sub packages: "scaling", "watermarking", "storing" and "retrieving". Likewise in the microservice architecture you don't want other parts of the monolith use these sub packages directly. Otherwise you have interconnected modules and it will be hard to change internal details of the image implementation. Everything should be behind a high level interface and everything else should be hidden and not be used anywhere else. Then it's possible to rewrite the complete image package without the need to adapt code in other packages. The only thing you need to take care of is to support the interface.

I think it's very appealing that this concept of abstraction is assignable to a monolith and to a microservice architectures. If the concepts were completely different then something is most likely wrong.

The module abstraction is often violated in monoliths and the complete codebase is very interconnected. It's bad, but it's manageable. Because you have the complete code in your IDE with all the useful refactoring tools. But if you have separated code bases it's very hard to refractor anything. So do it right in the first place and don't move it to a later date.

Microservice grouped by Domains
One way to group the system is to group it by domains. A domain is a (mostly) independent part of the system. It's very important that you get your domains right. Because if you don't then the problem is that for nearly every new feature several domains must be changed. But this should be only an exceptional case.
For example the user part of a system consists of: login, registration, password reset mail, user profile and so on. All these basic user functions are more or less equal for each system. Therefore the user-sub-system should be also usable for any other system. This only works if this domain is completely independent. The domain is not allowed to have any dependency to any other domain. Otherwise you have to remove those dependencies to make the user-sub-system usable for another system.
Another domain could be the product-domain. In this domain all the data of a product is saved: price, stock count, description and so on. The user-domain and the product-domain must be independent of each other.

How to deal with features that require two domains
Lets approach the problem again with an example: a shopping cart. A shopping cart is individual to each user. Therefore it must be in the user=domain. The problem is now that you also need the data from the product-domain. Otherwise the shopping cart is quite useless if you can't see the details of the stored products. But since the user-domain isn't allowed to have a dependency to the product-domain how to solve this problem?

Actually it's quite simple: with an aggregation microservice. In the user-domain all stored data is related to the user like added-date, user-id and so on. There is only one exception: the product-id is also stored.
The user-domain provides an API to get all items from a specific user. These API will be consumed by an aggregation microservice. The product-ids will be collected from the aggregation microservice and then the details of the products will be fetched from the product-domain. The data will be merged and then the shopping cart can be displayed or the data can be passed to the next microservice. By that you can implement the shopping cart feature so that both domains are still independent.

The aggregation microservice/layer is nothing else than a Lego™ block which connects two other Lego™ blocks together. If you use third party APIs then you do exactly the same. You create some kind of Lego™ block which aggregates the data from several third party APIs. It helps to think if you treat your own microservice APIs like they are third party APIs. Because then you will get the independence of the microservices right. You won't violate the independency.

Performance Issue with the Aggregation layer
Let's assume that it's common that a shopping cart has thousands of items. Additionally you need to support to sort the items in a shopping cart by price. Then the aggregation microservice has to load thousands of items from the user-domain, load thousands of product details from the product-domain, merge the data, sort them by price and then throw away thousands of items except for 100 which will be showed to the user (pagination). That's very inefficient.

The only way to solve the problem is with data duplication. It's necessary to store the price of the item in the shopping cart database as well. Then you can do the pagination in the database and only get out the right 100 items.
That means that you have to supply the additional data if an item is stored into the shopping cart. Additionally the data in the shopping cart database must be updated if the price is changed in the product database. To do that you need events. If a product is updated an event must be thrown. An event listener will take this event and update the price in the shopping cart database.

Eventual Consistency
This means that you have only an eventual consistency. Because the price could be changed but the corresponding event could be still in the queue. Then the item will sorted in a wrong way. The price itself is correct, because the price which will be displayed will be loaded from the product-domain with the other product details.
There is no way around this problem. Only if you use just one database, do distributed transactions, or do the inefficient loading of thousand records. But that conflicts with scalability and/or the microservice architecture mindset. Therefore it must be alright for all data you duplicate that they are shortly out of sync. In some cases you can't do it. For example payment data. But then don't duplicate the data.

Conclusion
You need guidelines which microservices are allowed to talk to which microservices. Structure the communication workflow and use grouping and abstraction. Otherwise you will not know which microservices rely on a specific microservice - directly or indirectly. That's a very bad spot to be in. Another thing you probably need is a good monitoring. So that you can see what microservice calls were made for a user request and how long they take. Because if you don't have this data then you have a very hard time to find performance issues. Consider to use Zipkin.
Design the system very careful and have good high level documentation for each microservice. 
If you get all of the things right then you should be fine and have much fun with your microservice system :-)

Montag, 1. Juni 2015

Microservices: a few thoughts



Microservices are a hot topic right now. At work we are discussing if we should move to microservices, too. In my opinion it would be the right choice. First of all our current software is very old and has many flaws. Therefore we need to rewrite the software anyway. Second the concept of microservices fits us very well and it's in general a great concept. 

Specialists versus Generalists
A problem we currently have is that everyone must know the complete project, which is quite big. We don't have modules on which the developers could specialize. This causes a lack of code ownership and deep knowledge and understanding of the code. Therefore the code gets worse and worse. With microservices you have strong "modules". So each team/person can focus on a group of microservices and know only about the interfaces of the other microservices.
The disadvantage is that you have a more difficult time with the project management. Because you have to align your features accordingly to the teams and their knowledge.
If you can handle that then the overall output and quality should be considerably better with specialized teams. More features mean mostly more money and that's a good thing. This is not really a microservice thing. It's just an argument for a good architecture and for specialized teams.

Impact of changes
Another nice benefit is, that you can delimit the impact of a change
better. Since a change in a monolith can always have strange side effects. This shouldn't happen with microservices.
It's very important that the microservices are fault tolerant. So if one microservice misbehaves then it must not poison the other microservices. The outage of one microservice must be contained. Meaning that a part of the functionality is missing or broken. But everything else must still work fine. With this concept you can take better care of your core microservices. The not so important microservices could have less quality. Thereby it's possible to optimize the development output - invest only as much as needed.

Homogeneous Stack versus Inhomogeneous Stack
Many people say that another benefit is that you could use different frameworks or even other programming languages. But in my opinion this doesn't make sense. At least if the team is not very big. Or you have very special requirements. Because a homogeneous stack makes everything much easier. Even the build tools, build configuration and the deploy pipelines should be the same. Otherwise you violate the DRY (don't repeat yourself), because you have some kind of "code/configuration/script duplication" and have to solve each problem for each programming/framework-stack.

Upgrades
You still have the benefit of upgrading the microservices independently. If a microservice runs stable and doesn't change then there is no need to upgrade it. With a monolith you don't have this option. If a library is upgraded then everything is affected. Even the code you didn't touch for years and worked perfectly until the upgrade. Because of that a library upgrade in a monolith is very dangerous and is done rarely. Microservices can be upgraded one after the other. So there is no big bang upgrade but many little upgrades. Furthermore you shouldn't have a jar hell like in a monolith. This makes upgrading easier, too.

Flexibility
In general you have much more flexibility with microservices. Because one huge monolith is very hard to change. What would it mean to change a core library in a monolith? For example to change a self written URL dispatcher to Spring MVC. Of course there is way too much logic in this layer. For the current project this change would mean a rewrite of a big part of the system. Which would be very error-prone. Like in the "Upgrades" section the problem is, that you have to change everything. It's not reasonable to change only a part of the monolith to Spring MVC. In some cases a partly change is just impossible. The general problem with partly changes are that you have to support still the old self written URL dispatcher and Spring MVC. The whole system gets more complex. And in a few years the next library will come that you will want to use.
But this isn't only true for core libraries. It's also true for smaller libraries. If there is a new major library upgrade, which breaks the old API, then you have to migrate a huge code base to the new version. It's not possible to write only one new feature with the new library version. For this single feature this doesn't pay out. So you don't do it and use the old library which forces you to write more code. Now you have a technical dept. Because all other features in the future are limited to the old library version too.

Reusability
How does the reusability go? First you have a class which uses the same method for the same task. The next step is to have a package for a more complicate task. If it gets even more complicated you write standalone and independent libraries and add them as a dependency. Microservices are the next step of the reusability.
If you have several systems which need to send emails, then you probably have in each system an email library dependency. Furthermore code to use this library and this code is most likely an code duplication. Because it doesn't pay off to create a library which will be used in each system
only for a few lines of code. In a microservice world you create a microservice which handles the emails. Then in all other places you just have to do one remote call and you are done. If you create a new system, for example a new batch job, then you have to use the email-microservice and you are done. No need to integrate an email library into the batch. If a new requirement comes in that all mails should be resent after an hour if the initial delivery failed then you only have to take care of this requirement once. Not several times - for each system which sends emails.

Scalability
This is a controversial topic, because not every software system needs to be extremely scalable. Many software systems work fine with just one big database (which is hopefully redundant). Therefore it's not always an argument for a microservice architecture. If you need scalability then microservices are great. There is one condition: the microservices need to be stateless. Then it's very easy to start up more instances of the microservices and scalethe system this way.

There is another kind of scalability besides of the technical scalability: the developer scalability. It most likely won't work to have hundreds of developers working on the same monolithic code base. But again not everyone has to face this challenge.

Conclusion
Microservices don't solve everything. In contrast this concept introduces new challenges and problems. Nevertheless I think that these challenges are manageable. The benefits you get from the use of the microservice archtecture are huge. If the system is complicated enough, you know that your team can handle the microservice-challenges and creates a good microservice architecture then you should definitely consider a microservice architecture. If one of the criterions aren't met then stick to your monolith.


A word of warning: a good microservice architecture and microservice infrastructure is very hard to create and you need many things: an excellent concept, a very good understanding of your domain - so that you can split up your monolith in the right way, excellent monitoring, continuous delivery, automatically deployments and fast deployments, handle failures gracefully - so that failures don't create a ripple effect, be able to handle your data if it is distributed over several databases, and so on.