Monolith First (2015)
martinfowler.com
martinfowler.com
Where I do push, though (and this often surprises people because "what does a devops person know about code architecture"--the answer is "a lot, both from writing it and seeing it badly written"), is hard demands of app statelessness and an encouragement of business logic internals that are functional in nature, with I/O, wireups, etc. handled at the outer edges of the application. Functional-core-imperative-shell lends itself to decomposition later if you need services (and I say "need services" because the size of the set of companies that "need microservices" is within epsilon of zero)--you replace RPC with a network layer because you built your app with clean, bright-line divisions.
Rule of thumb: if you have two "service" talking to the same set of data in the same general-purpose datastore (i.e., not pub-sub, not opposite ends of a job queue), they're the same service.
Why? The whole point of a general-purpose database is to allow multiple applications to use the same data. Consider an online ordering system for merchandise. There's an service for checking product availability. There's a service for building a shopping cart. There's checkout and payment, the only one that faces the outside world that needs high security. There's customer order tracking. On the back end, there are various services which deal with fulfillment, shipping, accounting, and reordering. There may also be customer-relationship systems which can read order data for marketing purposes. Each of those functions can be worked on independently.
Let's ammend this: so teams in large organizations +{that lack both a coherent architecture and the ability to devise one} can do independent releases +{by adoptiong the no-architecture architecture}.
Could be simplified to just "teams.
These days from what I can see teams in organizations with no ability to devise a sane architecture usually take the microservice route. Management loves this new buzzword. To them it means problems go away.
After rearchitecting around services, pieces could be restarted and upgraded independently. As a customer, we didn't notice differences in the UI (e.g. the file storage on AWS).
Epilogue: They had a shared login system, multi tenant and all... which they recently replaced by a third party. Proof that services are replaceable, but also an acknowledgment that simple critical services can be hard at scale.
I personally believe that Atlassian switched to services at the right time, when it's hard to coordinate teams of thousands working on a dozen products, and when the monolithic approach was way past refactoring date ;)
Yes, they did that!
I wouldn't look to Atlassian for anything regarding engineering practices.
I don't understand how you engineer even a monolith like that in the first place.
Disclaimer: I'm the main developer behind Signum Framework
The final cherry is that it uses zero joins, it's all done in code.
It's going to be a bastard to unfuck that mess.
Are there any examples of things architected in that way?
Doesn't it also run into the problem of needing to version the events that you broadcast?
[0] https://martinfowler.com/eaaDev/EventSourcing.html
If your services are completely separable, this approach is great. Otherwise, monolith it.
also, what happens down the line when you suddenly need a service which is contingent on the data stored in multiple other services? do you add another synchronisation layer over the top?
The downside is you have a potentially hard scalability ceiling, and you have coupling of every downstream service to the schema which means all teams have to coordinate with the DB team for schema changes.
I think startups now always go the vertical services route without thinking too hard about it because A) they all aspire to be Amazon/Facebook/Google even though 99.99% will never face that scale and B) resume-driven development.
Hopefully that parses as English, it's still morning for me.
Which 99.9% of projects will never ever hit, and those that do will by that point have become such an extreme scale that significant reengineering should be something that's happening anyway.
I couldn't agree more with your last paragraph.
It is far more likely that a company gets a "small", stable user base that provides enough income, than it is to become the next Google.
It doesn't make sense to apply all the complexity for large-scale problems when your user base isn't even small-scale. And if your monolith is structured in a remotely sane way, you can introduce microservices (or whatever will be best practice) later on.
On the other hand, it doesn't hurd to write your code with performance and scalability in mind. Neglecting this too hard may result in bad performance even for the first 10 users. But that's more in the sense of "keep it simple enough so it can be easily restructured later on". And in the sense of "measure performance to know your hot spots" ... and concentrate you efforts there.
Knowledgeable and experienced people know that there is no free lunch, but not every opiniated voice isn't necessarily so, and refuting bullshit takes ten times more effort than producing it, so objections might be handwaved away as backwardness if there is a strong force for a modern architecture.
I mean, more of the same old shit isn't exactly an easy sell either, even if every old bad choice could be rationalized at the time...
Absolutely. But the architectural choices you make for making the first 1000 users fast are often the complete opposite of ones you'd have to make to make it "scale" to 100k users.
Service/microservice architectures that I've seen are, from an absolute point of view, hideously inefficient, simply because the component that needs data isn't able to ask for the data from the other component (way across the other side of the architecture) in a precise enough fashion. Cue reams of data being sent to you that you don't need because you need to figure out one small aspect of it. And of course, jsonifying it all and de-jsonifying it a few times for good measure. All this with no transaction safety.
Now, if you're operating at the kind of scale where you know that these specific calls are now part of your business critical requirements, you can spend the effort optimizing the hell out of them and reap the gain of horizontal scalability. Of course, that comes at the expense of flexibility.
To be fair, until companies are willing to pay a premium (1) for working on bad-for-resume projects, this is very rational behavior on the part of the technical team.
1) That is, job listings can't have merely "competitive" or "market rate" salary expectations.
There could also be separate order, invoice, and delivery.
Essentially your question is: what if my models are highly overloaded? Won't they need to be used in multiple places?
And of course the answer is yes. But if you are using the same model in dramatically different contexts you probably have a problem of another sort. This is the dreaded "One Object To Rule Them All" OO anti-pattern.
Key aspect when you design the monolith is not building a spaghetti monster. If X doesn't need to talk to Y, put a wall there (package level)
Upvoted you, but I disagree with a little. "Microservices" tend to pathological cases, but you 100% can segment a monolithic apps into services. Each module in your application has an interface. Behind that interface, replace it with network calls that fulfill the interface's contracts. (If your monolith doesn't have sections that encapsulate work--well...kinda did it to yourself, but that IME is rare.)
We tend to go for throwing related web front and and API endpoints into the same ball of mud silo and back end that directly with the storage and cache services. We scale those silos up and partition across tenants too. I think a couple of the silos are around the 1 million LoC size now as well.
Just like that?!
You can encapsulate work fine, without making microservices at all easy to retrofit. There's a big difference between calling a function that does something and returns a result, and calling a function that does nothing until you've gone back to the main loop to handle network input.
(You can confront many of these issues by implementing your services as threads from day 1. Decoupling request and response is a major issue, and this will force you to do that straight away. The other issues associated with moving from single process to multiple processes are fairly minor by comparison.)
the difference is latency, so of course you put extreme low-latency operations inside the same service.
Which is not at all to say that you're wrong - a long delay in the capability to release something can definitely be an earlier and more severe death knell than friction when trying to grow.
For me personally, I like to see all of: tools that aim to make it easier to modularize from the start, tools that aim to make it easier to modularize an incumbent monolith, and tools that make monoliths work better themselves. I think all of these solve real problems for different teams at different stages of growth and facing different trade offs.
The right way to do it is to write a modular monolith and maintain a loose plan on how to break it up later. Admittedly this is difficult in SV's youth-obsessed culture where "senior" devs have 5 years experience, and staying at a company long enough to understand the long-term repercussions of architectural choices is seen as a black mark on your resume, but on the bright side languages like Elixir/Erlang help you do the right thing more naturally than more traditional languages.
In my experience refactoring microservices only takes a long time if the microservices are badly done, just like refactoring a monolith only takes a long time if the monolith is a horrible piece of spaghetti code.
If refactoring requires modifying multiple microservices then the microservice boundaries and contracts were badly designed at the start. When you have practice drawing boundaries well then refactoring with microservices becomes very fast, as you only have to modify the code of one service and redeploy it.
I think too many microservice enthusiasts have a tendency to claim monoliths always turn into spaghetti code, and monolith enthusiasts claim microservices are always slower and hard to refactor. This is just a sign that neither group of architecture fans knows how to do the other architecture properly, because its possible to build microservices fast and refactor them quickly just like its possible to build a monolith that is clean and scalable.
I had to laugh at that...
And then throw up just a little.
If you have a team that can't make a monolith scale because of kludges, that says more about the team than the monoliths.
Different architectural choices lead to different challenges. Different teams in different organizations will rise to those challenges with different degrees of success. But it's still worth thinking about the trade offs of the different architectures from the perspective of what they make easier and harder.
"Micro" means "one millionth", so it's easy to get the impression that you should have at least thousands of "microservices".
In reality, if each of your microservices don't fill up at least dozens of heavily loaded servers, you're better off with a monolith architecture.
Microservices allow and require low coupling in the organization. If you want to reduce coupling in your org, you'll be well served by microservices. If you want tight collaboration in your org, you'll be well served by a monolith. As orgs grow into multiple independently executing units, a monolith starts to limit the ability to independently execute.
During the early stages where there is maybe 1-2 people coding, microservices add a lot of unneeded overhead and orchestration.
Yes, microservices adds overhead in tooling. You have to measure carefully the trade-offs. It only pays off in a way larger scale than most of the organizations.
Keeping a monolith's concerns cleanly segregated does require a small amount of discipline.
Stopping someone working in one area from deciding they just don't like the look of the other is a benefit or all its own.
This is a dangerous ideology. Process separation doesn't enforce loose coupling, it simply makes the pain of tight coupling between them that much worse.
I worked on a "microservice" system once where I regularly had to debug a problem across four different service boundaries and two different languages. It felt like I was using boxing gloves to perform surgery on a rube goldberg toy.
This is a statement about an existing organization and the system they design. It doesn't say you can design a system and then the organization of your choice forms around it.
Bezos had to start with a management edict about how the organization is structured (by threat of firing) to get the SOA system he desired at Amazon. https://plus.google.com/+RipRowan/posts/eVeouesvaVX
I think both systems and org structure are iterative and evolving. A mismatch between the two will cause friction. Change the org structure and the monolith starts looking like independent services except without the conveniences of microservices (deployment, isolation, independent scaling, etc). Try to change the monolith without changing the org structure and your microservices will eventually turn into tightly coupled RPC hell.
Sometimes that friction prompts change in the org, sometimes it prompts change in the code.
Most individual computers combine seamlessly interoperating software that was, ultimately, written by tens if not hundreds of thousands of people, most of whom do not know one another - all the way from the kernel to the highest level scripting languages.
I think you're right microservices are primarily about Conway's law. I just don't think it's about operational effectiveness (it actually makes operational effectiveness harder, IMO).
I think it's primarily successful in large orgs because it minimizes finger pointing by establishing tighter loci of responsibility.
My rule of thumb is "could this service be spun off as a separate business?". E.g. postcode->address service, sure, microservice that. Image manipulation service->maybe yes, maybe no, "user" service-> no, price calculation service->no
Curious: why would a "user" service not be viable as a separate business? There's always space for one more authentication option.
When I've done this in the past I had a key goal: reliability. The cost was about 10x the development effort of the monolith in order to add an extra 9 to the reliability. The monolith was wonderful for getting up and running quickly as a business solution but it actually crippled the business because they had failed to identify how essential reliability was. KYC.
Personally I've come to the conclusion that the main benefits of SOA/MSA are not necessarily technical but more organisational/sociological. Having distinct silos of activity/responsibility, separate teams and communications channels; all can make a large project more manageable than the monolith by allowing the lower level problems to be abstracted away (from a management perspective).
There are plenty of industries where solid reliability is a hard requirement.
If companies got heavy fines like in other industries for selling broken products, the scenario would be quite different.
It also has the effect of making small projects large even if they never needed to be in the first place (which of course looks great on a resume)
This line of thought reaches two decades ago and was expressed in a wonderful essay "Big Ball of Mud" http://www.laputan.org/mud/
EDIT: updated with the quote
The developers I know who can't think about clear interfaces are also the ones that won't know how to write clear RPC, and won't be able to create clean microservices.
It's a very nice and natural way to write code to do it all horribly dirty and only when a sizeable portion if ready to start cleaning it up and making it look and read good.
Both are basically "good comes from evolving/refining bad".
https://docs.google.com/presentation/d/105ZgwafitwXH6_sWevFH...
E.g. if you have 500 employees having 100 microservices is fine. If you have 3 engineers and try to have 20 microservices you are wasting tons of time, you should do monolith.
All this said, I do sometimes wish we had started with a monolith, if only because we paid the microservices tax in deployment and infrastructure maintenance way too early, long before we had the scale to warrant it. I feel starting with a monolith would have probably meant more progress in less time, though with a risk of not being able to refactor smoothly when the time came.
Overall a hard call to make, since I'm happy with the result, but wonder about the pain it took to get here, and at the same time counterfactual universes are hard to instantiate...
And, I've found that integration tests help keep my sanity if a refactor occurs anywhere. Any learning(s) for adapting to refactors across services?
I think if you design fault tolerant micro service based services with something like the erlang BEAM VM, things will workout well, since you're being very careful about message passing from the beginning.
Why should we believe his statements about microservices?
Personally, my experience in microservices vs monolith has been as follows:
If your system needs fast update with quick rollout of new features, monolith is probably superior. Being able to touch everything quickly and redeploy is generally quicker in a monolith.
If your system needs to be able to survive component latency/failure, microservices are probably superior. You will have hard separation that enables testability from the beginning.
Overall, I find the monolith vs microservices debate insipid. We have LOTS of counterexamples. Practically everybody writing Erlang laughs at people building a monolith.
Sadly, Chrysler was bought[1] out in 1998 and the project was canceled in the year 2000 for unknown reasons. 1999 when it was intended to be fully deployed, Crystler was in the middle of a reorganization, which would lead to layoffs of more than 21,000 people in the next 3 years.
I'd guess organizational politics had more to do with the cancellation than anything the development team did wrong.
[1] Oh, sure it was labeled "a merger of equals", but consider that the share price fell to 50% within a year afterward and Crystler was eventually split off and sold... it just makes me think of the Time Warner + AOL merger.
To be fair, he has an axe to grind, but C3 was quite far from a success.
To be fair, big projects fail. A lot. For many reasons. So, I'm not going to blame the people involved.
And I find Fowler to actually be probably the most level-headed of the bunch to come out of that project.
However, I WILL apply requisite amounts of skepticism when those same people start peddling their "expert knowledge" about subject matter in which they provably didn't "beat the average".
The only real lesson I feel comfortable drawing from that is that we need more diverse case-studies than C3 because very few other projects are going to have the same environment with a huge company, multi-divisional politics and then a merger happening shortly into the project, one of the best defined problems, etc.
You can do monoliths bad. You can do microservices bad. You can spend too much time architecting and not enough time understanding where the practical design considerations lie.
My philosophy is to build with the intention of delegating. If you can communicate how your software works well enough that someone can take over maintenance, then you've done your job. Better if you can communicate it well enough that you can delegate it to separate groups with differing responsibilities.
Any system can get complex to the point where it's dangerous to make changes.
I think solutions that are a hybrid of Monolith and Microservices work out well. As another person pointed out.. this can be fairly easily achieved by having a monolith with multiple sub-projects to get separation of concerns. The code is all in 1 place so it's easier to design and refactor. You can also deploy different sub projects as microservices if you need to later on. So it's basically having a monolith with separately deplorable sub-components.
Once boundaries are clearly understand, it can then be easier to physically separate services
I wholeheartedly disagree with this point.
I've found that if I build monolith first, it becomes harder to draw the line of how to separate endpoints, services, and code within the system(s).
If I design in a "microservice first," or just a service oriented design -- I find that there is much more clarity in system design. In terms of exposing parts of a system, I find that the microservice first approach makes me consider future optimizations, such as caching policies, whereas, in a monolith, I would proceed in a naive, "I'll figure it out later" approach.
Each school of thought has its downsides. Monoliths move fast and abstracting parts of the system later that arise as bottlenecks is a tried and true pattern; however, there aren't too many product / business folks who want to hear:
"Hey, we just built this great MVP for you. It probably won't handle significant load, so we're going to go off in a corner and make it do that now. Oh yeah, we won't have time to develop new features because we'll be too busy migrating tests and writing the ones we didn't write in the beginning."
The flip side is, microservice first has a lot of overhead, and (as things evolve in one system) refactoring can be extremely painful. This is an okay trade off where I'm at... for others, maybe not so much.
Monolithic applications don't encourage naive approaches; your conscious and unconscious choices around desired development rigorousness do. If those choices are causing you problems, there are much easier ways to prevent yourself from doing it than introducing network boundaries.
I don't disagree at all. As with most things in life, throwing money at the problem will solve this. In a lot of positions I've been in, this cost becomes "the cost of doing business" that everyone assumes rather than, "we can do better, but for now this is our reality."
> Monolithic applications don't encourage naive approaches;
Perhaps, naive is a poor choice of words. Maybe, an example is better:
You develop a large system that's going to support an entire state's childrens meal plans. You can easily do this in a monolithic architecture, but is it worth investing time to save on peeling apart the obvious points of high use now?
Children / parents only have to sign up once, but they need access to their meal plans everyday. I see two services here in this simple example, and if I'm strapped for time -- I defer a user management service to some sort of SAAS / PAAS so I don't have to deal with that overhead.
I see a great deal of value to everyone in investing in the optimizations that have high return, but minding Knuth's rhetoric that "premature optimization is the root of all evil."
Your example is an emoji shrug, because I use Passport, a Postgres table, and a user token in a cookie and I'm done. It's not a big enough problem to break out across a network boundary because it's just code that may or may not be hit on any given server and I totally don't care if it is or not because a given node not running a subroutine costs nothing. It's trivial; don't complicate it.
This goes to his point, the time spent considering these points is time gained in delivering an MVP to market.
> "Hey, we just built this great MVP for you. It probably won't handle significant load, so we're going to go off in a corner and make it do that now. Oh yeah, we won't have time to develop new features because we'll be too busy migrating tests and writing the ones we didn't write in the beginning."
You don't need that level of scaling potential in an MVP, and you get to have something out -- in time. While if you had started with a microservices approach you would still be off in that corner trying to get something usable out. Now you have a product in the hands of users, and your refactoring can consider that feedback.
In undiluted markets where you're entering 'new territory' -- sure. Get a product in user's hands and let it evolve how it should. There are different scenarios where companies already have a user base and you have to be able to respond to that load within your MVP.
Referring to what I said in my original comment, there are always trade-offs, and monoliths have their place. If you have the luxury of time and money, I would say that a monolith is less than ideal.
> You don't need that level of scaling potential in an MVP, and you get to have something out -- in time. While if you had started with a microservices approach you would still be off in that corner trying to get something usable out. Now you have a product in the hands of users, and your refactoring can consider that feedback.
Don't disagree with this at all. Context of your market probably dictates whether you'll be in a corner or still figuring out service boundaries.
Please define significant load.
Some would consider it light, but I work alongside a company that is currently struggling to get beyond that mark.
This is why I hate this subject. People use terms and don't define them. If you think microservices is the only way to scale past 30 requests per second you're extremely wrong.
If you need to. You probably don't, and you definitely don't if you're struggling with 30 reqs/second. This isn't just YAGNI. This is YAHYBDI. You Are Hurting Yourself By Doing It. You need to write better code and examine the assumptions that have created the mess you're dealing with.
I get that microservices are trite, and most people think they need them long before necessary; however, they have uses beyond scale.
I'm sure that there are ways to mitigate every point I can make within monoliths.
My points are just opinions.
This is what I do for a living, and I am regularly but-but-microserviced by people who are equally ignorant of competent application design and who think that breaking it into HTTP-intermediated chunks will solve that they are choosing to write bad code. That segmentation doesn't--it does nothing. It isn't YAGNI, but YAHYBDI, and I'll get hot under the T-shirt occasionally 'cause people who read discussions like this will get the wrong idea and stick their hands into the saw, too.
I could get paid more by letting people mangle themselves, but it'd be mean.
There's the occasional case where a single language won't fit the bill, but there's a big difference between 2 services and 200.
(Note: I'm not saying microservices are bad – I just think that the process which lead to that ball of mud will unfold similarly with a different methodology)
Why is this though? There is nothing stopping you from thinking about architecture in a monolith, or deploying a monolithic code base to different server classes to optimize workloads.
Where microservices really come into their own is when you want to scale and decouple your engineering teams. At that point, the effort of defining and maintaining "public" interfaces between services pays dividends by providing a defacto specification that serves as a talking point between teams who literally do not have to know the inside of the others' black box. If everyone has to know the internals of multiple microservices, then why are you paying for that overhead instead of an internal method call that has all the benefits and assurances that a language can give in a single process rather than whatever pale imitation you get through RPC.
I'll concede that it depends a lot on the problem domain. Perhaps the service boundaries are obvious, the interfaces stable, and so you can easily reap the benefits without a lot of refactoring. Okay, that's a possibility. But in most cases I have to agree with Martin Fowler that when you embark on a new project you just don't know enough about the requirements to make that call. Unless you've already built the thing you're about to build, I think you very rarely will have the prescience to design the service boundaries correctly on the first go.
> I don't feel I have enough anecdotes yet to get a firm handle on how to decide whether to use a monolith-first strategy.
after linking and mentioning points of a guest post [1] (with which I strongly agree) which argues against starting with a monolith. A key part from that post:
> Microservices’ main benefit, in my view, is enabling parallel development by establishing a hard-to-cross boundary between different parts of your system. By doing this, you make it hard – or at least harder – to do the wrong thing: Namely, connecting parts that shouldn’t be connected, and coupling those that need to be connected too tightly. In theory, you don’t need microservices for this if you simply have the discipline to follow clear rules and establish clear boundaries within your monolithic application; in practice, I’ve found this to be the case only very rarely.
[1] https://martinfowler.com/articles/dont-start-monolith.html
This should be easy to achieve in most "object-oriented" languages (like Ruby; a Rails monolith should have no problem being structured this way, even if quite a few of the ones I've seen in the wild seem to forego this). Erlang (and Elixir by descent) is also well-suited to this, since you can break your application into a collection of processes that - whether individually or in combination with other processes - can act like their own little microservices.
Six separate services allowed multiple benefits:
1.) each service was smaller and faster
2.) each service was focused on the real needs of its users
3.) each service was free to evolve without harming the people who did not use the service
There was some duplication of code, which suggests a process that is the exact opposite of "Monolith First":
Start with separate services for each group of users, then later look to combine redundant code into some shared libraries.
I created a simple rule long time ago: <insert name of a "thought leader" here> last.
Nobody says they want to waste money building bad software. The problem is that the factors which ensure it are political and hard to change. It's easy to forget that since we tend to focus on the visible technical aspects but they're almost always a reflection of the environment.
Development processes and software architecture follow from business process and architecture... it's hard to be agile and develop services with clean separation of responsibilities when business insists on monolithic hairball project reqs with fixed deadlines.
(aka Conway's Law: https://en.wikipedia.org/wiki/Conway%27s_law )
For one thing, I've seen each generation of new managers forget or ignore the knowledge hard-won by their predecessors or the software body of knowledge. Oh, and ignore their current experts, too. I think it's a bug in the human wetware because it happens so often.
For another, it was explained to me when consulting at $giantcorp that sometimes - for example, regulatory compliance or competitive edge - it's more important to get a shitty monolith out there, bugs and all, by a drop-dead date than to save in the long run by doing a good job.
And as long as there are people out there willing to work for low wages fixing or rewriting the pile of crap, and it's profitable for $company, the practice will continue.
Or until someone can prove, with cost measurements on multiple large-scale projects implementing the same requirements, that the ROI - in bottom line $ terms - on a well-engineered system is much greater than the crappy equivalent.
I can't see that happening because of all the variables involved (team, skill, chance, cost, variance in interpretation of requirements, and so on), plus, who would pay for that?
EDIT - typo.
Build something useful, fast. Then refactor. Write tests when refactoring or fixing a bug, but not when prototyping.
Don't underestimate how hard it can be for developers to accept this. The hacker community can become a tedious game of one-upsmanship at times, and it's way too easy to slip into "imposter syndrome" mode. Often, the people barking loudest about the newest ideas have slipped themselves, and are just trying not to appear clueless.
But cluelessness is fine. It's the default state of being, we all need to be comfortable (if not satisfied) with it.
I don't want to suggest that Martin Fowler is clueless, of course. He has described many prudent battle-tested techniques that can be absolutely essential in context. If you haven't seen his article on Collection Pipelines, it's relevant to all kinds of modern programming: https://martinfowler.com/articles/collection-pipeline/