Danish version: http://qed.dk/jeppe-cramon/2014/02/24/microservices-det-er-ikke-kun-stoerrelsen-det-er-vigtigt-det-er-ogsaa-hvordan-du-bruger-dem-del-1/
Part 2 – Microservices: It’s not (only) the size that matters, it’s (also) how you use them
Part 3 – Microservices: It’s not (only) the size that matters, it’s (also) how you use them
Part 4 – Microservices: It’s not (only) the size that matters, it’s (also) how you use them
Part 5 – Microservices: It’s not (only) the size that matters, it’s (also) how you use them
Part 6 – Service vs Components vs Microservices
Text updated the 22nd of June 2021
We have finally come to the exciting development I hinted at in SOA – Hierarchy or organic growth?
In the blog post Microservices: synchronous communication, data ownership and coupling, we examined the 4 Tenets of Service Orientation and specifically focused on service boundaries and autonomy problems due (synchronous) 2 way communication between Services. With this knowledge in hand, we are well prepared for the topic of this blog post.
The subject, as the title indicates, is micro services, which in many ways is a response to monolithic architectures. As with SOA, Micro Services also lack a clear definition. The only thing people seem to agree on is that Micro services are small and they are individually deployable. Rule of thumb says that Micro services weigh in around 10-100 lines of code (for languages with minimal ceremony and excl. Frameworks and libraries – although the last point is disputed among purists).
Number of lines of code is in my opinion a horrible measuring stick for determining whether a (micro) service has the correct size or for that matter if it is a good service.
Good guidelines for designing micro services in terms of scope (size) and integration form (how to use them) seems to be lacking. Without these guidelines it becomes difficult to separate the wheat from the chaff and one could easily be tempted to claim that the layered SOA (anti) pattern (see diagram below) also meets the micro service size rule of thumb (and then we know that some people will be tempted to cross off micro services on their list and say that they have them too, without looking closely at what micro services is all about and therefore never will come near designing proper micro services).
So are the services within a classic layered SOA real micro services?
Is layered SOA also a micro service architecture?
Entity / Data Services are thin services that have roughly the same purpose as a Repository / Data Access Object in classic layered OO code. An Entity service is a thin shell on top of a (typically) relational database. Depending on the programming language and framework, a Repository exposed as a (REST / Web) Services could be implemented using 10 to approx. 300 lines of code.
Micro service size rule compliance – CHECK
Task services are thin services coordinating / orchestrating calls to multiple Entity services. Depending on the framework / library and extent of data conversion a Task service can be implemented using 10 to 1000 lines of code .
Micro service size rule compliance – CHECK to just about check
Process Services are thin to semi thin Services that coordinate calls between multiple Task/Entity services. They typically require a bit more work as they often need to handle data conversion, compensations in case of update failure and support long running transactions. Depending on the framework and library (e.g . BPEL and an ESB), a process service can be implemented using 100 to several thousand lines of code.
Micro service size rule compliance – approximately CHECK
As we discussed in Microservices: synchronous communication, data ownership and coupling (synchronous) 2 way communication, which is used in layered SOA, is deeply problematic in terms of service responsibilities delineation and service autonomy. The need for compensation with process/task services alone imposes a lot of complexity. Another problem is contractual and temporal stability: If just a single service is down, nothing or very little works. Latency, the time from a service is called until the answer is received, is typically high since we’re communicating with many services over a network protocol.
Based solely on the rule that Micro Services is categorized by containing few lines of code, we could boldly claim that Entity / Task / Process Services also are micro services. This clearly shows that the use of lines of code to determine whether a service is a micro service or for that matter a good service is lousy!
If we decompose our services even further and make them really small (micro) services and then let them communicate 2 ways our latency will suffer tremendously. If the focal point of micro services is exclusively on size and not use usage patterns it is not hard to imagine a star chart of service calls: Our application calls a service, which in turn calls a lot of little (reusable) services, which again (potentially) calls another service that calls other services, etc. Circular calls is a real problem with such a usage pattern.
Micro Services star (synchronous) 2 way communication diagram. Service call services calling services, etc.
In our attempt to decompose our services we have made them very small (e.g. responsible for handling ony a few data attributes). This easily creates the challenge that individual services will need to talk to other services to accomplish their own task. It is as if they’re jealous of each other’s data and functionality.
One of the goals of service orientation was to ensure reuse.
How do we ensure the highest possible reuse? Make all service so small that they can be reused in many different contexts as possible.
This logic is fine on the surface, the problem is just that every time we reuse, we also increase our coupling and increase latency. One of the other goals of service orientation was to ensure loose coupling such that we can easily change our services to keep up with business and technical demands.
In Microservices: synchronous communication, data ownership and coupling we discussed how (synchronous) 2 way communication leads to some pretty hard forms of coupling that are not desirable:
- Communication-related coupling – data and logic are not always in the same service
- Layered coupling – business-related-security and persistence are not in the same service
- Temporal coupling – our service can not operate if it is unable to communicate with the services it depends upon
Coupling has a tendency of creating cascading side effects: When a service changes its contract it becomes something ALL dependent services must deal with. When a service is unavailable, all services that depend upon the service are also potentially unavailable (depending on the form of communication). When a service fails during a data update, all other services involved in the same coordinated process / update also have to deal with the failed update (also known as process coupling):
Did the error occur before the service received the message and performed the update or was it after?
In the example above the client, which could be another service, is performing a 2 way synchronous call against a service. Since the communication is 2 way the client sends a Request message to the Service. The service receives the Request and performs some kind of processing, for example updates a database. After completing the processing, the service sends a Response message back to the client with the result of the processing. The communication occurs over a network, e.g. a HTTP call.
Note: A network is always slower and less reliable than the in-memory calls we are used to with our monolithic applications.
If the client runs into a timeout or another network I/O error it is typically for two reasons:
- Either the Request message did not reach the service, which in turn did not update the database
- Or the Request arrived at the service that updated the database; but Response message never made it back to the client.
Lack of Reliable Messaging means the client does not know if the service has performed its job or not. This leaves the client with a problem: What should it do?
- Should it try to ask service if the job was performed and then retry if it wasn’t performed?
- Should it blindly retry the call?
- Should it try to compensate?
- Or should it give up?
Last reaction tends to be the predominant solution.
If the client tries the call again, then the service operation must be implemented so that it can handle more than one call / request message for the same job and still only perform the job (e.g. database update) once. This is known as being idempotent.
If the call to the service was part of a series of update calls to multiple services then we have a larger consistency problem because we do not use, and should not use distributed transactions, to handle the coordination of the updates. As we saw in Microservices: synchronous communication, data ownership and coupling compensation logic is not necessarily trivial or simple to implement when using 2 way communication:
Transactional compensation with (synchronous) 2 way integration
Imagine an extreme micro-service architecture where each service is responsible for only one attribute (e.g. first name, last name, street name, street number, zip code, city, etc.). With such a design latency time will be a huge problem, stability terrible and our coordination and compensation problem very big. There must be missing something to guide us to a better service design!
In the next blog post we will look at how to integrate services in a distributed context and see how this affects our service granularity and the way we integrate our services. Until then, I look forward to your comments, questions and ideas 🙂
Hi Jeppe,
Very good articles, well written, raising up all SOA challenges and concerns (nothing new, but few people can realise that).
I am very interested to see how you will end up this journey once that you have an excellent arguments but any solutions until now (Hopefully waiting for).
i am responsible for a Integration Team in one of the biggest investment bank in Brazil, we deployed an ESB 2 year ago then after few months running it almost 300 integrations were delivered.
We have “believes” a real SOA oriented approach such as Canonical data models, focus on reuse, Soa Governance Team and so on.
I cannot deny all your observations over this topics are totally coherent, I am not taken such consequences yet but it is becoming harder while we are growing.
I am looking forward for your next post!
LikeLike
Thank’s a lot for your articles. Very good job, congratulations, and keep going.
That’s very clear, well argued, clever, and relevant.
LikeLike
Great post! You got several admirers in our organization! I have a few comments:
1) I think the LOC discussion for microservices is white noise. The main issues that microservices try to address are related to deployability and runtime qualities, mainly service autonomy.
In that case, the size that really matters is the size of the runtime element that corresponds to the executing service. And guess what? The service logic will call other classes for util kind of functionality, to access the database, to do validation, access control, transaction demarcation, logging, etc. All that code is present at runtime.
From a maintainability point of view, the LOC count does matter, but even more important is whether the code is “clean code”. Maintainability (aka modifiability) is less of a runtime quality attribute and more of an implementation attribute.
There’s an aspect to size of services that really matter at runtime: memory utilization. So much so that orchestration platforms have created mechanisms for dehydration, and others have created technical solutions and patterns to minimize the amount of memory a service requires when processing and the amount of time it’s active. This issue is also the focus of the “service statelessness” principle for service design by Thomas Erl et al.
2) Picking on one more thing about LOC. You say for process services the LOC can vary from 100 to several thousands depending on the framework (e.g., BPEL). But if you use BPEL, more likely you modeled the process service visually. What’s the LOC in that case? The LOC count of the BPEL XML script?!
3) You say “How do we ensure the highest possible recycling? Make all service so small that they can be reused in many different contexts as possible.” I don’t understand how a small LOC count makes a service more reusable. To me reusability is primarily the result of modeling services with an “enterprise focus” and carefully designing the contracts.
4) Towards the end, you say “There must be missing something to guide us to a better service design!” The following posts give excellent ideas for service-oriented design, focusing on improving autonomy. I just want to add that there’s a body of knowledge to help with service design. In addition to DDD, which is directly applicable to SOA systems in general, we have several published SOA design patterns and distributed systems patterns, and we have the 8 principles of service orientation by Erl et al, which I mentioned previously. I think these 8 principles give you a good mechanism for design assessment. For example, if you neglect service statelessness, you may end up with services that make your server crawls; if you neglect standardization of contracts, you may hinder interoperability and may need lots of transformation logic that represent implementation cost and performance overhead; if you neglect service discoverability (especially in the enterprise IT), you may produce a well-designed service with good runtime qualities (e.g., high autonomy) but others around the organization may end up recreating the same logic because they never found your service; and so on. So, although I agree with the design ideas proposed in this series of posts, I want to emphasize the bigger picture of principles and patterns. The right solution is not always the one with highest autonomy–the job of the architect is to evaluate the tradeoffs.
5) Suggestion for the second figure (micro services star): if you add a circular call to the figure will help to make the point that circular calls are the real problem.
LikeLike
Hi Paulo
Thank you very much for your comments to all the blog posts. Sorry for the late reply.
1+2) I completely agree the LOC (Lines Of Code) are mostly irrelevant, which was why I made this statement: Number of lines of code is in my opinion a horrible measuring stick for determining whether a (micro) service has the correct size or for that matter if it is a good service.
The reason why I discus size so much, is that it was/is at the epicenter of what microservices was/are for some people. That’s also the reason why I mentioned layered SOA in the light of code size, because many will call it “classic” SOA and not at all microservices. However if we look only at LOC they could be seen as microservices.
The point, which might have been lost, is that LOC is mostly irrelevant, it’s things like autonomy, runtime qualities etc. are much more important.
3) I corrected the blog to correctly say “highest possible reuse” (and not recycling). The point about smaller services (i.e. for some also small in terms of LOC) is that it, by several “SOA” architects, have been tutored as the best way to get reusability from many different contexts, because the smaller they are (e.g. only care about a single property/attribute) the less context specific they are. BUT this introduces a lot of bad side-effects such as consistency problems, latency problem, etc.
4) I agree there IS a lot of good guidance out there, sadly most of it is not known or ignored. Finally we completely agree it is our job, as architects, to evaluate tradeoffs and being pragmatic 🙂
5) True – good point
LikeLike
Hi Jeppe,
Good articles, I like it very much, and I translate this article into Chinese.
I have not Web site to host it, is it OK to put it to github?
Chinese Version:
https://github.com/hotjk/translation/blob/master/microservices/micro-services-its-not-only-the-size-that-matters-its-also-how-you-use-them-part-1.md
Weixiao
LikeLike
Hi Weixiao
Thank you so much for translating the articles to Chinese. It’s absolutely ok to put them on GitHub 🙂
/Jeppe
LikeLike