Don’t Sell Me a Monorail
Every time I read a story about microservices, I have to sit down afterward and pet my cat for a while. She looks up at me, slightly annoyed at all of the sudden and uninvited attention, but she puts up with it, because she’s aware that I am distressed. “But Greg,” you say, “why do you get so upset when you read about microservices?”
People are missing the more interesting facets of microservices.
A presentation from Google had nothing to do with architecture decisions at Opsee. Netflix and Amazon, likewise. When I go to conferences and hear talks from giant software companies, I don’t think to myself, “Wow. What they’re doing is so cool. I want to do that too, because it’s cool!” These talks are generally more like candy than they are informative discussions of useful patterns in architecture — demonstrations of internal software products designed to entice talented engineers, because the talk was part of a recruiting pipeline.
Opsee is a team of six engineers, and we have had microservices for all but a few months since its inception. We regret nothing about this decision.
Microservices allow us to:
- Rapidly prototype features in isolation.
- Safely operate under degraded conditions.
- Deploy services without impacting unrelated components.
- Clearly define separation of concerns.
Ship quickly.
Prototyping and shipping a feature in a matter of hours or days is critical to a company of any size. Beyond the simple competitive aspect of delivering features faster than anyone else, maintaining developer momentum is one of the most important organizational benefits of microservices. Allowing engineers expressivity and creativity in an independent codebase is not only productive, but a boost to morale. This applies for both on-boarding new engineers and keeping existing engineers engaged. When code gets into production quickly, it takes less time to get the product of your work in front of customers — which is why we’re all here in the first place. Prototypes can also be discarded entirely or easily replaced with a more sophisticated implementation at a whim without tedious extrication from a large monolith.
Shipping new features in isolation is important. It allows the impact of those features to be well-understood. They can be independently instrumented, logged, and monitored so that their cost and performance can be quickly and accurately calculated. Their interaction with other services can also be easily identified, and this visibility into component interaction doesn’t require scouring a codebase across separations of concern to discover its interactions, because inbound and outbound communication with the microservice is found by analyzing logs.
Handle partial failure gracefully.
Good software operates in a degraded state — either doing what it can while it can, and queueing requests for later consumption by an upstream dependency or by rejecting requests if they simply cannot be completed at that time. If your web browser cannot resolve a hostname, it doesn’t crash and then fail to startup at its next execution. The same is true for distributed systems. In terms of the CAP theorem, Opsee’s architecture attempts to provide availability and partition tolerance. We have eventual consistency guarantees, but when operating in a degraded state, the delay for consistency is appropriately increased.
A lack of clear separations of concern is how sprawling, incomprehensible monoliths evolve. A good monolith is going to have this, just like any microservices-based architecture will. That aside, in endeavoring to build with microservices, we’ve found ourselves coalescing around an architecture that has developed in this way organically. Building microservices requires building discrete APIs for components — which in turn requires handling failures of those APIs in reasonable ways. It comes with an operational cost, of course, but we feel the return on investment far outweighs it.
No Rails, no Redis, no ORM.
No. No. No. Rails is completely inappropriate for microservices. It doesn’t at all surprise me that Rails proponents are some of the most vehement opponents of microservices. Because we deem durability of transactions to be actually important, Redis is likewise an inappropriate queueing mechanism for writes in times of degraded performance. And while ActiveRecord is a powerful and sophisticated ORM, its expansive bloat makes it inappropriate to include everywhere.
We use a lightweight Go framework to build microservices that is based on Protobuf and GRPC. It’s not as hipster as Clojure or Haskell, but it gets the job done. Protobuf allows us to generate an extraordinary amount of code that we use to build our APIs, and GRPC likewise gives us robust, flexible interfaces to our services. Protobuf is worth noting specifically as it has been instrumental to the success of microservices at Opsee. The plug-in architecture for the protobuf compiler has given us the ability to do some really interesting things that we’ll talk about in the future.
Architectural decisions should be made deliberately and carefully, and their implications should be well understood. Design patterns are situational and depend on software requirements. It is never the case that one architecture is universally and objectively better than another, and software design has little, if anything, to do with organizational structure.
Opsee is application monitoring designed from the ground up to work with how you build apps in AWS. We keep an eye on all your backend services, letting you get back to doing what you do best: shipping code. Sign up today.