Why Uber’s DOMA is WRONG

Rogelio Consejo
8 min readMar 19, 2022

Dear Uber, please do not mistake the map for the territory.

Domain-Oriented Microservices Architecture 😑.

Let me start with what is wrong with the highest-level idea.

Microservices Architecture

“Microservices” is definitely not a type of architecture. Microservices is a deployment strategy.

What is the difference?

One is supposed to deal with abstractions, boundaries, actors, dependencies, responsibilities, modularity, uses, interfaces, relationships… the other is about a specific way to deploy, monitor, and maintain a system over a network.

It’s like talking about a “pdf poetry”, a “1080p dance”, or an “mp3 compression style of music”.

I don’t have a single doubt that you are an expert in the details of implementation, but you are missing a lot on the abstractions.

The moment you call something a “Microservices Architecture”, it does not really matter what other qualifier you append to it, you are not going to make too much sense. Not in a practical way.

This is not a small thing or a pet peeve.

And it worries me because I know that a lot of very smart people are going to start talking about “Microservices Architectures” because of that.🤦

It is very bad semantics. And to be honest, it shows.

You start by moving in a great direction, only to end up doing a hard left into a ditch.

And I am not just talking about the five-word title of your article.

By the way, “Service-Oriented Architecture” is just another example.

So there is that…

Cringe

Now, let’s go down to the details

The fact that you are even able to manage 2'200 services and offer the level of service that you offer (I am a happy customer) is, to me, an amazing feat of engineering. Seriously.

Now let me tell you a secret.

But first, what are the biggest benefits of a “Microservices Architecture”?

  • Independent deployments? You can get that with plugins.
  • Clear ownership? You can get that with an interface.
  • Better separation of concerns? You get that by following the Single responsibility principle, keeping your system Open for extension but closed for modification, making sure that your inheritance follows the Liskov substitution principle, keeping your Interfaces segregated, and using Dependency inversion.
  • System stability? You can get that with a load balancer, distributed persistence, and horizontal scaling (but you know that).

Let me quote you: “In order to build a simple feature an engineer often has to work across multiple services, all of which are owned by different individuals and teams.”

  • Independent deployments? Maybe.
  • Clear ownership? Nope.
  • Better separation of concerns? Lol, no.
  • System stability? Maybe, if you have enough other systems and engineers to throw at it to keep it “stable”…⸮

Again: “In order to build a simple feature, an engineer often has to work across multiple services, all of which are owned by different individuals and teams.”

Oh but there is more: “This requires extensive collaboration with time spent on meetings, design, and code review. The earlier promise of clear lines of service ownership is compromised as teams build code within each other’s services, modify each other’s data models, and even perform deployments on behalf of service owners. Networked monoliths can form, where services that appear to be independent all have to be deployed together to safely perform any change.”

And yet, “The operational benefits are too important, and it seems that there are no, or limited, alternatives”.

Are we reading the same thing?

I am confused, Uber

Ok, so let’s go back a few paragraphs and check again what you think about microservices…

“…few people have advocated an outright rejection of microservice architectures.”

I wouldn’t expect “a lot of people” to grok both microservices and software architecture, so that seems hardly a good rule of thumb.

“With a large, monolithic application, an organization is forced to deploy or release all of their code at once.”

There must be a middle ground between “a large monolithic application” and over 2'200 microservices.

“Each new version of an application can involve numerous changes”.

That is not bad. That means that your development team works fast.

“ Deployments become risky and time-consuming. Anyone can bring the whole system down.”

Are you telling me that, when using microservices, you cannot bring the whole system down?

Especially when “In order to build a simple feature an engineer often has to work across multiple services, all of which are owned by different individuals and teams.”

So yes, Uber (not a sponsor), I think you are wrong.

And I think that you can see my point.

Yes, Uber, I am pointing at you.

Here is the secret: your code is NOT SOLID.

It is so unSOLID that you have managed to couple a microservices deployment strategy to the point where it had the same problems as a huge monolith.

And by the way, don’t feel too bad, most of the code that I have seen (and written, at some point) is seriously not SOLID.

