75 comments

[ 2.9 ms ] story [ 152 ms ] thread
I have seem cases when a group of stateless monoliths would have solved more efficiently a business case than the micro-services alternative.

I'm not saying micro-services are a fad, because they are great for isolating domains and degradation of service, but that comes with a high amount of boilerplate code for remote invocation and serialization.

I would personally say, if your project has not reached 10,000 lines of code do not go that way. also if you can solve the problem at hand with a cluster of stateless monoliths, keep it simple.

Dear friends, keep at hand the Occam's razor

> decentralize all things – well, the services are still logically coupled, no matter how you split them.

I think this is missing the point. It isn't just about decentralizing services, it is also about allowing you to decentralize teams and decision making. With very large teams working on a single monolith, features that are complete often cannot be deployed because of the larger organizational overhead of planning and coordinating a release. The higher coupling in most monoliths also restricts a teams ability to try new things and take risks.

If performance of technology is the only metric that matters to you, then yes, microservices are probably a horrible idea. If you are having difficulty scaling your teams, then it might be worth looking at.

I don't see how microservices makes that overhead go away. That overhead is really a matter of proper systems engineering. With well-defined interfaces and properly-sectioned functional components teams can work just as independently on a monolithic application as they can on a microservice application. Similarly, a poorly-sectioned application with bad or nebulous interfaces will be a planning and coordination nightmare regardless of the architecture.

What I think the microservice architecture does is force some measure of good systems engineering on a team. You can't design a microservice application without putting effort into deciding how to divide the functionality and defining how those functional blocks will work with each other. You can get away with not doing that (until it's too late) when designing a monolithic application.

I've actually found overhead to increase significantly when using micro-services vs a monolithic codebase. Automated integration testing is far more complex. Ensuring that a change in one micro service doesn't break things in another can be daunting. But when you have a common codebase dependencies are far more clear.

Of course if you have tons of cash and resources to throw at it then that isolation could be beneficial. But is it worth it? I have no idea.

> Ensuring that a change in one micro service doesn't break things in another can be daunting.

That is what I was referring to when I mentioned nebulous APIs. In a properly-designed system, each component (be it a module of a monolithic system or service in a microservice system) should be a black box with known APIs and no side effects. Part of defining the API is defining the acceptable values that can come across the wire. If designed correctly, breaking another service should not be possible without an API change. An API change should be something signed off by systems engineering and communicated to all affected teams before it happens.

Done right, this is a fairly heavyweight process and so, as others have said, is only something that should be done when a project reaches a certain level of complexity (both code and team).

The more your system is broken down into smaller components the more you have to break an API to make a change. Unless all change consists of nothing but bug fixes and performance enhancements, changes to the API is as common as anything else.

If your need is respond quickly to changing requirements this setup falls flat.

Yes, if services are too small than it will be hell. A lot of microservice proponents talk about how services should align to a "bounded context", essentially a domain of the business. That makes it more likely that (1) there are well defined interfaces between business domains and (2) changes will be grouped into a single service as opposed to requiring changes throughout the codebase.

Also, if it's being done right, you rarely "break" an API in a single blow. You add new functionality, possibly via a new API. You give your consumers ample time to migrate. Only then do you remove the old API. One of my old teams had a hard requirement that all APIs had to be backwards compatible for 2 versions, which was essentially a year. We got very good at adding functionality without any impact on consumers that had not upgraded yet.

We've never had to break an API to make a change. Changes to the API should not break existing consumers. That's just as true for monoliths as it is for microservices.
A change to an API necessarily, by definition, breaks existing consumers. You can add to an API without breaking consumers but you can't change or remove.
There are at least two ways to change an API without breaking it:

* Adding a new method to the API does not break the API.

* In certain language/middleware combinations, adding new properties to a parameter object does not break the API.

If we have a method that needs to change, we add a new method. New clients use the new method, and we support old clients until they are retired. When using JAX-RS, we can add new properties to JSON objects provided they are annotated correctly.

You effectively have to maintain a backwards compatible system but that limits the amount of change that you can really do. It also definitely makes the system more complicated but more stable. That's the trade off.

If you're constantly changing/breaking the API then that's just a monolith in micro-service clothing. But the system can be simpler and you can make more significant changes quicker.

If you think of "the system" as the entire suite of applications across space and time, then the one approach is no more complicated than the other. One involves some effort thinking about backwards compatibility, the other with trying to get a bunch of teams to all commit to the right branch, and then get it integrated at the same time. My experience is that the latter proves far more disruptive to production. One of our groups (the monolith team) has one month code freezes while they integrate all the teams work into the latest monolith.

