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.