I know I’m not the first person to say that the benefits being sold of microservices are very similar to that of objects in object-oriented programming (OOP) during the 80s and 90s e.g. you can easily model the real world, and you get modularity, encapsulation, message passing etc., etc., baked-in to the technology. However, I’ve been constantly reminded of this comparison over the previous two weeks at work - the interesting thing is that the discussions I’ve had weren’t particularly focusing on the benefits of microservices, and in fact, they were focusing on the drawbacks. I’m starting to ask myself more and more, as an industry are paying enough attention to the microservice lessons that we should have already learned from OOP?
Inside the mind of Greg Young: Microservices and objects...
My thinking about this challenge was kick-started at last year’s muCon microservice conference here in London, where Greg Young did a superb talk about ‘The future of microservices’. I can’t recommend enough that you watch this, but in a nutshell Greg was pitching that when implementing microservices we should all take heed of lessons learnt from our first passes at building service-oriented architectures (SOA). I chatted to Greg in the pub for quite some time, and he also mentioned about the relationship between objects and microservices, which was definitely food for thought. He reminded me of Alan Kay’s definition of OOP, which I’ll paraphrase as ‘individual computers on a network, only able to communicate with messages’. Hmmm… this sounds familiar?
Now, don't get me wrong - there are quite a few differences between objects and microservices too. The first is that service-to-service calls will typically occur out-of-process, and most likely over the wire, which brings with it a series of challenges that you don't get with object-to-object calls. In terms of message passing, it's generally more difficult to cheat with out-of-band communication in microservices, with the shared database the obvious antipattern that Sam Newman talks about in his Building Microservices book. With OOP, it is often easy to access an object's internal state, either by bypassing accessors/mutators, or using reflection or monkey patching. There are a number of other differences too, but examining all the similarities and differences is probably a topic worthy of its own blog post...
Lessons learned from OOP, perfect for microservices
So, lets make the jump in our thinking that microservices and OOP do share some similarities. What have we already learned from 30+ years of OOP? (This list is primarily generated from my personal experience, and lots of great books I have read and awesome people I've worked with over the years, but also check out this list of software antipatterns)
- Avoid overgeneralising. I’ve seen quite a few projects where the class hierarchy was so deep and so muddled that even the smallest change near the base of the hierarchy often had massive ramifications. The same issues apply if you have overgeneralised microservices. Here though, the problem is often not code related, but instead data related e.g. what happens when one overgeneralised service that calls many downstream services to perform its task must change the format of its data payload… As Will Smith famously sung, 'tick, tick, boom!'
- Encapsulate what varies. OOP popularised the ability for us to hide the inner workings of objects (although Uncle Bob reminds us that you don’t really need OOP to do this). There are obvious benefits to doing this, and the same arguments apply to microservices. This is why domain-driven design (DDD) and bounded contexts are often mentioned in the same breath as microservices. If you haven't done so, please do check out Eric Evans' seminal DDD book, and attempt to base your microservices around these concepts.
- Avoid ‘god objects’. You know why, and so please don’t accidentally create an uber-microservice! All jokes aside, it is all too easy to do - check out Gilt.com’s admission of creating a Java monolithic ‘Swift’ service in their journey from a monolith to microservices.
- Don’t chain too many method calls together. The fluent interface is a very popular paradigm, particularly for creating DSLs (I’m looking at you Ruby). However, a less useful implementation of method chaining involves calling methods on multiple different classes for the sake of convenience (which can create a train wreck). The problem with this is that abstractions often leak, and you are left with very brittle code that may require modification when any of classes change within the method chain. The same issue applies if you are chaining lots of microservice calls together. Try and keep the call stack shallow if you can (and don’t forget with microservices, you are often going out-of-process to make a call, which only adds to the woes).
- Don’t create ‘utility classes’. We’ve all been here - as you’re busy creating a project you end up with a handful of classes that don’t quite belong anywhere… (even the Java language suffers from this problem). The utility class/package/service then becomes a dumping ground for random pieces of code, and so please avoid this antipattern if you can!
- Mutability causes problems. Being able to mutate state in a object is awesome, until you start doing so concurrently. Anyone who has worked with a language that supports the creation of immutable objects will immediately recognise the benefits, and the same can be said with immutable data being passed between microservices. A lot of people are having success with event-driven architectures (EDA) in microservices, and there is a good reason - events are immutable, and provide the canonical source of truth (along with a record of changes!)
- Modular code is easier to maintain. We all know this, and yet we don’t always code like this. Simon Brown has been championing this need for modularity for quite some time, and recently Martin Fowler waded into the conversation too. Even if you are creating small services, this doesn’t remove the need to structure your code well.
- Don’t abuse design patterns. Do you really need that AbstractSingletonProxyFactoryBean class, or is it just adding clutter (sorry Spring :-) )? Do you really need a 'aggregating-fascade-decorating-transformer-service' that coordinates all communication and performs payload transformations?
- Avoid ‘golden hammers’ - just as that all-singing-all-dancing application server or ESB didn’t solve all of your problems within development in the 2000s, please don’t expect that the latest microservice product will either!
- and many more…
Of course, these are only the tip of the iceberg, and I would be very keen to hear other lessons learned. Over the next few weeks we'll be sharing more similarities between microservices and object, and also talking about the lessons we have learned.
Please do share your thoughts in the comment box below!