top | item 26190584

Monolith First (2015)

820 points| asyrafql | 5 years ago |martinfowler.com

340 comments

order
[+] ser0|5 years ago|reply
A more common scenario I see is that people start with a monolith that ends up inheriting all the conceptual debt that accumulates as a project evolves. All this debt builds up a great desire for change in the maintaining team.

A champion will rise with a clean architecture and design in microservice form that addresses all high visibility pain points, attributing forecasted benefits to the perceived strengths of microservices. The team buys into the pitch and looks forwards to a happily-ever-after ending.

The reality though is that the team now has multiple problems, which include:

- Addressing conceptual debt that hasn't gone away. - Discovering and migrating what the legacy system got right, which is often not documented and not obvious. - Dealing with the overheads of microservices that were not advertised and not prominent at a proof-of-concept scale. - Ensuring business continuity while this piece of work goes on.

I would propose alternative is to fix your monolith first. If the team can't rewrite their ball of mud as a new monolith, then what are the chances of successfully rewriting and changing architecture?

Once there is a good, well functioning monolith, shift a subset of responsibility that can be delegated to a dedicated team - the key point is to respect Conway's law - and either create a microservice from it or build a new independent monolith service, which aligns more to service oriented architecture than microservices.

[+] lmilcin|5 years ago|reply
Hahaha. I have been saying the same for years but have either been punished by my managers or mercilessly downvoted on HN.

Somehow mentioning that microservices might not be perfect solution in every case triggers a lot of people.

I have actually helped save at least one project in a huge bank which got rolled from 140 services into one. The team got also scaled down to third of its size but was able to work on this monolithic application way more efficiently. Also reliability, which was huge issue before the change, improved dramatically.

When I joined, I have observed 6 consecutive failed deployments. Each took entire week to prepare and entire weekend to execute (with something like 40 people on bridge call).

When I left I have observed 50 consecutive successful deployments, each requiring 1h to prepare (basically meeting to discuss and approve the change) and 2h of a single engineer to prepare and execute using automation.

Most projects absolutely don't need microservices.

Breaking anything apart brings inefficiencies of having to manage multiple things. Your people now spend time managing applications rather than writing actual business logic. You have to have really mature process to bring those inefficiencies down.

If you want to "do microservices" you have to precisely know what kind of benefits you are after. Because the benefits better be higher than the costs or you are just sabotaging your project.

There are actually ways to manage huge monolithic application that don't require each team to have their own repository, ci/cd, binary, etc.

How do you think things like Excel or PhotoShop have been developed? It is certainly too large for a single team to handle.

[+] Mandatum|5 years ago|reply
I think my biggest gripe with orgs that adopt microservices is they don't build out any of the testing, CI/CD, monitoring and debugging workflows to support it. It goes from shitty, slow monolithic application that super-pro can debug in a few minutes to.. Slow, shitty disparate services that are managed by different teams who don't share anything, suddenly you've got a Cuckoo's Egg situation where 1 guy needs to get access to all the things to find out what the fuck is happening. Or you just accept it's shitty and slow, and pay a consultancy to rebuild it in New Thing 2.0 in 8 years when accounting forget about the last 3 rebuilds.
[+] sublimefire|5 years ago|reply
LOL I'm in talks of migrating microservices into a monolith purely because there is so much overhead at managing it. You need at least a couple of people to keep it in place. And the latter means the company is prepared to kill the product even when there are actual customers plus new ones are coming.

Microservices make sense when you have millions of users and there is a need to quickly scale horizontally. Or when you have a zillion of developers which probably means that your product is huge. Or when you are building a global service from the get go and get funded by a VC.

[+] obviouslynotme|5 years ago|reply
I can understand Résumé-Driven Development. Our industry is famous for its "Must have (X+2) years of (X year old technology)" job requirements.
[+] delecti|5 years ago|reply
A sister team at work (same manager, separate sprints) split a reasonable component into three microservices. One with business logic, one which talks to an external service, and one which presents an interface to outside clients. They then came to own the external service as well. So now they have 4 micro services when one or two would have been plenty. I don't hesitate to poke fun when that inevitably brings them frustration. Another team we work with has their infrastructure split across a half-dozen or more microservices, and then they use those names in design discussions as though we should have any idea which sub component they're talking about. Microservices are a tool that fits some situations, but they can be used so irresponsibly.
[+] measure2xcut1x|5 years ago|reply
> There are actually ways to manage huge monolithic application that don't require each team to have their own repository, ci/cd, binary, etc.

Would be interested to hear about some of these.

