Honest Microservices
danielbmarkham.com
danielbmarkham.com
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.
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.
A monolith can be quite scalable and portable.
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.
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.
There is an in-between that you can aim for.
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.
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.
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.
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.
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
> performance/flexibility/scaling of microservices
Tiny microservices tend to perform and scale poorly due to their granularity.
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.
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.
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.
Then enjoy all the pains of a planned economy-errr architecture
- 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.
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.
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.
Microservices are just managers codifying the boundaries of their kingdom.
Large groups operating well together without clearly defined boundaries of responsibility is very much the exception rather than the norm and always has been.
The dysfunctionality of middle management outweighed economies of scale for a long time but I doubt it will last forever.
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.
If your business logic is contained in another framework and your microservice just call a subset of those functions, is it really a microservice ?
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.
"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.
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.
> 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
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".)
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.
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.
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.
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.
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.
RPC boundaries have costs and benefits. They are a tool, not an ideal to aspire to.
[0]: http://architecturethehardparts.com/ - book on the way, the training course is excellent
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.
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.
Example implementations are also available for deeper inspection on GitHub. See link in article.
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.
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.
In fact, I would posit they're littered with tech debt, especially from an infrastructure and shared services standpoint.
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 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."