Our teams really have no limits on the changes they can do. If the change really is so complex that it requires changing every system, then we just build another system with zero dependencies. In fact we did that once. Got it working really fast. Usually branch-by-abstraction resolves any big issues, while updated method objects or just new methods handle most "changes".

Granted, this is with several hundred developers across multiple engineering departments. YMMV.

So you either use versioning or RESTy immutable endpoints. Anything else is going to be unmaintainable
In most environments systems are already distributed, it's just that each "component" is a massive monolith. Sometimes, it's the cobol monolith, the .Net monolith that partially replaced it, and the NodeJS monolith that is going to replace everything (for sure, this time we mean it).

Integration testing is already complex in these environments, regardless of whether the components are monoliths or microservices. BDD style integration tests should be almost equivalent, regardless of the architecture, since they are driven by the business domain.

Caveat: I have not implemented microservices, but have plenty of experience with monoliths.

"With well-defined interfaces and properly-sectioned functional components teams can work just as independently on a monolithic application as they can on a microservice application"

Basically if you build a monolith such that it is not a monolith things are ok. This doesn't happen in my experience.

Speaking first hand, working with 40 engineers on a 500k+ line codebase. The monolith is as robust as it's weakest component.

There are secondary organizational effects that come into play as well with monolith architecture that need to be mitigated. Because there's a single monolithic platform, the org believes it can throw any set of engineers at a problem in the monolith and get the same result. This largely gets down to what philosophies you buy into, but I personally believe long lived teams with long lived missions are better capable to innovate (team builds depth of expertise in a capability vs breadth). There are plenty of ways to do anything wrong of course.

More so, as the team scales to more engineers, the monolithic architecture re-enforces the monolithic engineering org which means an increase in communication overhead.

My experience is that by creating a monolith you create one way to do things that leaves the organization less adaptable to change to implementing a new technology or pattern.

> The higher coupling in most monoliths also restricts a teams ability to try new things and take risks.

I think it's you who's missing the point. That right there is why.

Turning your monolith into a micro-service architecture should add up to basically: - Wrap your internal modules in your favourite form of RPC - Replace the modules with stubs that call the RPC

If you need more work than that, the problem with your application isn't being a monolith, it's having functionality be way too tightly coupled.

If turning your application into a micro-service architecture actually _is_ that simple, then you already have well-delineated modules that your teams can focus on. Deployment is just about integrating the latest stable version of each module, and the decision process within each team needs only respect the contract around the interface they provide -- same as a micro-service.

Saying you need a micro-service architecture to keep a sane internal structure to your application is a symptom that you need to review your engineering practices, because people aren't respecting the interfaces, and it's throwing out the baby with the bathwater.

Don't forget that an RPC call can take longer to complete than a function call. Loose coupling doesn't imply that your program expects operations to routinely take 1ms to return results.
Or sometimes 10s, or never, or partial results, or dropped connections, or...

The network is not reliable.

I was replying to a comment that ended with "If performance of technology is the only metric that matters to you, then yes, microservices are probably a horrible idea". I figured that it'd be more effective to make my point without bringing performance into the discussion.
I get what you're saying, and I've tried both ways: using project structure to explicitly force modularity vs trying to instill culture and practices to preserve modularity within a monolith. I should mention I am a consultant, so I tend to work in enterprise environments with varying levels of practices and maturity.

