64 likes
·
2.4K reads
8 comments
Thanks for the article. I have had countless arguments with colleagues over this as well. Most times, a monolithic approach is perfect for a solution and one doesn't need to overengineer
"Pebble vs. Brick" is a great heuristic to use when thinking about micro service boundaries. Good post.
Great post! When I now think of a lot of SaaS products, monolithic would just do fine, but they tend to go towards micro services. Unless there is an extreme volume of concurrent users, from your experience when would you advocate a micro services architecture?
Good question. The scope of each service is less about scale (because we can generally run as many instances as needed) but rather about the shape and complexity of the overall system and flow.
For example, it's reasonable to implement three independent aspects as three separate services. Or to implement a two-phase system as two services. There is nothing wrong with small-scope services, per se, from a correctness point of view (although operational overhead is another matter). However, the microservice approach gets into trouble when it breaks up tightly-coupled functionality into multiple services -- and then the failure modes pile up.
In my experience, team structure or existing unmergeable codebases sometimes make the problems with such a service division the lesser evil. But it's not ideal.
Henning Rohde - Interesting!
Thanks for sharing your thoughts. The books that I read so far generally take small scoped services as an example, hence I tend to think of smaller services always. Probably, I need to change that mindset.
I extremely loved this article.
Partial failures. The Request service is promising atomic behavior to its callers, but it uses several primitives, which makes it difficult to honor that promise. For example, if a request, A, updates the address and "the usual," then it may succeed in updating only the address (due to a failure from the Config service, say, or a crash). That leaves the system in an inconsistent state
I am not sure I understood this paragraph correctly, but isn't it a simple implementation detail of the "Request" service? If it fails to update, it should not proceed to placing an order -> "Sorry something went wrong with your order. Please try again." should be sent back to the customer.
I do get the point, though, that with the monolith there are less points that you have to consider to maintain atomicity.
Race conditions. Even in the successful case, inconsistency is possible.
Well this one comes from (in my opinion) a poor design decision for how the services communicate in the given example.
In the case of the pizzeria here, I would change the design of the system so that the "Request" service packs all necessary data in a "Place Order" job that is sent tot he "Order" service. So the order placement would contain
{
pizza: <Pizza ID in the products service>,
delivery_address: "42 Universe street, Alabama"
}
This is good article nevertheless and the main message that microservice architecture brings extra complexity that is non-existent in monolith is very much true.
Good points!
In the partial failure case, the request that fails to update some information would indeed not place an order. The problem is that there are 2 requests: the first fails halfway after updating the address, and the second does not specify the address or the usual. If the node handling the first request crashes, the service would not be able to undo the update or even return a reasonable error message to the first caller.
Your suggestion for a better contract is definitely an option and is mentioned in the article. However, in practice, it's easy to overlook these failure modes and only realize the complexity after they happen (especially if the system is used in unexpected ways).