fbpx logo-new mail facebook Dribble Social Icon Linkedin Social Icon Twitter Social Icon Github Social Icon Instagram Social Icon Arrow_element diagonal-decor rectangle-decor search arrow circle-flat

Tandem Roundtable: Microservices Vs. Monolithic Architecture

Darcy Garrett Engineering Manager
Toby Leonard Engineering
Varell Hawkins Engineering
Ryan Ferguson Engineering

What experience do you have with either microservices or monolithic architecture?

Darcy: Most of my experience has been with monolithic architecture. I have a bit of experience with microservices at previous jobs, but most of that was with hybrid situations with legacy apps that followed monolithic architecture and new microservices for newer apps.

Toby: That’s about the same for me. One of the student workers at my last job, who was very ambitious, was trying to sell my team on breaking up our services into microservices. Our team lead turned down the idea, saying it sounded like too many moving parts.

Varell: Pretty much all the apps I’ve worked on have been monoliths, even when working for an e-commerce company. With a client of ours, an application was set up as a microservice architecture but still deployed to one machine with many Docker containers. I’ve read a lot about the advantages and disadvantages of microservice and monolithic architectures over the years and am excited to work on a microservices structured application.

Ryan: I’ve worked mostly with monoliths. I’ve worked on a project that involved splitting up a giant monolith into microservices. I’ve also worked on a project going the other way, consolidating microservices into a more monolithic architecture. I’ve never worked on something that was designed as microservices from the beginning and stayed that way; I haven’t worked on problems that required that sort of architecture.

What are the biggest differences between the two? Benefits? Drawbacks?

Ryan: I think one reason you might want to have a microservices architecture is to have smaller units of deployment so that you can deploy services independently of each other. That doesn’t come for free, though — you have to design them not to be coupled together, but it gives you more freedom in deployment if done correctly. For example, if you have two different parts of an app, one changes a lot and one doesn’t change very much, in a monolithic architecture the number of deploys is driven by the part that’s changing more. This means you have a lot of turnover in the code that might be unnecessary compared to if they were split up.

Another nice thing about microservices is that you can scale services independently as well. If you have a particular part of your app that is CPU-heavy or uses a lot of memory, you can dedicate infrastructure resources to fit that need, and the rest of your application doesn’t need to be on a big machine. With a monolithic architecture, the resources are determined by the most resource-intensive part of the application. In general, that’s one of the benefits of microservices — you can scale parts of your application more precisely.

Varell: Ryan hinted at a lot of things I want to highlight, like horizontal versus vertical scaling.

Ryan: Yeah, that’s a better way to say it.

Varell: With vertical scaling in a monolithic application, you have to deploy the whole app to be able to respond to more requests. You can add more resources to the infrastructure that the monolith is deployed to. When scaling microservices and one service needs better performance, you can deploy more instances of a particular service, allowing you to target particular parts of the app.

One difference I want to bring up is their deployment strategy. Like a Hershey bar, you can either choose to have the pieces all together (monolith) or break them apart (microservices). Also, when first developing a project, you often don’t know what parts would be good to break into microservices. Creating a monolith first and then breaking it out into microservices could be a good approach to take. I’ll pass the mic to DJ Darcy Darce!

Darcy: Woohoo! I need the lights to change or something, haha. With microservices, ditto a lot of what’s been said. I am thinking about it more organizationally — typically with microservices, you have a team that’s responsible for a specific service and one of the pros is that you can have a team specializing in a specific tech stack, and then another team working on a service with a completely different tech stack — and that’s okay! As long as these teams communicate and make sure the services are compatible.

However, a drawback is knowledge siloing, that’s one of the challenges with microservices. It’s difficult because large organizations will hire people familiar with different tech stacks for different services, and that usually ends up with few people who understand the whole system. And testing for a monolith is a lot more straightforward. With microservices, you have to test each service and account for its dependencies which can be very complicated. I’ll pass it to DJ Toby Tobe!