Now let’s talk about why you adopted microservices:

Availability Risks. A single regression within a monolithic code base can bring the whole system (in this case, all of Uber) down.

Separation of Concerns? Single Responsibility? Open/Closed? Inverted dependencies? Stable Abstractions? Common Closure? Encapsulating change?

You don’t need a deployment strategy for that, you need SOLID.

Risky, expensive deployments. These were painful and time consuming to perform with the frequent need for rollbacks.

I am ready to bet that the problem was caused mostly by having too many dependencies… but having too many dependencies is not solved by cutting your system into tiny pieces.

Poor separation of concerns. It was difficult to maintain good separations of concerns with a huge code base. In an exponential growth environment, expediency sometimes led to poor boundaries between logic and components.

TDD is actually faster even in the short run.

And breaking your code into little pieces does not make it SOLID.

Just saying.

Inefficient execution. These issues combined made it difficult for teams to execute autonomously or independently.

And you made the execution more efficient by breaking it down into over 2'000 independently maintained but deeply cohesive pieces?

The problem is not the size of your services, it’s the high coupling and low cohesion of your components.

Yes, having microservices allows you to use different languages, but that in itself is not a bonus. Don’t get me wrong, it is super cool, but not really a source of value. And you could also use all sorts of different technologies within one system without having to break it down into smaller parts.

Your computer can interact with lots of different devices and it does not need to be broken into “thousands of little independent but highly coupled pieces” for that, it just needs a USB port.

This is not to say that breaking down components into smaller subcomponents and even making them into services is a bad thing. It is not. But it should depend on the needs of every boundary.

Breaking down a component when it makes sense is not wrong, but making that kind of decision in bulk and in advance by deciding to “use a microservices architecture” is (very wrong).

And I haven’t even got to the actual “Domain-Oriented Microservice Architecture” part yet. And I won’t have to, because I found the founding stone of your mistake:

“For organizations that have already adopted a microservice architecture there is no turning back.”

First, let me remind you that “microservice” is a deployment strategy, not an architecture.

Now let me ask you a question: couldn’t you take 2 of your over 2'000 services and make them into 1 bigger, well-defined, cohesive, and decoupled service? Could you do that more than once? With more than 2 microservices at the same time?

Could you keep iterating like that until you found a good balance between the size of your services and the maintainability of your system?

Could you try to do it in a small isolated part of the system first to try if it works? (it will)

Did you know that you can deploy and release plugins without even having to stop your core applications?

Now let me ask you, do you really think that “For organizations that have already adopted a microservice architecture there is no turning back.”?

We went to the moon with 4KB of RAM and a 32KB hard disk! And you are telling me that there is no turning back from microservices?

Yes, trying to have one big monolith is bad, but so is breaking your code into over 2'000 pieces. And the problem is not a size problem, it’s that you are focusing on the wrong thing. If you know what I mean 😏.

You are focusing on the details of implementation and deployment, and you are actually forgetting about the Architecture, even when you kind of know that architecture is important (and you try to apply it to your monolithic microservices system).

DOMA is definitely a step forward (for Uber), but I think it is just not radical enough in its objectives. It’s too timid.

The M in DOMA is the biggest problem.

It is actually awesome that you are starting to be more deliberate about your boundaries and more conscious about your dependencies (any time is a good time to start), but you are gold-hammering the s**** out of architecture by trying to make it fit into your deployment strategy, instead of doing it the other way around.

Architecture is about usage, not about implementation. And you kind of got a glimpse of that already.

So Uber, don’t take this too harshly. You are a big grown-up company already and I know that you can handle criticism. And I want to believe that criticism is actually what you published your paper for in the first place, so here it is.

Think about it.

You already found the right path to agile scalability, but you were maybe too afraid to let go of your old ways.

Don’t do that, Uber.

I like you. And I would love to see you find your way out of your growing pains.

Anyways, I’ll call you next time I need to quickly and comfortably get somewhere.

Just remember: do not mistake the map for the territory.

🎤

--

--