Blog header image

False Dichotomy: Monolith vs Microservices. Introducing the Rider and Elephant Architecture

Here at DealGate we believe that that microservices versus monoliths discussion is a false dichotomy. We've always built products where our arcitechture of choice is what we call the "rider and the elephant". In this article We'll convince you why the rider and the elephant is better than microservices versus monolith. The 'elephant and rider' is not a new invention, but just the name we call it internally.

GoLang gophersAlt: GoLang gophers

The name of this architecture comes from ideas similar to Sigmund Freud‘s ID, ego and superego concept (yes, yes, he's a freak. We understand). This concept is where your ID and superego are in constant battle with each other while being referred to by your ego. Simply put, your 'ID' is the part of your mind that wants the icecream, your 'superego' is what knows you shouldn't have the icecream and your 'ego' is the judge between the two. Another modern and understandable example of this is likening your more developed, and intelligent forebrain to an elephant rider and your less developed but more powerful 'lizard/caveman brain'. To an elephant. As with an elephant, you can't control it, but you can guide it.

The elephant does what he wants without working with the rider and nothing productive will get done, if the rider tries to work without the elephant he won’t be able to overpower the elephant because the elephant is much more powerful than him. The only way these systems can work is when they cooperate.

Traditionally, the question when designing a web app comes down to many microservices or a monolith. Why not have the best of both worlds for my company? At DealGate, We process many tens if not hundreds of thousands of images and PDFs, scrape tens of millions of websites, and run tens of millions of regular expressions per webpage. But our main app is written in NextJS, how doesn't our infastructure choke? NextJS/NodeJS is single threaded afterall (yes, NodeJS isn't, JS is. This is for the sake of argument). Our infastructure doesn't choke because we have two nodes. The “elephant” is a GoLang application, it doesn’t contain any business logic, but at any moment has tens of thousands of goroutines turning data at all times. This is controlled by the “rider” NextJS application. The NextJS application contains all the business logic database access et cetera but none of the actual heavy data processing capabilities. We've found this to be a highly performant, and decently easy to maintain architecture.

In recent years, many companies have been moving away from microservices back to traditional monoliths. Most recently (and publicly), Amazon Prime. You know it's a decision made as a neccesity when the company that owns all of the infastructure (AWS) is moving away from from it's own products. Those of us who never bought into the hype-cycle could see this coming. (without even jumping onto microservices in the first place.)

This is because during testing. We figured out that, even though NodeJS runs many threads for IO/networking etc, It's regular expressions all run on one thread. This translates to is terrible performance when you’re doing many regular expressions in the background while also trying to run the back end of your web app. Alternatively, Golang is perfect for this, you can comfortably throw tens of thousands of CPU-bound processes and it will happily churn through all of them.

Now the reply to this is why not just write your entire application in something high-performance like for example go Lang. By that logic, it should be written in assembly. The reality of the matter is higher languages are safer and more productive. There were considerably more JavaScript or Python programs than Rust programmers. This means that not only does an hour of Rust developing cost more than an hour of JavaScript developing but an hour of Rust developing ends up achieving a lot less than an hour of script developing. This is the nature of having a higher-level language.

Extending this thought process makes sense that we want to write as little high-performance code as we can get away with. Realistically, 90% of businesses can probably run on monolith, my eyes roll every single time people at my day job recommend a new tech project to be built using a monolith. It’s necessarily loads of technical. Overhead. Assuming you’re not the 10% or so tech businesses that do need some kind of data processing set-up.

Your elephant can be written in a high performance, but harder and therefore more expensive language. And most of your Web app i.e. the rider can be written in a high-level often asynchronous programming language like node or python. This means that you can keep your low-level programming costs to a minimum while maintaining high performance and being hyperproductive with a cheaper workforce working on your high-level languages.

The argument of “if there are higher performance languages, then why not just write everything in a higher performance language”. This is one of the responses to companies like Discord switching from higher level to lower level languages such as their famous Go to Rust switch a few years back

If you have a full team of engineers who are willing and able to write Rust, then you should probably write Rust (unless the extra complication doesn't make sense, like a basic CRUD application). Even as someone who has been programming for the best part of a decade, I find Rust tedious (even if I am a fan of it), preferring much simpler writing and productive languages such as Typescript, Golang, Python etc. The issue with this "just write Rust" thought process, is that the overwhelming majority of software dev houses probably cannot afford to attract programmers who are raving about Rust. Most programmers are just regular people who clock in at 9 and out at 5 then go home to spend time with their family, they don’t have 23.5 hours a day to spare learning about the wants and needs of Rust's borrow checker. Most companies cannot afford Rust developers, and most developers can't be bothered to develop in Rust. (this applies to all lower level / higher performance languages, not just Rust). Inherently, you get more done, in a shorter period of time in a higher level language.

This is something to keep in mind, and this is one of my many gripes with microservices. Something very important, especially because our elephant doesn’t contain any kind of business logic is that we want to reduce communication overhead as much as possible often microservice architectures are built off of interprocess communication using HTTP and JSON is a terrible format to communicate between microservices the serialisation deserialisation overhead of JSON is obscene.

At my own company, we use gRPC to keep the overhead in communication between our elephant and rider to a minimum. Using a low overhead communication protocol we can afford to send a huge volume of data between the two nodes. Sending large volumes of data is also oftentimes a requirement because the elephant will not contain any kind of business logic so it will be treated as a “dumb worker” have tons of work on it's way.

There are no solutions, only tradeoffs

- Thomas Sowell

Ultimately, the conclusion of this article is the old Sowell quote. But I like this compromise between the flexibility of microservices and the reduced mental overhead of a monolith. Go fast where you HAVE to, choose higher level languages for everything else.

👋 Hey, thanks for reading.
Here's a gift for you! 🎁

We're giving out 10 FREE leads.Check it out here

Hurry up, offer ends this week!

Article written by
Dylan Moore

Dylan Moore

Read more

Post Tags

TechblogSoftware ArchitectureMicroservicesMonoliths