82 comments

[ 3.2 ms ] story [ 153 ms ] thread
I have a theory that microservices evolved because developers don't know how to write libraries that they publish to or install from private package repositories.
There are also pushy managers who would insist on slapping another function onto an existing microservice and then cut time spent on writing tests, just so that he can meet his targets and get a bonus. Then the app with time will start to perform worse and worse but when it happens, the manager will be long into a new gig and the developers will get the whipping. Then they'll get new developers to fix the mess and once it gets stable, a new manager will start to insist on adding that function to an existing microservice... and the cycle repeats...
I always thought microservices were an organizational solution to the problem of bloated headcount on teams meeting the hard reality that if you put more than two cooks in the kitchen, diminishing and counterproductive results are the norm.
Team oriented architecture?

I worked in some companies where each team were managing multiple micro services.

Micro services answer a very specific problematic: they are small to allow the least technical debt per "business features". The less technical debt there is, the more robust your system is against changing specs.

What is your definition of technical debt here? Just because something has a small LOC, doesn't mean it lacks tech debt. It's just harder to find and quantify.

In fact, I would posit they're littered with tech debt, especially from an infrastructure and shared services standpoint.

Services should be independent of each others (as much as possible).

IMHO, technical debt is found in coupling (modules, services, ...), the number of dependencies (not only code dependencies, since making an HTTP request also introduces a soft dependency), and the amount of knowledge required to understand the code.