My experience has been that lots of different people will touch a codebase over the lifetime of a piece of software, and they will have varying skill levels. Even if everyone on the team right now will avoid creating a ball of spaghetti, at some point everyone involved will be gone. Patches and new functionality end up getting bolted on and added to an ever growing list of technical debt (we'll get to it, we swear). 10 years down the line no one can maintain it because no one knows what the interactions are across the system.

Splitting code into separate services/codebases doesn't totally prevent this from happening, but it does make it a lot harder. First, adding coupling is no longer the path of least resistance when you "need to delivery yesterday". Second, each service is significantly easier to grok than a single legacy monolith, and therefore easier to refactor back to sanity.

Vaguely related, I also really like how microservices offer a non-insane approach to modernizing a large system. A lot of my clients have large highly-coupled mainframe applications written in Smalltalk/COBOL/etc. They want to migrate to a "modern" language (ie one with a workforce). Big bang migrations almost never work, so we end up carving off functionality, re-implementing it in the new framework, and then integrating it with the monolith via messaging/services. Microservices make it easier to pull out one piece and rewrite it, without having to also touch every client.

> First, adding coupling is no longer the path of least resistance when you "need to delivery yesterday".

I wouldn't be so sure. More often than not, it's cheaper to set up a new module/library than it is to setup a whole new service, and I've seen plenty of cases where people just tacked functionality on to the handiest service because they needed to get shit done Right Now, and setting up a new service was a waste of time.

The microservices approach has 2 major benefits - 1. Those boundaries running over services allows you a lot of flexibility in read caching using commodity software like Varnish or Mongo (generally assuming REST here) 2. It allows for a polyglot architecture (ok it's a benefit or disadvantage depending on your outlook)
One thing I don't think gets hammered on enough is that if your organization is not large microservices will create a lot of overhead for little gain.

If you have few developers most of the time it's a lot more efficient to have a modular monolith than it is to have 50 entirely different rocks.

Decentralizing teams, decision making, and allowing increased independence of agency within a larger org is great. Trying to do the same thing when you only have 5 people is usually borderline crazy.

One advantage of a microservice architecture not mentioned in the article is the ability to scale services independently of other services. Being able to fire up more instances to address a bottleneck is often much simpler than managing threads in a monolith.
that is true in some very rare cases. CPU-intensive bits of the application should definitely be separated, so that they can scale independently. But that's not the main point of microservices.
I don't think I've ever seen a piece selling the benefits of microservices which didn't mention the scaling or reliability benefits as a primary motivation.
I would concede that it only happens at scale but I would contest that it is rare.
(comment deleted)
you can often as easily deploy a monolith to multiple servers and grant different subtasks different cpu and memory for their work. for me its just a matter of adjust with the supervisord config for each server, how many of which workers start and run on that machine.
This is an important point. If you have a system that processes billions of inputs a day along a fairly complex pipeline, you're constantly scaling up and down. It would suck pretty bad if you had to scale mostly vertically.
It depends how you do micro services. There are middle grounds. One big gain of micro-services is that it guarantees things are separate and can be handled by separate teams if the need arises. That doesn't mean you need to start out that way. For instance in Python I use hug to create my microservices https://github.com/timothycrosley/hug, then I can just install them to create a "monolithic" services that consumes all the microservices, the great thing is that hug allows you to expose both as a webservices, and as a Python library so I can consume as a Python library with no overhead, until the need to split is evident, and then can split up the services with very little work. Of course the need may never arrive, but the modularity that is forced when using micro-services pays dividends quickly regardless
Huh, I had never seen `hug`, thanks for mentioning it!
This is more or less what I'm doing right now with Dropwizard. I started building this app as a set of separated microservices, but was finding it to slow me down more than I wanted it to--so I flipped it upside-down and loaded every "application" into a single Dropwizard service. Which is uncomfortably like application servers, but I'm using this almost exclusively to get around doing the inter-service wire-up that I'd have to do manually, rather than letting my DI container do it. Each module's still separated, and if we need to fork services off in the future, it's about twelve lines of code.
🅰s always, there is room for both styles of programming. Some applications simply do not require this type of architecture. Testing a monolith is certainly easier than testing thousands of microservices. Debugging is also a lot easier. Dependency management is also hell of a lot easier.

Yes microservices (or simply services) are very good at large scale applications but they are simply an overkill for any startup project. Why not concentrate your efforts on selling your product first vs building the perfect infrastructure that no one uses? When time comes to upgrade - well upgrade. Amazon is a good example of a large company making the move from monoliths to services and they executed well. You can also do the same thing when it is absolutely required. You know, refactoring!

There's a dangerous, contagious illness that developers of every generation get that causes them to worry about architecture and getting "street cred" even more than they worry about solving business problems. I've fallen victim to this myself, because street cred is important to me. But it's a trap.

An important meta-idea that encapsulates all of this is remember to solve your business problems as your first priority, and implement technology in service to that goal first and foremost.

It's far too easy to put various technology decisions in the driver seat, ahead of the business problems. This leads to all kinds of anti-patterns over the years. For example, I saw software development organizations destroy themselves trying to do 100% pure UML-first software using Oracle's Designer/2000 and Developer/2000 tools, because that was just the "right" way to do things. I've seen organizations destroy themselves in the morass of trying to do things with Enterprise Java Beans (EJB) because it was just the right way to do things. I've seen companies spend 10x more time getting their automated build processes and super duper full test coverage going than they spend trying to actually write software.

And I've seen companies navel-gaze over their processes and methodologies and buzzword compliance far more than they worry about meeting the needs of their customers. If you've ever worked for a company with a 15 year history of ironclad determination to use all of Microsoft's latest preferred data access methodologies, then you have first-hand knowledge of another example of this.

I'm not saying microservice architecture is a worthless fad, but I am saying that putting your business needs into the done-basket absolutely must be the first priority; never lose sight of it. I'm all for being wary of technical debt, but "technical debt" isn't nearly as dangerous as whatever you call that contagious illness that infects developers and makes them put architecture before business needs.

It would help a lot if those developers spend some time in companies that don't produce software as their main business.

Where software is seen as a cost center and is actually developed 100% by contractors that fine-tune existing code bases across multiple years.

I guess there isn't better cure to those that tend to follow each fad that pops up.

I've worked in both types of environments and the companies where software is a cost center are just as bad if not worse than first class software companies with regard to latching onto a new technology or framework, half-assing an implementation, and then moving on to something else. The pattern seems to be to blame the framework rather than realizing the implementation was poor. "We made something and it sucked, must be the framework's fault!"
I agree with you, but the opposite of this is also horrible. I'm working for a company where they only focused on business needs, and the result is horrible. Terrible, unwieldy codebases, full of bugs, have no security at all. It's hard to develop features, it suffers from feature creep, have absolutely no test coverage at all.

It's important to find balance.

You're totally right: balance is the key. At least where you work there's a deployed product in production, though it is difficult and unwieldy. When you go the architecture astronaught direction, it's very easy to never make it into production at all!
Sounds like they only focused on short-term business needs.

Maintainability and security are definitely business needs, but they're long-term instead of short-term, so it's easy for new companies to underinvest in them.

I don't think that feature creep is a characteristic or symptom of a poorly-architected software system. It's just indicative of immature product/business management or conflicts of interest spanning the entire organization.
Ha, so true. Learned that lesson with Rational in my case :-)

Plus the compulsory EJB pain. I found it quite liberating to be able to just abandon EJBs once it became clear what a disaster they were without having to do do the Emperor's New Clothes bit.

I kind of share the scepticism about shiny new paradigms now, but one does have to be mindful not to inadvertently reject good new things because they're new.

The thing to keep in mind is a minority of software developers are actually working on software with actual customers.

So when you don't have customers you get to anti-pattern and navel-gaze, because then, at least people are doing something.

> more than they worry about solving business problems.

That's why companies implement Agile. It's all about "solving business problems". No architecture, no design, no documentation (merely 'user stories'). Dump and re-write instead of maintain. All your problems solved!

Very well said.

A clean data model, one aligned with business needs, and sensitive to the nature of machines, makes the best architecture.

Far too many decisions are based on what is trendy and fashionable rather than what is right for the domain.

It's important to solve problems, but if you want to win at this startup game, how you solve those problems can make or break you.

In most cases there others solving the same problem. If you are the team bringing a better solution to market faster, with better design, higher quality, less maintenance, lower infrastructure costs, fewer developers - you win easier and earlier. But, that takes some dabbling and experimenting.

In the end, it's really about working with high quality people. A team of focused, pragmatic, informed, and disciplined people can take the right new tool and become an order of magnitude better. And, they can take the wrong new tool and know when to shelve it quickly.

The winners in this game aren't writing COBOL against mainframes - even though that would probably yield a sufficient solution eventually.

Being a new "convert" to microservices, and coming from a classic OOA/D/P background, I read these critiques with great interest. I keep waiting for one that shows me what I'm missing.

What I'm finding, however, is that many of these authors have such a broad understanding of microservices are that they miss any benefit. Then, of course, they complain about there not being any benefit, natch.

I suspect -- and what I have feared -- is that the term "microservices" has been co-opted and rebranded by a variety of vendors and proponents. The goal here is to sell products and services, not necessarily solve problems.

There is certainly a huge wheel of hype in the technology world, where things become cool, then old hat, then abused, then nobody does them anymore, then they return under a new buzzword.

Having said that, in the future I'm going to always make a point of defining exactly what I mean instead of just using the term "microservice". My current definition is something like this:

- Pure FP

- Unix Philosophy

- Each microservice has less than 150LOC

- Common code, types, and persistence functions are moved to shared libraries to reduce interop concerns

Not sure if this makes a difference in the discussion, but I know that it will help me keep straight whether various authors actually have quibbles with microservices -- or are just re-applying their pre-existing OO thinking to a place where it doesn't necessarily map so well.

"- Each microservice has less than 150LOC"

Sounds like a nightmare, you know how much instances / vms you need if you spawn one thing for 150LOC?

Nobody said anything about 1 VM per microservice. You're using the term "service" in a way I did not intend (and which is a carryover from other kinds of services)

This is illustrative of the point I was making.

One of the big benefits of microservices is the total decoupling of the code from the underlying hardware. Things are glued together by Ops, not by code. So you might have all the microservices in one VM -- or spread copies of one critical microservice across a thousand vms. It's all configurable.

I don't understand properly the term micro service if services aren't separated in a "network" way.
You have an entire A-Class network(127.0.0.0) within a single VM. That makes ~2^24 possible adresses and 2^16 ports. Isn't that enough of a network?
it's more like "Why would you put multiple process on the same instance instead of just using a monolith app, the overhead for IPC / network isn't worth it if you put every process on the same machine"
The abstract notion of a service has nothing to do with an execution model. It simply means a well defined interface to some meaningful unit of functionality.

Properly designed, the execution model is orthogonal and the service itself doesn't care if it's invoked in-process or not. It delegates that to some other manager, which probably has a service interface of its own.

It's about sane abstract system boundaries, not mapping to real world facilities.

don't you think 150LOC is a bit extreme on the low side? I don't see how this is possible unless you compose services out of other services... and in that case it's probably a nightmare to debug anything. Can you elaborate a bit on types of things that this worked great for you?
I can't walk through a solution to a complex domain inside of a HN comment, but I can provide an overview of the theory.

Take a large monolithic app or framework. What I've found is that if a problem is properly coded, while you may have tens or hundreds of thousands of LOC, the actual code doing the work is quite small, on the order of hundreds or thousands of LOC. The rest of it is all "wiring".

Moving to pure FP means that a lot of the code structure of OOP disappears and you're left with just the critical pieces. This consists of composed functions performing translations on immutable chunks of data. Your functions become microservices and the composition of functions, scheduling, and moving around of data become Net/Dev Ops.

There are many ways to fall off the path here. You start using mutable data, you start associating services with business domains, you start coupling microservices together more tightly than necessary -- there's a ton of ways you can accidentally screw up, and then you're probably better off with a monolithic app.

Microservices should be like ls, cat, or chmod -- small pieces of composable functions that run directly in the O/S. They don't blow up the system when they fail, they do one thing and only one thing, they're configurable using command-line switches, they're transport-independent, and so on.

From GNU coreutils:

$ wc -l src/ls.c

    4980 src/ls.c 
$ wc -l src/cat.c

     768 src/cat.c 
$ wc -l src/chmod.c

     570 src/chmod.c
Obviously, those are monoliths in dire need of decomposition.
they do one thing and only one thing, they're configurable using command-line switches,

The more command line switches the more code paths. The more code paths the less of doing one thing and only one thing.

Do you subscribe to the idea that services should correspond to "bounded contexts", ie a business domain? And if so, what do you do when a bounded context is too complex to fit into 150LOC?
We've had a lot of issues with micro services. They're extremely difficult to debug and end up being too generic. I argue the reasons why we introduced microservices has merit (massive code base) but does not mean they are simple to use/maintain.

Here are some examples:

1. A chat service (race conditions, synchronization issues between the game server and chat service that had to be debugged using sequence diagrams)

2. A payment service that handled Facebook, PayPal, and other payment methods. (race conditions, complicated integration, difficulty upgrading for different products, complicated code base to handle multiple use cases)

3. An authentication service (complicated protocol, hard to integrate)

4. A worker service for handling bcrypt (because blocking a single threaded server for 0.5 seconds is not acceptable, race conditions)

5. A tracking service (could have probably just been a library you included instead of hitting an API)

Core issues:

- Race conditions

- Synchronization issues

- Complicated to upgrade/maintain for multiple products

- Become too generic and solve multiple problems (services tend to be used for multiple products in the company)

- Extremely difficult to debug

- Complicated error handling for when a service is not reachable

pro-tip: don't share databases between multiple products for each service, deploy a new service for every product with its own database.

I think I'm scratching the surface here, but I would be really careful with introducing this kind of architecture when you can do it all in one server.

I don't mind to sound crass, but it sounds like you implemented microservices poorly. You could s/microservices/threads/g on your post above and it would point much more to a problem with your implementation rather than a problem with threads.
Writing multi-threaded code is hard, writing micro services is even _harder_. I don't think your argument holds here.

Micro services are hard to write, so don't write them unless you absolutely have to.

I think in 2015 we should be pretty good at writing code without race conditions, especially considering the tools made available to us by our programming languages.
No true Scotsman...

Monoliths have the advantage of being able to trivially take out a global lock.

Removing global locks from a monolith to migrate to a micro-service deployment adds a ton of design and engineering cost.

Yeah, my general rule is that unless your production environment is already so complex that you have multiple people who do strictly DevOps work, you should probably stick to a monolith. It's a lot easier to transfer state between multiple deployments of the same monolith than it is to go full microservices.

Microservices are a gigantic pain in the ass. They increase operational complexity significantly, and they will require you to make a lot of investments in building/buying/implementing infrastructure around things like logging, monitoring and config management. Your number of possible variables in QA explodes exponentially, and suddenly you have to worry about things like API versioning.

If all of this sounds easier than dealing with the tech / organizational debt in your monolith, then microservices may be right for you. Otherwise, save yourself the trouble and focus on scaling your business instead of your technology platform.

I'm not advocating for microservices, but I think the blog post fails to understand the difference between patching a single microservice and deploying a single microservice without needing to restart your entire environment stack, and patching a single module in a monolith which requires you to restart the entire monolith... Maybe that's not an actual issue but the author seems to disregard the fact that restarting a single microservice is explicitly different than restarting the entire monolith for the very reason that time may become an actual factor.
You know, nearly all software running out there does not need to be online 24x7. And for the tiny exception that needs, reducing restart time is just not the way to get that 99.9% uptime.
Not disagreeing with you, just adding some color.

We have a monolithic application (well, an application composed of many libraries that have a dependency tree, but live in the same process) that contains individually deployable services that talk to one another over RPC. So when you're patching part of the codebase, you can deploy a branch to the servers that are executing the code you've just changed, thus no need to restart the whole thing. But you still have the benefits of shared libraries, moving functionality between services, collapsing service calls that are no longer performant, adding new calls temporarily, etc.

In this model, you can introduce and remove service calls when it's useful, as opposed to as a result of how the code was put together. You will often do this for performance reasons, but just as often you'll do it for deployment flexibility. When you want to prototype something new, you can create a new service, branch the codebase, and have it talk to the rest of the infrastructure via service calls. When you're done you can keep it as a service, or, just as often, you can collapse the service back into other services so we don't have excessive RPC calls.

When would we consider moving to services backed by different codebases? If our company were to grow so large that highly disparate teams would want to manage their own dependency trees--that's around when it makes sense to me to go microservice. (I not sure I'd call it microservices then, more SOA).

