Over the last few months, the buzz around Microservices has become omnipresent. Tech conferences, blog posts, technology forums – even analysts feel compelled to chime in (I sometimes wish they wouldn’t). Of course, Constant Contact is no different. I get the occasional email about ‘look what cool things Netflix is doing with Microservices’ or questions like shouldn’t we do ‘Microservices’?
So, what’s our story here at Constant Contact? Truth is, we have been doing Microservices for years. Not like Netflix, which invests a ton into writing even infrastructure components – we are a much smaller organization.
But, what are Microservices, really?
If you want a good write-up, head over to Martin Fowler’s blog post on the topic.
I think he puts it well in stating that Microservices are what service oriented architecture (SOA) should have been. Microservices and SOA share many common principles, just like most component based, distributed systems. He makes a good point everybody should remember as well “While there is no precise definition of this architectural style, there are certain common characteristics around organization, around business capability, automated deployment, intelligence in the endpoints, and decentralized control of languages and data.”
Constant Contact Tech History 101
Before I go over the major components of a Microservices architecture, let me go on a brief detour to visit our technology history here at CTCT.
A few years ago we realized, like many tech organizations, that our monolithic application architecture had reached its limits. Velocity was slow, build times long, extensibility and scalability at their limits. So we embarked on a services based architecture (the term Microservices didn’t really exist yet). We don’t call it SOA for a reason: it means so many things to different people – just ask the major vendors in that space (my advice – don’t).
We wanted a lightweight version of that. Simple, easy to understand, open source driven and allowing us to learn as we went along. And boy, did we learn. We chose a REST/JSON-based approach over SOAP/XML because we wanted good interoperability between tech stacks (ours are Java/Ruby). Let’s look at our architecture today, using Martin Fowlers blog as structure.
Componentization via services
Check. I’m not going to go into any details here. Our services are independently build-able, deployable and expose a remote REST/JSON interface. This is actually one of the most important aspects of Microservices: packaging and deployment: every service on its own.
Organized around business capabilities
This is a very important aspect to get right. How big is a service? What’s in it? There are schools of thought that services simply provide a Create, Retrieve, Update, Delete (CRUD) interface for one single resource or to do one thing and one thing only. Our approach is a little different. We us a domain driven design approach. We look at the business domain, create a domain model and figure out how many subdomains we can divide it in. Each sub domain becomes a service or more. Anybody who’s done it before will tell you that this is as much an art as a science. It’s difficult to get right. Make it too fine-grained, you wind up with hundreds of pico services (I’m claiming that term, if any analysts read this). It becomes an unmanageable tangled mess with too many cross dependencies. Make it too coarse-grained and you’re back to monoliths. I’d like to think we are doing a decent job.
Products not projects
Exactly. While Amazon says ‘you build it, you run it’, we aren’t quite that good yet, our dev ops needs work. But we do follow the principle: you build it, you own it. We may still have a centralized tech ops team, but the teams that build a service own it for the long run. “Owning” means that you don’t hand it over to anybody else, monitor its scalability, quality etc.
Smart end points and dumb pipes
This is actually a very interesting difference between Microservices and SOA. Many SOA practitioners advocate using coordination and business logic in Enterprise Service Busses (ESBs). Don’t get me wrong, it’s not a bad approach, but it’s complicated and most open source ESBs are, let’s just say, barely ok. I know I’ll get heat for that in the comments section.
We use a peer-to-peer based Microservices architecture, where one service makes direct, synchronous calls to other services following the “smart endpoints, dumb pipes” principle. It works, is simple, but has its challenges when it comes to fault tolerance and dependency management. As time goes by, we are introducing simple message-based integrations using RabbitMQ, but we are just getting started. Message-based and asynchronous integrations are great and, while a little more complex, greatly improve fault tolerance and scalability.
The entire coordination logic implementation can actually be a weak spot in Microservices architectures, but I’ll discuss that in my next blog post.
Check. Sort of. Yes, we run multiple technology stacks and we don’t really govern all services in a central fashion. But, we do have standards. Versioning is a great example, because we expose our services publicly and it’s important that the APIs work alike. Response and error codes are a good example. I know some Microservice practitioners frown on that, but it works for us.
When I say versioning, I don’t want to suggest that we are running multiple versions of a service as separate deployables at the same time. We simple hard version our APIs, whenever we make non-backwards compatible changes and while we require all of our services to be backwards compatible for one major version, this allows us to keep track of things: version 3 supports API calls by clients assuming version 2, but not version 1. That way, we can upgrade our APIs even if the changes are incompatible, without having to touch all clients at the same time.
Decentralized data management
Or course. Aside from legacy wrappers all services own their data. That’s nothing new and if you ever build a distributed system, that’s what you do if you want to keep your sanity. We have a strict ‘don’t touch my data’ policy here at CTCT. Actually, it’s an “don’t even look at it” policy. Access to data is only allowed via service APIs. The common exceptions are Extract, Transform, Load (ETL) jobs for our Big Data cluster.
This is our weak spot. and something I highly recommend that you focus on getting right, right out of the box. If you start today, probably on Amazon Web Services (AWS), you won’t struggle with this. This is just how you do it. But, if you come from a data center hosted approach with a dedicated tech ops team everybody relies on, it’s a little rockier. We didn’t invest early enough in dev ops and continuous delivery. We have made great progress now, running an OpenStack cloud and automated CDS pipelines, but it took a while and we definitely struggled. It becomes a real velocity limiter and I wish we had done more here, sooner.
This gives you an overview over what we are doing in regards to Microservices. If you follow our blog posts you will find a lot of related topics that deal with the underlying technologies. In my next blog post, I’ll be talking about our challenges in dealing with coordination logic.