Microservices for the Grumpy Neckbeard
chrisstucchio.com
chrisstucchio.com
The disadvantages of always paying for network costs are extremely steep, so the minute you care about latency, either because there's a user on one side, or because every second you wait is more money thrown the way of AWS, making everything a microservice quickly becomes a bad tradeoff for most applications. All the real advantages of microservices only start to pay off at scales that you'll only reach in a few handful of companies.
I am thinking of a small startup, that had a big proponent of microservices as the tech lead, which boldly went all in with this architecture, before any of the real advantages of microservices ever came into play. WHen they went to production the first time, they saw everything was slow, and quickly started to colocate said microservices in the same machines, and made them talk to each other directly, without serialization. In other words, they reimplemented EJB local interfaces. At some point in this process, the microservices champion left.
So now they have hundreds of little faux services, and instead of a monolith, they have to worry about what happens when someone wants to change the contract of one of those services. A ball of yarn, by a different name. Instead of wondering whether the code will compile if they make a change, they wonder whether it will run, in practice.
It sounds to me like they are not in any better a shape than if they had built a monolith anyway, other than the fact that their employees can now claim they have microservice experience.
If we go back to basics, maintainability comes down to getting lucky with your selection of abstractions, and trying to minimize coupling. If your abstractions are wrong, your modularization will be either incorrect or just lacking overall, and no architectural pattern will save you.
And if you take the suggested alternative of wrapping the return values in 'Future', then your interface just changed, so now the code that calls your service all needs to be rewritten.
It's pretty well established that service calls are either local or remote, and the service interface should show the difference between the two, but that means means that changing a service from local to remote is always going to break its clients.
That's a pain point, but it's probably better than making every service call remote by default which is what a microservices architecture does.
but microservices can scale in performance terms, and are the future (or something like it) IMHO - including local many-core.
they also enable an app-like per usage business model for libraries, which could be interesting...
Also, with a big org I like having the ability to use different languages and technologies in some small services. Is functional programming all the rage? Well we can experiment with it on this little service need to build without much risk.
Another thing I like is that other teams can leverage my micro service regardless of the languages we are using. With libraries, everyone has to use the same language.
Microservices, at least how I understand them, is just functional programming at the O/S level. Where they are located, how they talk to each other, and so forth? That all becomes SysOps stuff. That's good and bad. It's really bad if you don't stay on top of it and don't refactor. You can make the same mess you made in your big monolithic POS, just spread out everywhere.
But to state it that way misses the point. The point is to actively keep the number of microservices small, have tests for each one for compilation, deployment, and production. You should never deploy something that doesn't work in the ecosystem. The development environment should prevent it. You should also have a very good grasp on data flow between microservices and latency issues. Monitoring that should be part of your daily work.
You don't just do microservices because they're cool. You do them because they separate concerns in such a way to be both scalable and configurable without having to touch the IDE. With good safety protocol in place, this decrease both risk and complexity. Without good protocols in place, you'll make a mess no matter what tool you're using.
Look at it this way (put your FP hat on): any program takes input, runs it through some functions, and produces output. If you were debugging, you'd set up breakpoints along the inputs and outputs to chase down errors. Then you'd walk the data flow to see what was going on.
Microservices allow you to do this without touching the code. You would think that this increases network and system instability. It can, but when done well, you get the same thing you had with the big monolithic thing -- only in smaller pieces that you can change and reason about without introducing one of the thousand stupid coding errors that get introduced any time we open the project up and touch it.
Plus you get hot-swapping, easy testing, auto-scaling, and ease of programming. In the modern VM/hosted world, this is a pretty good trade-off.
There's no real problem with sharding using service objects: you parameterize them to determine what shard they are serving.
There are issues with replacing one if it goes down, or adding in a new one, or hot shards vs unused ones, but these are all familiar with any kind of sharding.
Y-axis scaling: splitting the application with functional decomposition (a server for account info, another for catalog, etc.)
Z-axis scaling: each server runs an identical copy of the application (like X-asis) but each server is responsible for only a subset of the data - some other component of the system is responsible for routing each request to the appropriate server (Ex: sharding)
See this diagram http://akfpartners.com/techblog/wp-content/uploads/2008/05/a... taken from http://akfpartners.com/techblog/2008/05/08/splitting-applica.... For in-depth discussion, see the book The Art of Scalability.
I kept reading, looking for the punch line, but as far as I can tell he's just describing really basic information hiding and interface design.
Maybe it's OOD For Server Programmers? There's nothing wrong with it, it's just surprising to me that it's news.
Slowly and surely, we relearned all of the lessons the mainframe designers had learned, and it became heavyweight.
The new generation is working on smaller problems and saying, "This is heavyweight!" And then they go with a minimalist solution, and the problem space grows. They then learn the lessons the crotchety senior programmers had told them.
Microservices is but one more example. Decrease coupling, increase cohesion. And then they'll get into hairier and hairier problems and rediscover the problems the last time someone tried it. Remember when service oriented architecture was the buzzword? :)
To be fair, each generation iterates on the past. I'd rather code today than the 80s. However, each generation, each programmer has to learn the lessons again.
Usually, we learn the hard way.
When I first encountered OOD, I lapped it up and became an acolyte. Then I discovered how OOD didn't actually solve ALL problems well. Now I consider myself post-OOD; I use the parts that are useful when they're useful, and otherwise I use other paradigms. I became a fan of and later gave up on Ruby as a language before Rails existed.
I don't think I've ever been the junior programmer you just described.
"Oh hey data hipsters, no I'm sorry our SQL data base is 200TB, no we don't have performance problems."
"Oh no we don't need your functional restless framework, we've been using a java framework for like 10 years, it peaks out at 1000 req/s."
"Why would we buy 4 webservers? It has 4 nics, and 8 CPU sockets. Sure it costs 500k each, but then we can keep using our java framework."
[1] https://en.wikipedia.org/wiki/Object-oriented_programming#Hi...
I also used to work with these a long time ago (EJB 1.1 and 2.0). A lot has changed since then though, and EJB 3 has solved a lot of the pain points (arguably pushed by the success of concepts used in Spring and Hibernate, whose authors served in the JCP for the newer JEE specs).
So instead we could pretend that it's something like MachineLearningProvider. When this converts to a Micro Service its failure space increases by an order of magnitude without expressing it in the interface.
That was the primary problem with RPC—you really don't want to confuse local function calls from remote ones.
When you switch from, e.g. `LocalMachineLearningProvider` to `RemoteMachineLearningProvider`, your interface will change from MachineLearningResult to Future[MachineLearningResult].
I'm not saying it's not solvable, just that it was so silently mentioned in the article. You should have to work to reproof your code after RPCing something.
It can just be scary if you've spent time making it out as though MchineLearningProvider can never fail. Interacting with an outside object can be a painful experience.
This gives you some of the worries of a true distributed system within your monolith.
What benefit does two levels of futures provide? I was sarcastic before, but now I'm honestly asking.
1. If you don't assume exception throwing (which
may be valuable) then you can more easily distinguish
one failure from the other.
2. It reflects in the type a more accurate representation
of the effect going on. If such distinctions are
undesirable it's a trivial `join` to eliminate them
but if the distinction is never exposed then you can't
get it back.
So really, my thought is that given greater opportunity to distinguish between end-user meaningful effects it's worth exposing them by default. It should be a deliberate choice to hide them with join.I think you just emphasized a bit that, in principle, should never be allowed to happen. When Team Email switches to a service their interface has changed, period. In some idealized sense the contract they implement is the same, sure, but an interface that allows for the possibility of timeouts, down servers, lost packets, etc. is fundamentally different from one that doesn't, and you can't (or at least shouldn't) use the same code to interact with the two. Ideally, the compiler wouldn't even let you try - and in a good type-safe language, this is accomplished if Team Email swaps out the interface they're implementing when they go service.
If you are assuming a fixed language (or at least fixed interoperability) the post rings true. If not, there service objects are clearly not appropriate.
- Lots and lots of large systems are decomposed across different languages based on the advantages they have. Some parts of the system may be extremely latency sensitive and therefore can't afford GC based languages, other parts may need to interact with existing enterprise systems and therefore are best written in Java or the .NET stack. A very common split is amongst quantitative developers who prefer languages like python vs other developers that prefer something else. So basically yes, there are times that I've worked on systems that were designed from the ground up to use different languages (or should have been). That said, of course you want to make sure you aren't adding complexity for it's own sake and a unified language is usually better.
- A very common use of service architectures is not green field development. It is in migrating from legacy systems or augmenting them. Lots of large web based systems were originally written as monoliths on the LAMP stack or as Perl/CGI. As they grow it becomes obvious that scaling will require different technology choices, but it is almost always a mistake to do a full rewrite. In those cases, language agnosticism can be a real boon as you can ramp up in languages that are more popular at the time of need (or in languages that allow you to attract good developers).
I understand that we shouldn't treat RPCs like local calls, but that doesn't mean we cannot do the reverse. If we design services properly we don't need to tie the design and the deployment.
I just can't see the purpose of making something permanently flaky at runtime for the sole purpose of keeping developers on track at design time.
In particular, I like this quote: "The second approach in common use is messaging over a lightweight message bus. The infrastructure chosen is typically dumb (dumb as in acts as a message router only) - simple implementations such as RabbitMQ or ZeroMQ don't do much more than provide a reliable asynchronous fabric - the smarts still live in the end points that are producing and consuming messages; in the services."
1. Would it be separated into the following services: user, post, and comments?
2. If I was designing this in flask or django, would each service be running on localhost with different ports, e.g. user is localhost:8080, post is localhost:8081, etc?
For my personal flask project, I am currently following this style http://mattupstate.com/python/2013/06/26/how-i-structure-my-... with service layers.
I did work on an application suite that included a blogging platform, that aimed for a service based architecture. We had services for: * taking screenshots of the rendered blog post, for use in preview thumbnails * user identity and settings (shared service for the entire suite) * asynchronous task handling * sending emails * adding blog post analytics to the dashboard * all things commenting (including a javascript embed code) * the main application for editing and rendering the blog content * customizing blocks of content based on visitor identity
No; posts and comments would likely be provided by the same service. Users, on the other hand, are likely an abstract idea implemented across several services (an authentication service, a profile service, etc.)
An easy way to think about it: if your business logic needs to do the equivalent of an SQL JOIN on data from both A and B to compute some common query (e.g. displaying a page with a post and comments on it), then A and B likely belong to the same service.
If, on the other hand, you have data/state that can be encapsulated into its own black box that none of the rest of the code needs to know the internal structure of (e.g. user authentication information: password hashes, 2FA token seeds, session IDs, etc.) then that data/state can be isolated to its own service.
On a plain blog, where "people who post" and "people who comment" are basically disjoint sets, comments can indeed be separated out into their own service, which may indeed be a gateway implemented by a third-party RPC consumer object.
I think the difference between "push a few words onto the stack and jump" and "send a bunch of packets out into the cold, harsh world and pray for a response" is simply too big for a common interface abstraction in many cases.
Ilaksh is describing something closer to the goal of akka or erlang - you define the logic of your program, but it can be run as a distributed system or monolithically without changing your code.
But what happens when you have serious traffic? One of the main benefits of microservices is splitting the system up for deployment & provisioning. One particular part of the system might be very memory heavy, you can deploy it on a cluster of machines that are spec'ed appropriately. One part of the system might have a relatively constant workload so you can put it on dedicated hardware, while other parts might be cyclical and you can spin up and down instances to match traffic. You can deploy particular components independently which makes releases a much smaller affair.
These things are not impossible with monoliths, but they are easier with microservices. So if the bigger pain point is deployment, scale, etc and not writing new code, then microservices might be a good choice.
Obviously I'm not advocating making a postgres (lots of seeks) server share disks with a hadoop server (lots of spinning). That's silly. I'm advocating sticking with service objects until you have a good reason not to.
Sigh. The use of json format is not necessary or sufficient make the service "micro".
There's this thing called "content-negotiation". IMHO, Any http web service framework worth using just does it. This means that the "xml or json" issues are fixed before they start. Unless you really want to have that trouble later.
I am currently working on a MicroServices based system that exchanges Google Protocol buffers over Tibco EMS.
Key ingredients of MicroServices are simply finely granular distributed components.
The actual method (json-over-http-, thrift-over-infiniband, best practices regarding conten-negotiation) is not the point of the article.
So to keep things simple for the purposes of an article he uses probably the most common method; is that really cause for a self-aggrandizing snarky comment?
I think it is worth addressing this. In my day job we are dealing with the ongoing effects of not doing http content negotiation a while back, and an article that perpetuates bad practices (even by the omission of simply failing to mention anything else because it's not the point of the article) is not helpful.
Obviously the solution would be to ensure that most SOs don't actually maintain much internal state to the greatest degree they can. This can be partially solved by abstracting out certain stateful primitives into other service objects—achieve this with dependency injection, perhaps.
I understand the semantic emphasis (and appeal) of 'neckbeard'/'graybeard' in the IT subculture is patently on expertise/oldschoolness, not on gender at all --hell, I've used that noun with gusto myself with barely any thought about the person's gender.
Yet, given the (by no means conclusive, but increasingly socially accepted) evidence that using implicitly-gendered words like that can be, at best, tacitly exclusionary to some degree --like equating 'balls' with courage/determination, or refering to 'man' as humankind--; perhaps one should, at the very least, pause and reflect on whether the amusingness of 'neckbeard' is, in this particular context, worth its use.
Maybe we ought to come up with a female stereotype to match, or a genderless one. Or maybe this will do just fine - you don't want to be a 'neckbeard' regardless of gender, it's not a good thing.
Wow, you don't get it at all. You are persisting a stereotype that is exclusionary and your reaction to someone bringing it up politely is to throw it in their face with explicitly sexist sarcasm. Nice job, and you have attached your real name to it all, Chris Stucchio. That alone might be reason to have a different, more professional tone.
If you want to make a moral case why I should change the title, do it. If you are persuasive I'll change it. Appeals to inconclusive but "socially accepted" evidence are, however, ridiculous.
I stand by the comments I've written here and would not change them if the edit button were still available.
It's very simple, just don't mix professional topics and gender specific terms.
It's otherwise a really good blog post and discussion, I'm actually sorry for participating in disrupting it.
Make a good argument and identify your axioms (fairly crucial) and I'll give a more intellectual response. Simply tell me to change my style for "socially accepted" but "questionable" reasons and I feel quite justified in ignoring and mocking you.