This article was originally posted on Aug 21st 2018 at: https://nickjanetakis.com/blog/microservices-are-something-you-grow-into-not-begin-with
As software developers we have a pretty interesting profession. We can be happily coding away one day, then read an article about something and suddenly you're questioning your entire programming related existence because Netflix said XYZ.
Just like that, from 1 opinion of an individual or company you begin to question everything you've been doing for years, even if it's been working great for you.
You're not Google (unless you're Google)
As we skim HackerNews and other programming news outlets we often come across tech posts from Google, Netflix, Amazon and Facebook and they love to talk about how many hundreds or thousands of services they run and go over the benefits of doing things their way. That's been a trend for the last few years.
But let's face it. You probably don't have 1,000 developers working on a massive project with 10+ years of history.
Just because Google does it, doesn't mean you should do it too.
We're operating in a completely different galaxy. Google faces problems that we'll likely never face but at the same time we can get away with doing things that Google cannot.
How Do Most Software Projects Start?
A lot of projects start with 1 person doing all of the programming. There's a million examples but let's look at Shopify. Shopify was originally coded by Tobias Lütke (it was based on Ruby on Rails and still is by the way).
Do you think Tobias sat there paralyzed by indecision while he painstakingly attempted to create a perfect microservices based architecture before he wrote a single line of code?
Hell no. I wasn't there when he developed the first iteration of Shopify, which was originally just an e-commerce store for snowboarding equipment but if he's anything like me (a typical developer), it probably went something like this:
- Learn a new technology while building your initial product
- Write some pretty non-idiomatic / crappy but fully working code
- See it begin to come together and get excited
- Refactor the "kill it with fire!" code into better code once it becomes a problem
- Keep repeating this loop while adding new features and running in production
This may seem like a really simple loop, but it took ~20 years of programming for me to really understand how profound that cycle is.
You don't get better as a programmer by theory crafting optimal set ups before you write a single line of code.
You get better by writing a lot of code with absolute and total intent to replace almost everything you write with better code once you start experiencing real problems first hand.
That initial code you rewrite isn't wasted time or effort. It's very much responsible for you leveling up over time. It is the secret sauce.
Let's Talk about Code Abstractions
As developers we've all heard the phrase "DRY: don't repeat yourself" and in general that's a reasonable guide to go by, but often times it's very well worth repeating yourself.
It's worth repeating yourself because if you try to abstract something without really understanding what you're abstracting then you create something called a leaky abstraction.
I'll typically repeat myself 3 times before I even THINK about refactoring some code to remove the duplication. It's often not until the 4th or 5th time that I actually take action.
That's because you really need to see how you're duplicating the code in different situations a few times before you know what needs to be turned into variables and also be removed from the place you're originally inlining it.
Levels of abstraction, from inlining code to external libraries:
- Write inline code
- Duplicate code a few times in different spots
- Extract duplicate code into functions / etc.
- Use your abstraction(s) for a while
- See how that code interacts with other code
- Extract common functionality into internal library
- Use internal library for extended periods of time
- Really understand how all of the pieces come together
- Create external library (open source, etc.) if it makes sense
And this is exactly what you can't "invent" a good library or framework. Almost all of the very successful tools we use today came from real world projects where the tool we love was extracted from real internal use cases.
Rails is a great example of this. DHH (author of Rails) didn't just wake up one day and say "OOPS! Time to make the models/, controllers/ and views/ directories!".
Nope. He developed Basecamp (a real product) and certain patterns emerged, and those patterns were generalized, then lifted out of Basecamp into Rails. This exact process still goes on today and it's IMO the sole reason why Rails continues to be so successful.
It's a perfect storm of very well tested (read: not theory crafted) abstractions, combined with a programming language that allows you to write visually appealing code. It's also why almost all of the "Rails but in XYZ language" frameworks fail. They are skipping key components of the abstraction chain and think they can just duplicate what Rails does.
Relating Code Abstractions to Microservices
Microservices to me are just another level of abstraction. I wouldn't necessarily say it's step 10 in the above list because not all libraries are meant to be microservices, but at a conceptual level it's similar.
Microservices aren't something you start off with from day 1, just like you wouldn't attempt to create a perfect open source external library on day 1 before you even wrote a line of code. At that point you don't even know what you're making.
A microservices based architecture is something you might grow into over time as you come across real problems working with your code base.
You may never come across these problems, and a lot of these problems can also be solved through other means. Take a look at Basecamp and Shopify. They are both monolithic applications that are doing very well.
I don't think anyone would call them small applications. Sure they are not operating at Google's scale but let's put some perspective on this.
Shopify Makes 17 Million+ USD a Month with a Monolithic App
As of mid-2018 Shopify publicly announced they have 600,000+ businesses running online stores on their platform.
Shopify is a SAAS application and its cheapest plan is $29/month and I have a feeling a ton of businesses are using their $79/month plan. In any case, even if 600,000 people used their cheapest $29/month plan, that's $17.4 million dollars a month in revenue just from the SAAS aspect of their business.
Basecamp is another monolithic application that is quite large. What's interesting about Basecamp is they only have about 50 employees and only a fraction of them are software engineers working on the main product.
My point is, you can get REEEEEEEEEEEEEEEALLY far without going down the rabbit hole of microservices. Microservices should be on a need to know basis.
When Should You Use Microservices?
That's really up to what you and your team decide. This is one of those things where if you are in a situation where microservices makes sense, you're not Googling around for "microservices vs monolith". You already know.
But one situation might be if you have a ton of developers who are best suited to work on individual sub-sections of an application. Having many dozens of teams working on various components of a product in isolation is one reasonable use case for microservices.
Keep in mind you probably had a very small team that slowly grew over time so you might end up starting with 1 or 2 microservices and going from there. You probably wouldn't end up going all-in and transform a monolith into 100 services right off the bat.
Is the Juice Worth the Squeeze?
It's also worth mentioning transitioning to microservices comes with its own set of challenges and problems. You're trading one problem for another, so you need to weigh the pros and cons on if it's worth it for your project specifically.
One of the biggest problems is monitoring. Suddenly you have a bunch of services which could be written with different tech stacks running across multiple machines and you need a way to pry into the details of everything.
This is a daunting task because ideally you'd want all of these services to use a single unified service to gather and display these metrics.
You probably don't want to develop your own tooling for this because that alone could be a full time job, and this is partly why companies like LightStep are so successful. They are one of the most interesting monitoring services I've come across in my travels.
Their product is targeted more towards larger scale applications (for good reason) but it could also work for smaller scale projects too. I only heard about them recently since they presented at Cloud Field Day 4.
Anyways, monitoring is only 1 of many challenges, but I thought I'd bring that one up because it's one of the more painful problems.
Final Thoughts
I mainly wrote this article for 2 reasons:
Firstly, 2 weeks ago ago I attended Cloud Field Day 4 and happened to do a group podcast on a related topic. That should be out in a few months (I'll update this post with a link), but I wanted to elaborate on some of my points here.
Secondly, as someone who creates online courses, I often get a lot of questions from people on the topic of how to architect their applications.
A common trend I saw was a lot of people were getting hung up on trying to break out their apps into isolated services before they even wrote a line of code. Even going as far as wanting to break up components of the app to use multiple databases from the beginning.
It was something that stalled them from moving forward and as a fellow developer I know how upsetting it can be to get stuck into an indecision loop (I've had my share of those!).
By the way, I'm currently working on a fairly large SAAS application which is a custom course hosting platform. Right now it's just me working on the project, and you can be sure I just cracked open my code editor and started writing code on day 1.
I have full intent on keeping it as a majestic monolithic until it no longer makes sense but my spidey sense is telling me I'll never hit that point anyways.
What do you think about this topic? Let me know below.
Top comments (14)
Really great post!
As far as I know, GitHub is basically monolithic Rails too. I may be wrong though.
All architectures are simply a point in time. Starting with the ultimate solution is almost always a bad choice.
There's absolutely nothing wrong with a monolith for a good majority of applications. If everyone is working on the same core product, there's really not much to be gained by breaking it into microservices, as seen with Github. Microservices are great for exactly that, services, and if your application needs to integrate with tons of other products and your team is the one maintaining those integrations, that's when you need microservices. As an ecosystem, Github really kind-of is built on microservices, they just call them 3rd-party applications. The core of Github is called a monolith just because they aren't the ones maintaining all the other services.
Let's face it... most current mature frameworks and programming langauges just dont adapt with microservices.
I'm talking about Rails and Django :(
Thanks. dev.to is also a monolith too right?
It seems to scale just fine with a quick loop on rolling out new features.
Yeah, traffic scaling is not a problem. Team-scaling remains to be seen, but I'm pretty happy with our outlook there.
How do you handle traffic scaling for monolith application? Just copy the application in different machine and allow load balancer to distribute traffic?
A couple of ways.
Vertically scale (add more computing power to a server) and then tinker with your app server's scaling properties. For example with Puma (a popular Rails app server) you can increase its workers and threads which in turn use more system resources.
Horizontally scale (add more servers) and then load balance them like you mentioned.
You could also do a combination of both.
For horizontal, there may be serveral technical issue I think.
For most application, the bottleneck would be in database. I mean the performance.
For the transactional issue, it could be done through pessimistic lock and optimistic lock I think.
For microservice, I often have a question for session. How can I get session info across different microservice component.
You can use a load balancer that supports sticky sessions, this way users are always routed to the same server.
Your cache would typically live outside of your application instance. For example with Rails you could use Redis as your cache backend and Redis would be running on its own server that's unrelated to your load balancer.
That's typically what people mean when they say they run "stateless" servers. For horizontally scaling most web applications, you want to keep them as dumb as possible. They should be disposable. I would strive for that even if you plan to do a small single server deployment.
I've dealt with some pretty big web apps before. A single SQL database can go a really really really really long ways as long as you avoid silly mistakes like N+1 queries and understand how to profile slow queries on demand. You can also cache expensive queries to avoid hitting your DB entirely.
I have to disagree. While as the article stated microservices are suited for a very large scale and specific scenario, in my experience containers solve problems common to even the smallest personal website deploy. They basically standardized a packaging format and configuration best practices, while before you probably had your zip, jar o executable that you had to copy to a server, unpackage/drop into an application server/run, changing obscure files to configure it for production use, now from an operation standpoint it doesn't matter how your application is put together. We have centralized repositories (container registries), a single method of distribution (
docker pull/push
), a single way of starting an application or a service (docker run
) and a single way or changing configurations (via environment variables). I migrated old Rails apps from a manual deploy to a containerized solution and even if they are simple monoliths used by maybe 50 people it made iteration and deploy so much easier and faster I would never go back.Kubernetes is another topic altogether, it is only useful when you have more than 30/50 containers to handle. But it can really help you scale out, even if the application is a simple monolith (monoliths still have to scale to accommodate traffic, spinning up more instances behind load balancers predates or microservices) being able to handle container lifecycle and scaling from a single point, having centralized monitoring and logging and easily scale to multiple machine if needed can really bring value to a business.
Thanks for the article Nick, big respect for your level of experience and insight applied here!
I like to do rapid prototypes and I've written a number of "tiny" monolithic applications, so what I'm about to write is a gut reaction based off where I am in my journey (devving for 15 years, pro for just 6):
The first version of a product (call it an MVP, whatever) is probably small enough that it can - by some stretch - be thought of as a microservice. Future developments to the product make sense either as extensions to what that service does or as new services.
My point is that if you sit down and start writing cloud-first code in today's PaaS climate, you'll write something that is not far from a microservice itself.
In fact, recently our team was writing an offline server-to-hardware integration (totally not cloudy at all) and we ended up building a number of services around an events queue as if by accident. No cloud! Perhaps there is a natural pull towards SOA as a sensible infrastructure in a wider variety of cases than we expect? (Link for the curious)
Love your post! I totally agree with it, I'm a big fan of the YAGNI (You Aren't Going To Need It) principles, which evolved into guidelines for me that are ver similar to the ones you describe. Software should always be an evolution, don't try and solve problems you don't have yet.
The list of "Levels of Abstraction" is so incredibly accurate, I love it! You really have no reason to create an external library if you haven't taken the time to really understand the problem, explore other avenues, figure out all the possible use-cases, and have a plan for how to abstract and build the library.
Totally agree on that, the monolithic is the way to go till you get clearer service boundaries and... move to the cloud services to infinitely scale (and get locked-in) :D