No such thing as an anti-pattern | Common repos aren't terrible y'all | dev hot takes
A few days ago, I sat in on a talk that dived into why common repos are “anti-pattern”. Yet just a few weeks before, I was going around gloating to my peers about how common repos saved my life. Having been in the software world for 5+ years now, my honest opinion is that there’s no such thing as an “anti-pattern”; software isn’t governed by the law of physics like traditional engineering fields but rather the law of usability for a given manmade context on an abstracted layer over the physical magic, which typically (though not always) translates to the law of business logic.
Think about it: How many times have you made trade-offs between what’s optimal in runtime versus what you can get into the client’s hands ASAP? If you’re a practiced Python dev with a deliverable due by the end of the month, would you start writing your APIs with Axum when you haven’t the faintest idea of lifetimes and ownership in Rust? What’s the point of optimizing CPU and speed if there’s no app at all? And it’s not like designing bridges where a manufacturing defect in a single eyebar can cause the whole system to collapse and kill people (re: Silver Bridge over Ohio River - circa 1967); in such cases, it’s a no brainer to design for the absolute best under the unforgiving eyes of the physical world.
But that’s not the case with software which is its beauty and curse. You can deliver faulty and iterate over time in most cases (*assuming you’re not building the core of an app that calculates the failure modes of an eyebar or other bridge parts for immediate use*). Sure, your client might hate you a little for the fact that their Twitter clone is not persisting people’s tweets past 48 hours in the first release but hey: no one’s dying. The real curse is when that release of your product is so unmaintainable you’re not even sure about how to deal with it anymore. And it’s not like rebuilding is always an easy backup plan.
So, this speaker at the talk spoke in favour of microservices for this very reason.
Funny enough, maintainability is why I am vouching for using a common repo.
So, who’s right? The answer? It depends. I am going to counter some of their arguments with a Rustic perspective:
Their points:
- Microservices ftw for dependency isolation
Ever heard of workspaces? This allows Rust to handle dependency isolation very robustly within a common repo. Suppose you have an app, myproj, that is essentially a cargo (rust) workspace comprised of LIBA and LIBB libraries.
# LIBA Cargo.toml
.
.
.
[dependencies]
old_crate = { version = "0.3.4", features = [ "plots"]} }
# LIBB Cargo.toml
.
.
.
[dependencies]
rust-decimal = { workspace = true }
# Cargo.toml in ROOT of project
[workspace]
members = ["liba", "libb"]
[workspace.dependencies]
rust-decimal = "1.38.0"
At a first glance, this is fine. It should work in theory… that is until you run cargo run and get a dependency tree conflict between LIBA and LIBB. This error will advise you to run cargo tree and give you the following:
myproj v0.1.0 v0.1.0
├── liba v0.1.0
│ └── old_crate v0.20.2
| └── rust-decimal v1.10.1 (*)
└── libb v0.1.0
└── rust-decimal v1.10.1 (*)
.
.
.
As you see, the drastically different versions of rust-decimal is causing this issue.
I never gave you much context on LIBA, did I? Suppose it’s a very dated machine learning crate with traditional ML algorithms that have been defined for well over decades now. The contributors of this project did not have any reason to really continue with it—it was meant calculate things (i.e. a decision tree). It’s now up to the users to handle dependency isolation when working with newer versions of the same crate within the same project (as seen in LIBB).
You may have to force dependency isolation with microservices. Or, you might want to go shopping for a new crate that handles decision trees with modern techniques.
What if microservices are just difficult and annoying for your use case otherwise? What if new crates aren’t any option? (Note: Traditional ML algos are not very hot right now. People are so focused of LLMs and image classifies so this is actually very likely.)
# LIBB Cargo.toml
.
.
.
[dependencies]
rust-decimal = "1.38.0" # Change the version in the local crate folder
# Cargo.toml in ROOT of project
[workspace]
members = ["liba", "libb"]
[workspace.dependencies]
.
.
.
That’s all you have to do to make it work. Keep on with your common repo, friends.
- Engineer for the future. Anticipate for the growth of your codebase.
Suppose you’re in a situation where the SLOCs for your services are going to increase by tenfold, no doubt, and you’re wondering if you should go for microservices. I’m going to still say it depends~
First and foremost, of course frontend and backend should be different repos if you anticipate more than one person working on these. They will almost always be built on different frameworks, and these will be drastically differen even if the core lang is the same. I started my whole career as a Flutter dev and ✨panicked✨ when I had to revisit the UI code while my frontend-guru comrade was away. Even though I could reasonably go in there and make changes, I don’t usually other than ~2x a year. There is absolutely no reason for that code to sit alongside my Rust backend.
My Rust backend on the other hand is comprised of ~4 different local libs. I am the only person working on them now, and my workflow is much more simplified when I can identify fault in my dependencies and change them on the fly as opposed to getting fatigue from scouring and committing to different repos. After all, they’re all based on the same vanilla Rust (if that’s even a phrase) so there’s no reason someone can’t reasonably work on another service in the same repo if needed. My teams (~2-10) feel the same way overall since we contribute to all services as needed. Microservices can feel like an over-engineered hell if you don’t have a reason other for it other than it’s “best-practice”.
Best practice to me is imo going with what I need to get the job done with my current workflow. There may be a time in the future when I think microservices are the right path ~ we will know when it’s time. Until then, monorepo all the way.