Toby: Haha I do have lights that change in here, I should get them going sometime. I like what Varell said about building the app first and then breaking it out because in my experience, that’s a good way to do it. It’s the whole Red, Green, Refactor thing — find where the problems are, fix them, then break it out, etc.

My issue is — and I may be overthinking this — communication between parts of an application. A microservices architecture has more complexity because of the possibility of network failures. In a monolith, instead of making a method call, which yeah it could fail, you’ve got the entire network stack on top of it, plus any unreliability that might be there.

With microservices, now you’re not just making a method call, you’re basically making an HTTP request to something else. So if part of the code needs to query some users, you’d get an HTTP 500 error with the microservice. As a Rails developer, that would bug me if I had to do things in a transaction. If my team had to update a batch of users, then begin a transaction, post, and still have to implement that ourselves? I’d rather do that in a monolith because with Active Record, there’s a way to just do that using Ruby syntax. Anything beyond the basics, like once you need transaction support, means now you’ve got to start writing that code yourself. Now you have all these layers in between, so there’s more chances for failure.

That’s always been my big thing — is it worth the extra complication? If you’ve got the numbers behind it saying we can scale up as needed, then great. I’m with Varell, you should wait until you have a definite use case for microservices.

Ryan: Yeah, Toby you brought up a downside of microservices that I think should be emphasized: they involve a lot more network calls and networks are unreliable. In making a request to another service, a transient error could occur because the service was down for a second. This means you have to include retry logic, probably with some sort of exponential backoff so you don’t hammer that service and cause cascading failures. None of those issues are involved in a monolith when making a simple method call.

And yeah transactions are another thing that become more difficult. There are patterns for implementing distributed transactions, like Saga or Two-Phase Commit. But implementing the Saga pattern can get complicated and not every database system implements the Two-Phase Commit protocol.

In a monolith, you typically have one relational database and transactions are just part of the feature set of that.

Toby: I’ll just add on that yeah, saying “Let’s abstract out the users’ stuff” means now you’ve got to write this abstraction and add to it; it’s not easy.

If you want to do it cleanly, you’ve got to add a layer of abstraction because you also need to be able to test this. So instead of “here’s the code to manage users,” you now have to create an abstraction like an interface or protocol that lists out all of the methods you may need to call on users – create, delete, update, etc – and then you have to write your specific implementation for it that will work with your microservice. This isn’t a bad pattern! It’s one I’ve used in the past very successfully because it separates things. But, in the case of something like database access, where for example something like ActiveRecord handles it beautifully for you, now you’re reinventing the wheel.

The end result is the code doesn’t need to know about the details of the database connection — it only knows as much as it needs to know. The benefit to that is now you can test all of your code very easily — you don’t need a database connected.

With a monolith, we already know we’re using Active Record, so I can just have a piece of code somewhere like user.find and then boom, I have the user. It’s that easy! With a microservice, now the code has to know to call out to the microservice or you’ve got to do the abstraction we talked about. The code calls the abstraction and further up the chain we’ve figured out that it’s going to talk to the microservice, but the code doesn’t know that. It’s extra work. It can absolutely be worth it, especially where testing is concerned, but it’s a tradeoff.

Varell: One thing Toby mentioned was that with monoliths you can just call a function. This is also possible with microservices using gRPC. With gRPC, you are able to call a function that exists on another machine as if it is a part of your codebase.

I agree with what was said about transactions, especially in a case where you have to change multiple tables in multiple different databases across multiple services — how do you roll back if only one of the databases updates correctly?

Ryan: One could argue that okay, look at all the monoliths out there — a lot of them turn out to be a big ball of mud, becoming difficult to work in as more people work in them and they scale up. The main thing that goes wrong with them is that there’s no module boundaries and everything gets coupled together, so there’s no way to decompose it into different units. You might say, “Oh, well why don’t we set some hard boundaries? Maybe microservices are better because we can avoid that ball of mud.”

