Transactions certainly make it easier to maintain global consistency, but one possible contradiction with the above is that if your modules are sharing transactions, then your boundaries are no longer narrow and well defined. By definition, your entire database and all its internal workings are now part of the interface.
This is one problem that I've observed with all the monoliths I've worked on. Because modules are colocated and sharing a database is easy, eventually somebody will do it (even if it wasn't originally intended), and you get lots of ostensibly modular code intertwined with other modules in non-obvious and subtly problematic ways.
I've heard this exact argument at work in favor of microservices. I honestly think it's a lazy man's cop out to say it makes sense to insert a network boundary because it's just assumed somebody's going to violate a module boundary. That's a huge tax to pay for being lazy. If you lack discipline it's going to show up no matter how you decide to distribute your complexity. From what I'm seeing the idea that "devops" is going to offset the increased complexity of network boundaries as interfaces is going to be the downfall of a great many of microservice based implementations that simply didn't need to take that burden and risk. I suspect that a hybrid approach is going to end up being the right solution for a great many companies. One or more well factored monoliths with common shared libraries and orthogonal services that each of them use that make sense being a service vs. a shared library.
I take it you've never worked on a monolith project before. There are always reasons (often deadline related, always legitimate, never as a result of laziness or lack of discipline) where developers have been forced to cross module boundaries.
Nobody arrives at microservices unaware of their complexity and complications. Developers are forced to choose the approach between monoliths have their own issues.
Also common shared libraries are a disastrous idea IMHO. They always become riddled with stateful business logic and the difference in requirements between their consumers means they end up brittle, inelegant and full of hacks.
Actually I've worked on a great many monolith projects over the past 20 years, including a J2EE server (WebLogic). And yes that was a huge monolith that had technical debt that needed addressing. When I left there was this future "modularity" project that I'm certain didn't involve introducing network boundaries between the servlet, EJB, JCA containers, etc. to achieve that modularity. And I can tell you that there was a definite effort to enforce interface/package boundaries between the various server components. If you introduced an "illegal" dependency you were going to be talked to about removing it. I want to say that we actually had the build breaking on such infractions after I'd moved to product management.
The point I was making is exactly what you're talking about. That sometimes deadlines force bad architectural choices and that's called technical debt. The laziness and lack of discipline comes with failing to acknowledge and address that debt in the future. As best I can tell people think that microservices are going to solve that problem and I'm saying they won't and that there's not enough thought going into the price of network.
Just like in real life when it comes to being in shape. Diet and exercise. There's no silver bullet there either and it seems to me as though microservices are the fad diet of the current tech cycle.
I realize this stuff isn't cut and dried and easy. If it were then none of us would have well paying jobs to figure out when to use what tool for what job. There's a time and a place for all solutions but I'm seeing the same groupthink I saw back when everyone was purchasing Sun, Oracle, and WebLogic for sites that didn't and would never need those tools. This is EJB all over again as best I can tell.
As far as your shared libraries comment goes, you wouldn't consider having any shared libraries ever? What you're describing are permutations of a shared library that either need to be addressed by the shared library's design by adapting or splitting into multiple libraries. I'd be interested in learning what you do instead of sharing components? Duplicating everywhere?
> Nobody arrives at microservices unaware of their complexity and complications.
I think the opposite is often true. There are developers that buy into microservice architecture without a full understanding of the complexities involved.
> There are always reasons (often deadline related, always legitimate, never as a result of laziness or lack of discipline) where developers have been forced to cross module boundaries.
Those are still a lack of organisational discipline.
Your argument is, in effect: In a modular monolith it is technically feasible to violate the stated architecture. Some organisations will chose to take that option for good reasons, therefore we should make it so it is no longer technically feasible to do so.
I've seen that too. I've done that too.
But let's call it what it is: It's choosing to implement the more complex technical design in order to remove options that you don't want to be available, because if they are available then someone will override your architectural decisions for short-term commercial reasons. And you don't want them to have that choice.
So, either
(a) the organisation is prone to making short term decisions with undesirable long-term consequences, and has collectively decided that they can't trust themselves to stop, and need technical constraints in place.
or
(b) the organisation is prone to making short term decisions with long-term consequences that the technical team don't like, and the technical team has decided that since they can't convince the organisation to stop, they need to put technical constraints in place.
One more point is that in your monolith you actually have the option of crossing module boundaries. You don't have that luxury with microservices unless you want to introduce XA (God bless you). So you better get your boundaries right. :)
>I honestly think it's a lazy man's cop out to say it makes sense to insert a network boundary because it's just assumed somebody's going to violate a module boundary.
Its not a lazy cop out when the comment he's replying to is the exact example in question.
It helps that network boundary typically becomes responsibility boundary (different teams)... in the end disciplined devs will make it work anywhere, anyhow.
Its a matter of making it work with the cards you have.
I agree about this danger. But then again, nests of services can also grow tangled dependencies, now in the form of RPC calls.
It's a general problem in software: adding a dependency without cleaning up (or even becoming aware of) it's effects on the dep-graph is often the quickest way to solve today's problem. You then pay for it over the rest of the life of the project.
What happens with monoliths is they tangle at all levels. How many times have you seen a giant "Utils" module which initially contained stateless StringUtils and similar classes then devolved into a dumping ground for stateful business logic.
Or especially with JVM applications how many times do library dependencies for one of part of the codebase end up causing issues with another. That's a big bonus with microservices i.e. being able to manage third-party dependencies better.
For a process that is inherently sequentially dependent on previous results, how is this not a transaction other than declaring it not so or 'dropping the outcome all over the floor' if there's a problem?
It's kind of like saying, "You're not allowed to have this problem, you be better off if you had some other problem like the one I have here"
Unless you're just saying to "grow the boundary" until all parts of the sequentially dependent process is inside the boundary? (this may be tricky to deal with the more external systems there are that cannot be "internalized")
* I am not saying you need distributed transactions - just that some processes cannot easily be encapsulated as "atomic" operations.
This is one problem that I've observed with all the monoliths I've worked on. Because modules are colocated and sharing a database is easy, eventually somebody will do it (even if it wasn't originally intended), and you get lots of ostensibly modular code intertwined with other modules in non-obvious and subtly problematic ways.