“I’d really like to make this change, but I’m afraid to, because I have no idea how that will affect the rest of the system.”
If you’ve ever felt this way - most of us have at one point or another - it’s likely that the code you are working on is basically a Jenga tower.
Pulling out one block could cause everything to come crashing down!
It’s a typical situation when it comes to refactoring your architecture from monolith to microservices.
Here are 6 signs your code base is like a Jenga tower!
1. You only have one or two repositories
Uh-oh! Even if it’s only a couple, but they are all large, these are tell-tale signs of monoliths!
Now, to be fair, this isn’t necessarily a bad sign; it’s more the ratio of ideas per repository. If you are dealing with several different conceptual ideas, even if they are vaguely related, it’s probably a monolith.
If you have a monorepo, that’s an exception to this rule, but do yourself a favor and use a metarepo instead!
Monoliths are bad for a lot of reasons! More on that later.
2. You don’t have enough test coverage
People always tell me, “You don’t need 100%; you just need to test the important parts.”
No. Wrong. Bad.
What they are actually saying is, “I don’t know how to refactor this appropriately so that it has well-defined and testable interfaces.”
It’s generally a sign you need to refactor a piece out into another module, or refactor to use currying or partial application. These refactors make modules super-easy to mock,
and they make tricky functions simpler. Remember, unit tests are about the individual units. Units should be small side-effect-free functions!
If I write code and expect it to be used in a certain way, or to do a certain thing, it needs tests.
If something is changing in your codebase and that affects other parts in ways you didn’t expect, you need to know about that immediately.
Sometimes, 100% test coverage isn’t even enough.
What I mean by this is, even if you have 100% coverage already, sometimes it’s worth adding a few more tests.
For example, if you have module A that imports module B, and the tests for module A cover module B’s usage, you may still want to write separate tests for module B.
Untested code is legacy code. Period.
If you are looking at your code and there are things like
async.series that give you a minor panic attack at the thought of writing tests for that monstrosity, well, see number 3.
👉 Further reading: Testing Distributed Systems: Case Studies from the Pros
3. Your code has side-effects
The spaghetti monster boss that is your code base has been secretly noodling its arms all throughout your codebase since the day you decided to do more than one thing in a function.
Spaghetti code is like a boa constrictor for your team’s productivity, squeezing more life out of it by the moment. Often, you don’t realize until you feel paralyzed at the thought of making anything more than a tiny change.
Unfortunately, this could have started quite early.
Refactor that code!
I recommend a dose of EventEmitters to untangle your local code, and then heaping a portion of message queues or distributed logs (RabbitMQ, Kafka) to start untangling your domain by moving side effects to their own area! Use a “servicebus” as an abstraction so it is as easy as using EventEmitters.
I use this servicebus for rabbitmq with Node.js: Simple service bus for sending events between processes using amqp.
Still, have you ever tried untangling a necklace that has been kicking around in drawers for who knows how long?
Sometimes, it’s really not worth it.
Instead, you may want to start refactoring your monolith to microservices by working at the database layer. Use a combination of “ingestor,” “model,” and “denormalizer” services to migrate your domain models piece by piece to microservices.
PRO TIP: Use two denormalizers to support the legacy db and the new API-friendly read-only projection db.
Which bring us to…
4. Your domain model is mostly in SQL
I know at least one former boss who’d probably be upset to hear me say this… (Hi Sergey!) but that was many years ago.
If most of your domain logic is made up of SQL logic, you may have a monolith.
In a microservices architecture, you’d instead have separate databases for each of your model services.
That isn’t to say each microservice couldn’t use SQL. They most certainly could.
However, I generally use the “Event Sourcing” pattern with model services, as it fits nicely with the nature of distributed systems. So usually, the only “table” I need is “Events” (and
“Snapshots” for optimization purposes), so SQL is just overkill. Schema-less databases also make this much easier given the varying payloads. Why do more work than you have to?
This log of events is an immutable source of truth for that domain.
If you’re a front-end developer, this should sound A LOT like redux to you, because it is. If you’re not sure why this is important, read this awesome article by Jay Kreps from several years back (it’s a long one): The Log: What every software engineer should know about real-time data’s unifying abstraction
5. You don’t even really understand your domain model, because, well…it’s complicated
Without actually taking the time to understand the domain model, it’s gonna be pretty hard to fix it.
You need to take the time and figure out not only what objects are important, but also what commands, what events, and who are the actors. You need to define boundaries around contextual representations.
Making a single “car” model doesn’t work if you need a representation of it for the marketing team, and for the engineering team building it. They both deal with cars, but with very different aspects of them. Their “context” is different.
A few years ago, I was in @garyvee’s office, and on the wall was the quote, “Content is king, context is God.”
Although he was referring to marketing, it rings true in computer science as well.
Build two car services if it makes sense for your domain: one within the marketing-bounded context, and one within the engineering-bounded context. Don’t try to jam all the ideas into once place. “Keep it simple, stupid!” (KISS)
6. You don’t have architecture, DevOps, or infrastructure in place to build these things
With the simplicity of your services, some complexity necessarily moves to the architecture.
The cat is out of the bag now.
jx create cluster gke
There you go.
You now have a Kubernetes cluster with versioned Production, Staging, PR Preview, and Development environments with Continuous Deployment and default opinionated deploy processes and Dockerfiles running on a Highly Available Google Cloud cluster! Just add CNAMES!
Jenkins X is a new tool by the CloudBees team, and it’s great.
No more excuses. Time to learn Kubernetes. It’s like your own mini-AWS, except way more
powerful and simpler, and made up of any group of servers!
Cause this is how you install stuff to your cluster:
helm install stable/rabbitmq --name rabbitmq --namespace rabbitmq
jx import ~/path/to/your/app
Oh… Were you still provisioning servers? Ouch. That sounds like a waste of time and money.
This stuff is 100x easier than it was just a year ago.
Of course, you’re gonna need to understand containers for that.
I’ve made it a mission of mine to help backend engineers and fullstack engineers like you learn DevOps (the easy way) - check out my story here for a FREE 21-day email course at the end: My Journey to Achieving DevOps Bliss, Without Useless AWS certifications
Conclusion: Monoliths are deadly for productivity - you need to refactor your monolith to microservices to save it from the spaghetti monster!
Sure monoliths feel fast when you get started, but after a few months, each change brings your tower closer to toppling over.
With that fear comes inaction.
If you or a loved one is still programming in a monolith, please help them, for their sanity’s sake!
Tell them to ask their microservice practitioner how refactoring to microservices can help relieve their stress, and save them hours upon hours upon hours of expensive development hours, and create services that will last for years Commits · mateodelnorte/servicebus · GitHub!
Here’s a boilerplate to get your started: GitHub - patrickleet/servicebus-microservice: A boilerplate for building microservices with servicebus
/Looking to learn Microservices? In my opinion, DevOps are a prerequisite due to the complexity of moving the architecture. I wrote a free 21-day email course to get you started on your journey with an introduction to containerization, orchestration, and how to run Next.js apps on AWS: https://www.devopsbliss.com Check it out!
Keep an eye out for my two upcoming premium web courses on Kubernetes and microservices!
Follow me on Twitter so you don’t miss it!
Until next time!