An honest question - has Martin Fowler ever written any code that makes him so much of an authority on software design?

I don't disagree with everything he's said, but what contributions has he made that makes him so much of an authority on how I write code?

That's an impressive logical jump you've made there. Starting with a man simply writing a blog post with his opinion on software architecture, all the way to implying that he's an authority on writing code.

He never said he was an authority...

Martin Fowler (not the author) definitely thinks he's an authority. Have you ever heard him speak?
You Google "Martin Fowler" and the snippet of text that accompanies his personal website is "Object-oriented programming expert and consultant, one of the leaders in refactoring, author of the book 'Refactoring: Improving the Design of Existing Code'"

He's a self-proclaimed "expert" and a "leader". Yes, he's basically saying right there that he is an authority on writing code.

That being said, I've read some of his Refactoring book awhile ago, and it's mostly good advice from what I remember.

I work for a startup bank in the UK (Mondo). Our Go microservices architecture allows us to maintain velocity whilst staying secure.

The core banking services that actually move money around are isolated from the more "fluffy" customer facing ones that we want to be able to push updates to several times a day. We have to have incredibly rigorous procedures for updating services that control money, if we had a monolithic architecture then these procedures would have to be used even if the change was simply cosmetic.

I think the choice of monolith vs microservice should be largely driven by the size of your team. Some guidance I heard on a podcast recently (can't recall which) was to only consider microservices if you had more than 50 people trying to deploy code in the same app/system.
The important thing is to improve tools, and with a monolith you can't do that.

So build a distributed PaaS with hot-deploy, then a distributed HTTP database; then you can use microservices without any of the problems mentioned (complexity becomes a non problem since everything is using the same complexity and it very quickly becomes bug free, overhead is removed by PaaS)!

The important step with microSOA is that each developer can choose his tools. That trumps everything else.