If your service is unaware of its infrastructure (understand: i don't care if i run in docker/k8s/baremetal/...), the infrastructure's technical debt does not leak to the code.

The author gets it. I hope every developer dealing with microservices will read this.
This really needs a tl;dr summary. And I think especially so given that they want to make things “simple”.
my takeaway: start formally treating microservices like libraries, and think about how we traditionally link libraries to get work done
tl;dr

"The Unix philosophy, originated by Ken Thompson, is a set of cultural norms and philosophical approaches to minimalist, modular software development. It is based on the experience of leading developers of the Unix operating system. Early Unix developers were important in bringing the concepts of modularity and reusability into software engineering practice, spawning a "software tools" movement. Over time, the leading developers of Unix (and programs that ran on it) established a set of cultural norms for developing software; these norms became as important and influential as the technology of Unix itself; this has been termed the "Unix philosophy."

I think its going more like this:

Cycle 1 1. Have you seen this new thing called X? It's like Y only with more coolness. 2. X is pretty good! 3. X is great! 4. All the X! 5. Problems I've had with X 6. You know, X is pretty bad

Cycle 2 1. Have you seen this new thing called Y'? It's like X only with more coolness. 2. Y' is pretty good! 3. Y' is great! 4. All the Y'! 5. Problems I've had with Y' 6. You know, Y' is pretty bad

Cycle 3 1. Have you seen this new thing called X'? It's like Y' only with more coolness. 2. X' is pretty good! 3. X' is great! 4. All the X'! 5. Problems I've had with X' 6. You know, Y' is pretty bad

Where X and Y are concepts which get slightly better in each cycle.

I see no evidence they get better. Software in 2020 is in a terrible state.
> Deployment decisions have nothing to with microservices.

Weird. My mental model of where to divide things is precisely this. If two things can change (and be deployed) independently, they can go in different services.

I think that's probably a good heuristic but it's actually just a proxy for the underlying domain separation, which is actually why two things should go into different services.
Microservices brings a lot of complexity. Yeah you're not analyzing a stack trace, instead you put correlation IDs in your requests and you analyze a network call stack.

And you'll need a centralized logging mechanism to not have to log to hundreds of servers (or pods if running on kubernetes) to get that sweet failing request with its correlation ID.

It's far more easier to do Service Oriented Architecture when you don't need the performance/flexibility/scaling of microservices. I would not recommend any startup to go through the microservice route until they have the resources *and the need* to do so.

Microservices done right help isolate issues though. If you have an ecosystem of 100 microservices, but blue-widget-microservice is throwing errors you know which codebase you need to look at. Of course it might turn out that the errors are coming from bad inbound REST calls, but regardless it helps to narrow things down.

I don't think that microservices work well with a pure server kind of architecture. For one you miss out on a fundamental microservice advantages, scalability and portability.

Does it though? If you're triaging an issue, in a microservice environment, errors become limitless red herrings. Reproducibility becomes an enormous undertaking, especially when you can't (quickly) recreate the entire stack ("won't fit on my laptop").

A monolith can be quite scalable and portable.

Not only can a monolithic code base be scaleable but you can make it into microservices by adjusting it's startup parameters:

    monolith --start-batch-jobs
    monolith --start-claim-processor
    monolith --start-graphql
And they would be interconnected probably via some clustering protocol with libraries that hide the complexity and runs a bus available in a litany of languages.

Need scale?

k8s replica count on the above pods.

Hmm, wouldn't it make more sense to keep batch jobs outside the monolith? Everything else makes sense to stay in a single binary.
The point of it is to be able to run it simply for developers developering things. If I run in prod with 12 different splits of code, that's fine. If I want to run this locally to develop new features, I would love to put in a breakpoint to see my puppeteer job run to do some screen scraping without having to have 15 VSCode windows open. (Which is what I sometimes have to do at my current company because we're a team of 6 doing microservices with 12 apps (more planned yay).
My experience is that monoliths tend to decay over time unless some serious discipline is enforced. It's possible to reference across boundaries, so that happens sooner or later.

By going down a microservice route, those boundaries are ruthlessly enforce because it's the only way. That trade off may or may not be worth it.

Not doing microservice does not mean doing monolith.

There is an in-between that you can aim for.

But you don't have to recreate the entire stack, that's the whole point.
> If you have an ecosystem of 100 microservices, but blue-widget-microservice is throwing errors you know which codebase you need to look at.

If you have a system with 100 well-defined modules, but blue-widget-model is throwing errors, you know which codebase you need to look at.

No modularization technique has a monopoly on this characteristic.

It's a lot easier to violate that when it's just an import and a call away. I will concede that if the system is well designed and developers follow the patterns set it doesn't matter if it's a microservice based architecture or not.
I suppose it depends on your language.

For example, if you're working in Java 8 where everything's just one big jumbled globally visible classpath, sure. If, on the other hand, you're working in Java 9 or later, you have the ability to control your public interface. People can get around that with reflection, so it's technically more permeable than a service boundary, but, if you're letting shenanigans like that sail past code review, you get what you deserve.

Perhaps the dev culture I am in is skewing my perspective. There have been developers that have changed private methods to public just so that they can access them. You make a good point, it's a code review problem and by extension Architecture teams not communicating effectively with Dev Leads.
I agree, and packages and package private access is such a powerful tool in Java, that so many developers completely ignore.
Largely because it's not that powerful. It's got several shortcomings, but the main one is that Java doesn't actually understand Java package organization conventions. To Java, "com.mycompany.stuff" and "com.mycompany.stuff.specialized" are two completely unrelated packages, and so stuff that's package-private to "com.mycompany.stuff" is inaccessible from "com.mycompany.stuff.specialized".

That and a few other details make the feature annoying enough to use in practice that most people just find it's better to just stop using it.

Fortunately, if you're on Java 9 or later, there are modules, which are a much better fit for what kinds of access control tooling programmers actually need.

True. Microservice people would reply that microservice enforce modularization (as remote address spaces do not allow for dirty shortcuts). However that argument seems to me like drilling out a spark plug to help yourself with speed limits.
People in the monolith space call that "different JARs"
This article explicitly goes down the "micro" in microservices route, which does have the problems you identify. There's an alternative school of thought that regrets, or at least downplays the obsession with "micro". Much of the drive to microservices was a reaction against things like the Enterprise Service Bus in the SOA world becoming the big ball of mud no-one can easily change - SOA and microservices don't have to look a world apart.

I'm a big fan of Mark Richards and Neal Ford's emphasis of architecture being about trade-offs. No one thing is a silver bullet.

What I call a Service Oriented Architecture is much like the Microservice architecture but without worrying about the "micro" aspect.

Typical architecture in our projects:

- User Management Service with ORY[1] - Payment Service with Stripe[2] - Hasura[3] for the storage layer - GraphQL backend querying the above services - Frontend querying the GraphQL backend

Basically, we tend to delegate the boilerplate to third-parties (self-hosted whenever it's possible) and only implement the business logic ourselves.

[1] - https://ory.sh

[2] - https://stripe.com

[3] - https://hasura.io

Microservices: Instead of debugging a function, debug why it didn't even get called. What a waste of time.
Ex-Amazon SDE here: SOA is good, team boundaries are good, tiny microservices are bad, distibuted monolith is really bad.

> performance/flexibility/scaling of microservices

Tiny microservices tend to perform and scale poorly due to their granularity.

>Ex-Amazon SDE here: SOA is good, tiny microservices are not.

Fully agree. Unless the strict granularity is needed, which it's usually not, services should align to the tasks that the upstream "user" needs to accomplish. If every task requires calling the same 4 microservices in sequence, you've done it wrong. At the very least, the granularity should exist only where it's needed.

Disagree with distributed monolith. It's a big team/small team thing.

We converted a big monolith batch processing application to streaming + Kafka using what might be described as a distributed monolith and it worked great.

Using a single framework abstraction (in this case Spring) helps eliminate a huge amount of incidental complexity. We don't want teams inventing their own ways to manage things like retries, topic creation / administration, dlq, partitioning,...

If I wasn't using Spring/Java, I'd be using Elixir for similar reasons. Frameworks are good for async/distributed programming.

If you work in big tech and have enormous amounts of resources to throw at it, sure, go with totally independent microservices. But, distributed monolith can be very productive and provide a significantly easier mental model.

Erlang/Elixir is a bad example because much of the microservice best practices come from (IMHO) the actor model implemented with OTP.

Put your microservices in a mono-repo, replace "microservice" by "erlang process", replace "orchestration" with "OTP supervisors", and that's it. Conceptually it's almost the same.

(comment deleted)
> We don't want teams inventing their own ways to manage things like retries, topic creation / administration, dlq, partitioning,...

Then enjoy all the pains of a planned economy-errr architecture

"Anything involving a large framework. What part of "micro" are folks missing?

What on earth is a "large" framework? What does the size of a framework (by whatever metric) has to do with the "micro" which refers to a slice of the domain the service is taking care of.

The author seems to think that a microservice should have as few dependencies as possible.

If your business logic is contained in another framework and your microservice just call a subset of those functions, is it really a microservice ?

You'll be surprised.

A group I work with opted for a microservices approach, a few years later they have 47 tightly coupled "microservices" that have to be deployed all together or they break.

That's unrelated to the framework used to power those things though. You can have perfectly decoupled services running some bloated Java framework or using Go with minimal libraries or whatever. You might prefer one or the other, but it has little do with how much those services fit into the definition of 'microservices'.
ah, the dreaded distributed monolith. I hope this is looked at as a terrible anti-pattern someday.

"But we can deploy services independently" they say, "we can scale these services independently too!", "We can finally scale our development team!"

Years later: no services are scaled independently. Deployments are a mess. Most deploys end up having to be lock-stop deploys. Issues are incredibly hard to track down because of complex network interactions. For some reason 7 of them are written in Rust and only 1 guy knows Rust. Stack traces become almost worthless. You find yourself having to piece together log files across tons of services to track down issues. You have hundreds of Jenkins jobs for deploying services.

For me framework here is a bunch of software that solves a problem that is othogonal to the business domain: logging, networking, Db access etc. An upstream service is not a framework in this context.
> What on earth is a "large" framework? What does the size of a framework (by whatever metric) has to do with the "micro" which refers to a slice of the domain the service is taking care of.

I entirely agree. The framework jab, along with the gratuitous and entirely unrelated assertion regarding containers, just lower the expectation that the article has any point worth reading, specially considering that afterwards the author decided to include "having tests" as a characteristic of his concept.

> Honest Microservices Defined

> An app that cannot crash. It has a way to fail gracefully but that doesn't involve some downstream coder poking through your call stack.

> An app that does one and only one useful business function. What's that? See IST.

> An app that is composable with other apps from the command line.

> An app that uses dynamic, free-form, late-bound text streams to talk to other apps.

> An app that can be reasoned about, modified, or completely replaced by a maintenance programmer many years from now with little or no preparation.

> The app must have tests.

> The app must do what the tests say it does.

> The app cannot do anything that the tests do not cover.

> These tests exist in a compilation unit separate from the app itself and are in no way coupled to it.

> When in doubt, think of your compiled app as a pure function running inside a program. That program is a plain, vanilla, default operating system.

A good chunk of it sounds _a lot_ like the UNIX philosophy, and it is more obvious when looking at the author's article on IST. In that view the services supervisor, like sysvinit, runit or systemd becomes the supercompiler in charge of starting the different services, watching them and restarting them when they fail.

Evidently if we haven't gone this direction and came up with different tools then something must be wrong in this architecture but I don't have enough experience to understand what exactly, unfortunately

> A good chunk of it sounds _a lot_ like the UNIX philosophy

Yes, it does _sound_ this way, doesn't it?

That's because the UNIX philosophy is misrepresented. Having small self contained pieces of code serves you nothing if they can't be called on many different tasks with different arguments.

In theory, you could make generic microservices that behave like UNIX tools. On practice if you try that, your CRUD application will reside 90% in a single service, and you'll have some extra tools for a few more complex parts. (What isn't a bad architecture, by the way, but it's not what people describe as "microservices".)

It all depends how your app is split. Maybe you have pagination, and pagination could be extracted. Maybe you have logs, and they are handled outside. For pieces to be truly independent and exchangeable it seems to me it has to happen on more technical jointures rather than functional ones.
It sounds very Erlang-y.. in fact, at the end of the article the author refers to OTP directly
It does sound OTP-y indeed, and successful companies have been built on this stack. I was just wondering if it was possible with UNIX and if any company had success doing that (using small programs and pipes everywhere)
I agree, but I think the author is missing one big point:

- microservices is not a tech concept, it's a management concept

We (devs) like to think that microservices is all about tech, but in reality it's a disguised tech concept used by management to:

- grow the team. We are going to need more devs to build more microservices!

- have more things to manage. Like, do you really think managers want to say "yeah, I manage the delivery of... a library". At the end of the quarter they want to say: "yeah, I manage the delivery of a service that..."

- get salary raises. See previous point

That's it. The tech industry is shifting (has shifted already?) from a "tech people know better. Let them drive the big decisions" perspective to a "lean managers drive tech decisions because they know Agile and Agile is good" one.

Microservices is all about management/organizational topics, not about tech.

In my experience, engineers will be pushing for microservices as often if not more than tech management. The reasons are sometimes different - a lot of times it reduces down to "our existing services are all written with old, unsexy technology and I want to do something greenfields with new tech that looks good on my resume", but they're definitely not fighting against the tide of management insisting on microservices.
I see that as well, but only if microservices have been already "approved" in your organization (usually by upper tech management).
Another reason I’ve seen is “this project will be around for a while and we should skip past the growing pains to go into microservices.” If it’s a rewrite of an existing system, then it’s more justifiable because it’s about eliminating technical debt and is not a greenfield project. But for new efforts where an organization has never seen customer demand in X, Y, Z services being created it’s pure conjecture and premature optimization to essential refactor across service boundaries before a monolith with some usage metrics even exists
I disagree. It appears that you are dealing with bad management. Most Business people think in terms of features and defects. In most cases they don't even know what microservice does what, they probably don't even know the names of most microservices.

I consider microservices a natural extension of OOP on a service level. Each microservice functions like an abstract object that represents a thing. Of course it has the same drawback as OOP, do it too much, or implement it too little and you got problems.

I am perplexed by the downvotes. Which part of my statement is "wrong" enough to get downvotes? I am trying to understand if I have an opinion that's incorrect, or that people don't like it?
Striping your objects across processes and machines is a very different set of problems than OOP.

But even ignoring that, you fail to see that OOP is also a form of management concept. Its still compartmentalization and abstraction such that no developer must understand the full state of the system to make sense of an api.

OOP is much more a huaman management and communication concept than a technical one.

I don't know why this post has so many downvotes. Kingdom building has come to developer teams. You no longer see large dev teams that collaborate really well, instead you see silos and pizza teams. No one talks, everything becomes a political turf war, and the end result is everyone taking their ball and going home. Higher ups don't understand the complexity of the stack, they just hear we're constantly shipping new things--that has to be good? New is better than maintenance right?

Microservices are just managers codifying the boundaries of their kingdom.

It's hardly accurate to say it's come to developer teams - it's always been like this. This is simply human nature manifested in large groups.

Large groups operating well together without clearly defined boundaries of responsibility is very much the exception rather than the norm and always has been.

I would argue that a lot of the "famed agility" and effectiveness of startups is their flat structure and lack of middle management bloat, especially for the devs. As soon as you start splitting the dev teams apart, productivity grinds to a halt and you start hearing phrases like "this team owns that" and other territory marking behavior.
I think that well defined software teams that can integrate effectively with one another in large companies could spell the end of software startups.

The dysfunctionality of middle management outweighed economies of scale for a long time but I doubt it will last forever.

That's probably because startups are small organizations. Nobody is arguing that productivity increases linearly as you add 2 pizza teams. Productivity increases logarithmically. '2 pizza teams' are supposed to represent where the productivity gains of adding a dev to a team are marginal.
It's definitely a tech thing if you "need" the resource scalability. All it takes in certain types of work. If a customer can click a button and that ends up allocating a gig of memory or using 5 minutes of CPU, then scaling out is the only reasonable option.
Agree. But this is true only for a very few of the tech companies out there.
The article does not address any of the problems related to microservices and provides approaches that seem deceptively language agnostic which are in fact heavily dependent and influence your microservices design. It's interesting that this is not compared to the 12 Factor App or similar methodologies.

Points 5 (maintainability),6 (testability) and 8 (test coverage) are added like they are trivial things to achieve.

Points 3 (composability),10 (single responsibility) - gloss over the fact that even if you do achieve this, deployment of services with detailed dependency graphs is still a big problem for complex environments. Deployments are a problem with microservices and it isn't clear how they address this.

I like the idea of the supercompiler at the end. It's a lofty ideal and don't think anything remotely near it will exist in a long time (or ever) where you compile to a cloud environment (unless it is purely immutable and versioned). Also consider that cloud services change. Can't imagine how a compiler will evolve with that, we have a hard enough time with changing ABIs.

I’ve seen supercompilers in their infancy, I’m certain they will exist someday as fully envisioned.
(comment deleted)
If you try to label X, it will fall into hype bullshit territory. Humans are largely stupid animals. We hear something is good and we parrot it, until we hear it is bad and we parrot it, and we use our very limited experience to reinforce our narrative.

Stop labeling X. Show the metrics for each way of working to empirically prove outcomes. Use what can be proven works better. Where there isn't one conclusive better way, do a meta-analysis and define the edge cases. Only use the case that has the best outcomes for your needs. Move on with life.

Or we could keep navel-gazing and admit that we actually just do what we like, not what works best.

Hot take: If simply arguing about definitions of terms could break the hype cycle, technology hype cycles would have died out in the 1990s.
Why do none of these articles about the appropriate way to organize RPCs in your service talk about technological reasons for doing an RPC? If I have a computation that has a very CPU-intensive part and a very memory intensive part, maybe I want more than one SKU/instance type involved. That’s a technical reason to do an RPC. I have a computation that doesn’t fit on any of my SKUs, gonna need some RPCs. If I have unsafe/unmanaged code for the performance sensitive part but managed code for the watchdog part, that could go either way, maybe I do IPC or maybe I use an FFI.

RPC boundaries have costs and benefits. They are a tool, not an ideal to aspire to.

It's in the early days but this is exactly what Architecture the Hard Parts[0] is working on: finding for each system the right balance between pulling things apart and putting them back together again.

[0]: http://architecturethehardparts.com/ - book on the way, the training course is excellent

(comment deleted)
I wrote a React electron app that starts all our companies microservices on a single laptop and docker images for databases (which has our canned test data). It runs like 15 things and you have the entire application running in about 12 seconds. (most of the waiting time is mysql/mongo/memcached/rabbit starting up slowly in docker). You can also grab the PID of one of the services it displays and throw it in VSCode for debugging (since it's all node). It also makes a bunch of recommendations like - git pull, change branch (if there are changes), open vscode, open Azure Devop's PR page.

All of the crazy microtasks our team has shackled ourselves to into are handled by this app.

It's fine now, but I would never ever ever ever in a million years recommend microservices to a team of 6 developers.

Agree wholeheartedly with the concept of this but the content is dangerously close to the style of discussion the author doesn't like about tech!
Like the SOLID post I feel like I somewhat agree with the sentiment but disagree with a lot of the specific points. This seems like a pretty opinionated prescriptive.

For example, in my mind microservices is just SOA with no lower bound on service size ...and no upper bound for that matter. IE we're not wasting time arguing services need to be split or merged just because of their "size." (units aren't even specified) OP seems to think we should be worrying about what frameworks are used...

This post seems to really beat the programmer with "just try harder with tests and you'll get it!", or "your app cannot crash". Oh sure. No crashes. Why didn't I think of that? Then it pines for magical tools that don't exist.

I really don't understand what learning there is to get out of this article. It doesn't even touch on the biggest problems of "how do you separate services into logical units with minimal cross talk" and "When you do have cross service communication, how can you deal with that distributed computation."

"Just write tests" isn't helpful.

Nope.

Types can completely replace tests. The tests described in the article are ATDD tests, not TDD or unit tests. They're the result of a specific business workflow that has a goal of making sure you're building things that are useful. If you don't have that workflow, you can just use types to do the same work.

And nope, no magic tool required. Sure, tooling and automation would be really cool, much the same as having a C compiler beats the heck out of writing assembly, but it's not directly relevant to the points in the essay.

Todays microservices (with containers and kubernetes) are so complicated that entire fleets of engineers are required to keep them from just falling apart. They make tools to automate a bit, that tool fails. So now, more tooling is needed to detect and failover this in-house tool. Now we're dependent on many other services simply to maintain another in-house tool. Then a new hire comes along and brings more tools.

The analogy is civil engineers have started building a structure with new and shiny tools/materials. With new materials, the buildings collapse in many new ways so they build tools to stop them from collapsing. Only now, these new tools need constant management and handholding by thousands more civil engineers. If one of these tools fails, the structure collapses.

While I'm not opposed to tooling and automation, the constant patchwork over patchwork makes enterprise Java look more robust and humane.