[+] ex_amazon_sde|5 years ago|reply
> I have been saying the same for years but have either been punished by my managers or mercilessly downvoted on HN.

Ex Amazon SDE here. I've been saying many times that Amazon tends to have the right granularity of services: roughly 1 or 2 services for a team.

A team that maintains the service in production autonomously and deploys updates without having to sync up with other teams.

[Disclaimer: I'm not talking about AWS]

[+] MattyMc|5 years ago|reply
I don't think you can downvote on HackerNews. That's reddit.
[+] kingdomcome50|5 years ago|reply
The problem with creating a dichotomy between "monolith" and "microservices" is that... well... it doesn't make any sense. You describe your "microservices" and I'll explain how it's really just a monolith.

"So let me get this straight, you have a Billing package and an Ordering package. How are these organized again?"

-> "We have a distributed system."

"So you you compile them together and deploy them horizontally across many machines? Like a distributed monolith?"

-> "No we use microservices. The two packages are compiled and deployed separately so there is no code dependencies between them."

"Okay. So if I understand this right, you develop each package separately but they both access the same database?"

-> "No no no no! Microservices are not just about code dependency management, they are also about data ownership. Each package is developed and deployed separately and also manages it own database."

"Ah, so you have two monoliths?"

-> "..."

You see the problem is that the above terms are too broad to describe anything meaningful about a system. And usually "monolith" is used to described the dependencies between code whereas "microservices" is used to describe dependencies about data. It turns out there is a lot of overlap.

Design and implement your systems according to your use-case and stop worrying about how to label them.

[+] rbobby|5 years ago|reply
> "Ah, so you have two monoliths?"

Made me snort.

[+] mstipetic|5 years ago|reply
I keep saying this every time this topic comes up, have a look at elixir/erlang. The OTP library gives some great reusable patterns (and elixir project is adding new ones[1]) out of the box. You're basically developing a single codebase microservice project which feels like a monolith. You can spread out on multiple machines easily if you need to, the introspection tooling is better than anything you can buy right now, it's amazing.

Things can get tricky if you need to spread out to hundreds of machines, but 99%+ of projects wont get to that scale.

[1] https://elixir-lang.org/blog/2016/07/14/announcing-genstage/

[+] runeks|5 years ago|reply
How does versioning work? Is it required that all VM instances run the same version?
[+] trabant00|5 years ago|reply
I see services (not micro) as an organizational solution more than a technical one. Multiple teams working on the same service tend to have coordination problems. From this perspective starting with a monolith as you start with one team seems natural.

Now the trend of breaking things up for the sake of it - going micro - seems to benefit the cloud, consultancy and solution providers more than anybody else. Orchestrating infrastructure, deployments, dependencies, monitoring, logging, etc, goes from "scp .tar.gz" to rocket science fast as the number of services grows to tens and hundreds.

In the end the only way to truly simplify a product's development and maintenance is to reduce its scope. Moving complexity from inside the code to the pipelines solves nothing.

[+] 100-xyz|5 years ago|reply
I work for a small company that uses microservices architecture. The product is simple where a user registers, enters preferences, selects from courses and submits a form. A monolith would be doable with 5 good engineers. Instead we have about 50 engineers+QA releasing bug filled code.

The company's design philosophy is based more on what is fashionable that what is suitable.

[+] ngngngng|5 years ago|reply
With this in mind, if I were to start a new project today (using Go, which is all I use anymore). I would segment my code inside of a monolith using interfaces. This would incur a very slight initial cost in the organization of the codebase prior to building. As your usage goes up and the need to scale parts of it increases, you could swap these interfaces out little by little by reimplementing the interface to contact your new external microservice.

I've worked at several companies where microservices were necessary, and I can't believe how clunky they are to work with. I feel like in 2021 I should be able to write a monolith that is capable of horizontally scaling parts of itself.

[+] foreigner|5 years ago|reply
I'm embarrassed to say that I recently built microservices first, and am now kicking myself as I merge them back in to a monolith.
[+] im3w1l|5 years ago|reply
If you don't mind telling this story, why did you end up merging them back in? I would have thought that needlessly making microservices is bad but not bad enough to justify merging back.
[+] spectramax|5 years ago|reply
You can still build a "monolith" but in a very modular way. Can't scale independently like microservices, but what if you don't need scale! Compile times are higher but what if you don't need to compile that much code! One error can bring down all "monolith services" but what if your app is not that big!

I'd wager, something like 90% of software projects in companies can just get by with monoliths.

You know...there are monoliths that are well architected, and then there are monoliths that are developed for job security.

[+] eerikkivistik|5 years ago|reply
The most amazing thing I saw with a microservices first approach was someone building a betting system with microservices. So if you made a bet, one microservice would make an http call to another to request your balance, make a decision based on that and then move the funds. No locks, no transactions, no nothing. I wasn't sure whether to laugh or cry. Fortunately I got there in time before this thing was launched...
[+] bernawil|5 years ago|reply
But how monolithic were our monoliths anyway? Take your bog-standard ecommerce solution, built with a LAMP stack.

Okay, we've got a php process. Let's put our business logic there.

Okay, I need a database. With current cloud practices, that's probably going to be deployed in a different machine. We've got our business logic service and our datastore separated by a network bound from the get go.

Okay this is not very performant, I need to cache some data. Let's add a redis instance in our cloud provider. I guess this we'll be our cache service.

Okay we need to do full-text search, let's add an elasticsearch cluster through our cloud provider.

Okay I need to store users. We can built that into our business logic core, of course, but screw that. We are using Auth0. I guess our "user service" is distributed now. Okay, my php process can't keep up with all this requests, let's scale this horizontally by deploying multiple php processes behind a load balancer!

Okay, now I need to do some batch processing after hours. I could add a cron job for that, but I don't want to deal with everything needed for retrying/reprocessing/failure handling plus now that it's a multi-instance deployment it's not even clear which process should do this! Let's put this work in a Lambda from our cloud provider.

So until now, we had a star architecture where everything talked to our business core. But adding this lambda that will talk directly to other services without consulting the core we've lost that.

Now, stripping out the business core into its constituent parts doesn't sound so strange, does it?

[+] luhn|5 years ago|reply
Yeah, that's a terrible idea, so don't do something like that. You don't need to break up your monolith to run cronjobs.

My setup: I have async worker running alongside my application. Same codebase, same environment, same monolith, just instead of accepting HTTP requests it pops tasks off a queue. The framework handles retrying/reprocessing/failure.

To run cron tasks, I have CloudWatch Events -> SNS -> HTTP endpoint in the monolith which pushes the cron task into the queue.

And this isn't some unusual setup. This is something that people have been dealing with for a very long time, long before Lambda came into existence. Most web frameworks have async tasks built-in or an integration with a framework that can do async tasks.

[+] hcarvalhoalves|5 years ago|reply
IMO this is an unnecessary dichotomy that we're currently forced to deal with, because we don't yet have a good solution for programming the "cloud" the same way you program a for a single computer, and that ends up leaking into all the consequent decisions of modularisation, performance, code management, deployment, team organisation, etc.

(My holy grail would be programming for distributed environments as if you had one giant single-thread computer, and function calls could arbitrarily happen over IO or not, then concerns would boil down to code organisation first. I believe Erlang's OTP model or the way some features of Google Cloud Platform are organised gets us closer to this ideal, but we're not quite there yet.)

[+] ex_amazon_sde|5 years ago|reply
> programming for distributed environments as if you had one giant single-thread computer, and function calls could arbitrarily happen over IO or not

Ex-Amazon SDE here. Message-passing libs like 0mq tried to push this idea.

They never became very popular internally because passing data between green threads vs OS threads vs processes vs hosts vs datacenters is never the same thing.

Latencies, bandwidths and probability of loss are incredibly different. Also the variance of such dimensions. (Not to mention security and legal requirements)

Furthermore, you cannot have LARGE applications autonomously move across networks without risks of cascading failures due to bottlenecks taking down whole datacenters.

Often you want to control how your application behaves on a network. This is also a reason why OTP (in Erlang or implemented in other languages) is not popular internally.

[+] Kinrany|5 years ago|reply
The difference between separate threads and separate machines should not be abstracted away, but managing them as part of the application through compile-time magic would be cool.
[+] ryanelfman|5 years ago|reply
Sorta the purpose of the Actor model but that has other complexities.
[+] matijash|5 years ago|reply
This is a great summary of the problem, upvoted.
[+] haskellandchill|5 years ago|reply
Have you seen Unison language? They are positioned as a solution to what you are describing.
[+] nojvek|5 years ago|reply
Most common architecture for SAAS apps is a frontend served from cdn + frontends served by app stores, an api server behind app load balancer (e.g nginx), a database cluster, and a bunch of async long lived cron jobs + on demand jobs.

In that architecture you don’t need that many Microservices. The api server is prolly the most important bit and can be served as a monolith.

Many 10B+ companies use this architecture. It works well and does the job. Mono/Microservices is really about separation of concerns. Separation of concerns mostly around what you want to deploy as a unit, redundancy and what should be scaled as a unit.

Agree with author. Start with one chunk, and split when you feel the pain and need that separation of concern. Fewer pieces are easier to reason about.

[+] arwhatever|5 years ago|reply
I’ve seen Separation of Concerns used as a “killer argument” in many a planning meeting, but reminder it needs to be weighed against the cost of being (un)able to test and validate the entire system atomically.
[+] bob1029|5 years ago|reply
We did the journey both ways:

Monolith -> Microservices -> Monolith

The first monolith was so bad that microservices made sense at that point in time. The current monolith is an exemplar of why we do not use microservices anymore. Everything about not fucking with wire protocols or distributed anything is 100x more productive than the alternatives.

Deciding you need to split your code base up into perfectly-isolated little boxes tells me you probably have a development team full of children who cannot work together on a cohesive software architecture that everyone can agree upon.

[+] ivanb|5 years ago|reply
There is no replacement for discipline. If you cannot structure your modules well, what makes you think that your microservice spaghetti would look better?
[+] tappio|5 years ago|reply
In my opinion it will become unusable and unmaintainable faster with microservice spaghetti. With monolith you can get by with bad design longer. But anyways, everything boils down to good structure as you said.
[+] Cthulhu_|5 years ago|reply
It's a means to shift the problem to Someone Else; I've got my codebase, it is mine and it is perfect. Integrating with it is someone else's problem.
[+] jiggawatts|5 years ago|reply
Not even just microservices. Even just unnecessary layers or tiers.

I'm working on a project right now trying to spin up the infrastructure in the public cloud for a simple application that some bored architecture astronaut decided would be better if it had an "API tier". No specific reason. It should just have tiers. Like a cake.

Did you know Azure's App Service PaaS offering introduces up to 8-15ms of latency for HTTPS API calls? I know that now. Believe, me I know that all too well.

To put things in perspective, back in the days I cut my teeth writing "4K demos" that would be added to zip files on dialup bulletin boards. In those days, I carefully weighed the pros and cons of each function call, because the overheads of pushing those registers onto the stack felt unnecessarily heavyweight for something that's used only once or maybe twice.

These days, in the era of 5 GHz CPUs with dozens of cores, developers are perfectly happy to accept RPC call overheads comparable to mechanical drive seek times.

I can hear the crunching noises now...

[+] caust1c|5 years ago|reply
I'm surprised that he doesn't mention the Monorepo + Microservice approach. I've found that most of the pain, confusion and bugs when dealing with microservice architectures has to do with the basic overhead of repo management, not the actual microservices themselves.

If you have a monorepo, and you make a change to a schema (backwards compatible or not, intentional or not), it's a lot easier to catch that quickly with a test rather than having a build pipeline pull in dozens of repos to do integration tests.

[+] mumblemumble|5 years ago|reply
I've only had one experience with monorepo + microservice, and it wasn't to my taste. As the volume of code sharing increased, the whole thing became ever more monolithic and difficult to change.

Also, if you have a bunch of services all sharing the same schema, you aren't really doing microservices. Not because of some No True Scotsman, "Best practices or GTFO," sentiment, but because that design question, "Do services only communicate over formal interfaces, or do we allow back channels?", is the defining distinction between SOA and microservices. The whole point of the microservices idea was to try and minimize or eliminate those sorts of tight coupling traps.

[+] mlthoughts2018|5 years ago|reply
The article doesn’t have any connection to monorepo / polyrepo axis. You can use either repo structure for a monolith application, or use either repo structure for separated microservices. The article is just not related to repo structure in any way.
[+] ilitirit|5 years ago|reply
There's another issue - sometimes people choose to migrate to microservices because the monolith "can't be scaled" or "is too slow" or whatever. Where often the case is just that the monolith was written poorly. But the team doesn't realise this and they and up copying the same poor design and technical processes that lead to the initial problems to the microservice refactor. Now they have the same problems with potentially higher complexity.

A problem I've seen on the project I'm working on is that the team seems to want to do conflate orthogonal issues from a design and technical perspective. "We're going to microservices because the monolith is slow and horrible. We're going to use CQRS to alleviate pressure on the repositories. We're going to use Event Sourcing because it works nicely with CQRS."

Question: "Why are we Event Sourcing this simple CRUD process?"

Answer: "Because we are moving from the Monolith to Microservices".

[+] jillesvangurp|5 years ago|reply
One issue with any form of modularization is that dependency cycles are not desirable and have the side effect of creating a need for ever more modules. The reason is that any dependency cycle can be trivially broken by adding another module.

You get dependency cycles when two dependent services need the same thing and you lack a good place to put that thing. You start with modules A and B. Then B need something that A has and then A needs something that B has. You can't do it without introducing a dependency cycle. So, you introduce C with the new thing. And A and B depend on C but B still also depends on A. And so on.

True weather you do Corba, COM, SOAP, Web RPC, OSGi, Gradle modules, etc. The only difference is the overhead of creating those modules is different and has varying levels of ceremony, management needs, etc. Also refactoring the module structure gets more complicated with some of these. And that's important because an organically grown architecture inevitably needs refactoring. And that tends to be a lot more tedious once you have micro services. And inevitably you will need to refactor. Unless you did waterfall perfectly and got the architecture and modularization right in one go. Hint: you won't.

Finally, the same kind of design principles you use for structuring your code (e.g. SOLID, keeping things cohesive, maximizing cohesiveness, Demeter's law, etc.) also applies to module design. Services with lots of outgoing dependencies are a problem. Services that do too much (low cohesiveness are a problem). Services that skip layers are a problem. The solutions are the same: refactor and change the design. Except that's harder with micro-services.

That's why Martin Fowler is right. Start with a monolith. Nothing wrong with those and should not stop you practicing good design. Using microservices actually makes it harder to do so. So, don't introduce microservices until you have to for a good technical or organizational reason (i.e. Conway's law can be a thing). But don't do it for the wrong reason of it being the hip thing to do.

[+] kpmah|5 years ago|reply
The main problem I have with the 'monolith first' advice is that it implies 'microservices later'. It feels like people have forgotten how to build good, simple, modular code.
[+] throwaway4good|5 years ago|reply
Martin. The archicture astronaut. Finally returning to earth. Landing the same spot where he took off. But wiser ...
[+] notfed|5 years ago|reply
Keep in mind this article was written in 2015.
[+] deckard1|5 years ago|reply
I was wondering why I was agreeing with this article. It's the most un-Fowler thing I've read from him.
[+] heuroci|5 years ago|reply
I was thinking the same thing. Funny how full circles come about in this industry sometimes isn't it ;)
[+] hayst4ck|5 years ago|reply
If you imagine a monolith as a service that:

  takes a request 
  -> deserializes it/unpacks it to a function call
  -> sets up the context of the function call (is the user logged in etc)
  -> calls the business logic function with the appropriate context and request parameters
  -> eventually sends requests to downstream servers/data stores to manipulate state
  -> handles errors/success
  -> formats a response and returns it
The main problem I've seen in monoliths is that there is no separation/layering between unraveling a requests context, running the business logic, making the downstream requests, and generating a response.

Breaking things down into simplified conceptual components I think there is a: request, request_context, request_handler, business_logic, downstream_client, business_response, full_response

What is the correct behavior?

  return request_handler(request):
    request -> request_context;
    business_response = business_logic(request_context, request):
      downstream_client();
      downstream_client();
    business_response -> full_response;
    return full_response;
    
  business_response = request_handler(request_context, request):
    return business_logic(request_context, request):
      downstream_client();
      downstream_client();
  business_response -> full_response;
  return full_response;  

  request -> request_context;  
  business_response = request_handler(request_context, request, downstream_client):
    return business_logic(request_context, request, downstream_client):
      downstream_client();
      downstream_client();
  business_response -> full_response;
  return full_response;  
    
  something else?
In most monoliths you will see all forms of behavior and that is the primary problem with monoliths. Invoke any client anywhere. Determine the requests context anywhere. Put business logic anywhere. Format a response anywhere. Handle errors in 20 different ways in 20 different places. Determine the request is a 403 in business logic, rather than server logic? All of a sudden your business logic knows about your server implementation. Instantiate a client to talk to your database inside of your business logic? All of a sudden your business logic is probably manipulating server state (such as invoking threads, or invoking new metrics collection clients).

The point at which a particular request is handed off to the request specific business logic is the most important border in production.

[+] ekvilibrist|5 years ago|reply
> In most monoliths you will see all forms of behavior and that is the primary problem with monoliths. Invoke any client anywhere. Determine the requests context anywhere. Put business logic anywhere. Format a response anywhere. Handle errors in 20 different ways in 20 different places.

This just seems like a poorly (or not at all?) designed monolith, if there's no standard way of doing things, of concerns or responsibilities of various application layers? I mean I've been there too in organizations, but it just seems like we're skirting around the obvious: the system should've had a better architect (or team) in charge?

[+] tappio|5 years ago|reply
I guess it all boils down to building software with modular structure rather than mixing business logic all around. I personally think that it is easier to isolate business logic with microservice structure, but you can also make a huge mess. You can also make really good modular monolith, where business logic and state is where it belongs and not spread everywhere.