But I think you could do the same thing just as easily, coupling microservices together so they have to be deployed at the same time, creating implicit dependencies in data processing. Just because you do microservices doesn’t mean you have decoupled architecture, you have to enforce it through careful design, like with a monolith.

Varell: This lack of structure and/or boundaries in code is also referred to as spaghetti code and when applied to architecture it has been referred to as spaghetti architecture. Either way, what you choose takes discipline to implement well and reimplement as an application changes.

Darcy: Microservices come with a lot more complexity that is hard to manage because for each service you have to handle deployment, logging, possibly a unique tech stack, and an entire team. It requires more responsibility for the team that owns it and it decentralizes decision making, which comes with pros and cons — could be a whole other blog post!

How does communication in the organization shift based on microservice or monolithic architecture?

Ryan: I think Darcy hit the main point very well — microservices can facilitate decentralized control and management. Also, with microservices, the medium of communication changes when you have to coordinate different services because you’re communicating via contracts or versioned specifications. What’s the shape of the data that this service expects and returns? Your service is expected to adhere to that contract so other teams can rely on it — it’s more formalized. On the other hand, in a monolith, those boundaries are generally softer.

Toby: Yeah just thinking about team communications, even if you had multiple microservices using the same tech stack, the structure could be one big team or split into a couple of teams. For example, if you had the option of coding in Go, and the main Go developer is on vacation, then the update won’t get pushed.

Varell: It’s quicker to get up to speed on a microservices app because it’s smaller — you wouldn’t really be looking at code that’s for a different feature because everything you’re looking at is part of the service you are responsible for. In that way, microservices are more streamlined.

Ryan: With microservices, you would have different versions and deploy a new version of your API, but continue to support the old version so nothing downstream breaks. But there’s often cross-team communication needed like: “Is everyone off of version two? Can that go away so we don’t have to support that anymore?”

Toby: You can kind of bake that into the contract if you agree to do semantic versioning, but even then yeah, you run into “We’d like to move to new framework X but we can’t because these two old projects run on this old framework, and we have to support it to keep it alive.” It’s hard.

Varell: Versioning for endpoints may be unnecessary because developers will see in their local environment if something breaks before they push to production.

What’s overlooked about either microservices or monolithic architecture?

Darcy: I don’t have hard data for this, but I highly suspect that monetary costs are overlooked with both — but more so with microservices because you have to pay for all the different services and applications that support each service plus the engineers.

You need more specialized engineering if you have a system with lots of different types of technologies because it requires developers familiar with specific tech stacks, DevOps, and numerous tools for monitoring and logging. Costs add up, so microservices can be unrealistic for many small and medium-size companies.

Varell: I guess my biggest ‘ick’ is when people get caught up in following trends. Both microservices and monolithic architecture are proven to work, you need to decide what’s best for you. Thought leaders start writing blog posts about the same topics and business professionals feel like they need to migrate to one thing.

It’s not a bad thing to learn what others are doing, but implementing a structure because others are is an overlooked thing. Just because others are using one type of architecture doesn’t mean it’s good for your situation. It’s good to read, learn, and see benefits of each architecture type, but it’s even better to draw a conclusion about if it’s the best thing for you right now.

Ryan: You can design a monolith to gain some of the benefits of microservices that we’ve mentioned, like being able to adapt and change quickly, by having well-defined module boundaries.

As your monolith evolves and the domain objects become established, you could even go as far as to separate modules’ data in the database and have different schemas for them. That has independent benefits for making your code easier to understand — making your system easier to understand and change — but also can give you more flexibility with what infrastructure the application is ultimately running on.

For example, if you had different modules that were separated, communicating via a message bus or some in-process facsimile of one, splitting one of those modules out to one or more microservices is not hard and doesn’t require a lot of changes to the code. If you write your monolith in a way that expects async communication, you can be a lot more agile and get the benefits of microservices when you need them.

Let’s do something great together

We do our best work in close collaboration with our clients. Let’s find some time for you to chat with a member of our team.

Say Hi