/ Back-End

Designing a fine-grained platform through Microservices

Microservices

  • are  an approach to distributed systems that promote the use of finely  gained services with their own life cycles, which collaborate together.
  • are primarily modeled around business domains, they avoid the problems of traditional tiered architectures
  • integrate  new technologies and techniques that have emerged over the last decade,  which helps us avoid the pit-falls of many service-oriented  architecture implementations

Concrete examples are present successfully around the world, in organizations like  Amazon, Netflix and more,who have found that the increased autonomy of  this architecture gives their teams, is a huge advantage.


Principles of Microservices

  • are  statements about how things should be done, and why we think they  should be done that way, and help us frame the various decisions we have  to make when building our platform

1. Model Around Business Concepts

History  has shown us that interfaces structured around business-bounded  contexts are more stable than those structured around technical  concepts.By  modeling the domain in which our system operates, not only do we  attempt to form more stable interfaces, but we also ensure that we are  better able to reflect changes in business processes easily.

2. Adopt a Culture of Automation

Microservices  add a lot of complexity, a key part of which comes from the sheer  number of moving parts we have to deal with. Embracing a culture of  automation is one key way to address this, and front-loading effort to  create the tooling to support microservices can make a lot of sense.

Automated testing is  essential, as ensuring our services still work is a more complex  process than with monolithic systems. Having a uniform command-line call  to deploy the same way everywhere can help, and this can be a key part of adopting continuous delivery to give us fast feedback on the production quality of each check-in.

Using environment definitions help us specify the differences from one environment to another,  without sacrificing the ability to use a uniform deployment method.  Creating custom images help us speed up deployment and embrace the creation of fully automated immutable servers to make it easier reason about our system.

3. Hide Internal Implementation Details

To  maximize the ability of one services to evolve independently of any  others, it is vital that we hide implementation details. Modeling bounded contexts can help, as this helps us focus on those models that should be shared, and those that should be hidden.

Services should also hide their databases to  avoid falling into one of the most common sorts of coupling that can  appear in traditional service-oriented architectures, and use data pumps or event data pumps to consolidate data across multiple services for reporting purposes.

Where possible, we have to pick technology-agnostic APIs to give us the freedom to use different technology stacks. Using REST will help us formalize the separation of internal and external  implementation details, although we are open to other architectural  styles that will embrace these ideas.

4. Decentralize All the Things

To  maximize the autonomy that microservices make possible, we need to  constantly be looking for the chance to delegate decision making and  control to the teams that own the services themselves. This process  starts with embracing self-service wherever possible, allowing people to deploy software on demand, making  development and testing as easy as possible, and avoiding the need for  separate teams to perform these activities.

  • Ensuring that teams own their services is  an important step on this journey, making teams responsible for the  changes that are made, ideally even having them decide when to release  those changes.
  • Making use of internal open source ensures that people can make changes on services owned by other teams, although this requires work to implement.
  • Align teams to the organization to ensure that Conway’s law works for us, and help our team become  domain experts in the business-focused services they are creating.
  • Where some overarching guidance is needed, we can embrace a shared governance model where people from each team collectively share responsibility for evolving the technical vision of the system.

This principle can apply to architecture too, as it follows:

  • Avoid  approaches like enterprise service bus or orchestration systems, which  can lead to centralization of business logic and dumb services.
  • Instead, prefer choreography over orchestration and dumb middleware, with smart endpoints to ensure that we keep associated logic and data within service boundaries, helping keep things cohesive.

5. Independently Deployable

We  should always strive to ensure that our microservices can and are  deployed by themselves. Even when breaking changes are required, we  should seek to coexist versioned endpoints to allow our consumers to change over time.

This  allows us to optimize for speed of release of new features, as well as  increasing the autonomy of the teams owning these microservices by  ensuring that they don’t have to constantly orchestrate their  deployments.

By adopting a one-services-per-host model, you reduce side effects that could cause deploying one services  to impact another unrelated service.We have to consider using blue/green or canary release techniques to separate deployment from release, reducing the risk of a release going wrong; using consumer-driven contracts can help us catch breaking changes before they happen.

We  have to remember that it should be the norm, not the exception, that we  can make a change to a single service and release it into production,  without having to deploy any other services in lock-step.

Our consumers should decide when they update themselves, and we need to accommodate this.

6. Isolate Failure

A  microservice architecture can be more resilient than a monolithic  system, but only if we understand and plan for failures in part of our  platform. If we don’t account for the fact that a downstream call can  and will fail, our platform might suffer catastrophic cascading failure,  and we could find ourselves with a system that is much more fragile  than before.

When using network calls, don’t treat remote calls like local calls, as  this will hide different sorts of failure mode; so we have to make sure  if we’re using client libraries, that the abstraction of the remote  call doesn’t go too far.

If we hold the tenets of antifragility in mind, and expect failure will occur anywhere and everywhere, we are on the right track. We have to make sure that our timeouts are set appropriately and understand when and how to use bulkheads and circuit breakers to limit the fallout of a failing component.

We  have to understand what the customer-facing impact will be if only one  part of the system is misbehaving, and know what the implications of a  network partition might be, and whether sacrificing availability or consistency in a given situation is the right call.

7. Highly observable

We  cannot rely on observing the behavior of a single service instance or  the status of a single machine to see if the system is functioning  correctly. Instead, we need a joined-up view of what is happening.

We have to use semantic monitoring to see if our platform is behaving correctly, by injecting synthetic transactions into our system to simulate real-user behavior.

We must aggregate our logs, and aggregate our stats,  so that when we see a problem we can drill down to the source; when it  comes to reproducing nasty issues or just seeing how our system is  interacting in production, we have to use correlation IDs to allow us to trace calls through the system.


As you have finished reading the high-level overview of the microservices architecture, make sure to follow us for the next follow-up article with focus on specifics regarding our platform, as this is a journey and not a destination.


Quotes, credits, references:

Daniel Tirzuman

Daniel Tirzuman

Genchi Genbutsu (現地現物) - in order to truly understand a situation one needs to go to genba (現場) - where the actual work is done.

Read More
Designing a fine-grained platform through Microservices
Share this

Subscribe to GoHelpFund Blog