Skip to main content

Microservices Architecture

Free2019-08-11#Front-End#前端微服务#微服务与微前端#微服务架构的优缺点#Microservices vs SOA#Microservices Pros Cons

What exactly is Microservices Architecture?

0. Starting with Monolithic Application

Monolith means composed all in one piece. The Monolithic application describes a single-tiered software application in which different components combined into a single program from a single platform.

(From Introduction to Monolithic Architecture and MicroServices Architecture)

Placing different components of a software application into a single program is called a Monolithic application. For example, dividing the application into classes, functions, and namespaces using basic features of a programming language, using a deployment pipeline to ensure changes are tested before deployment to production, and running multiple instances behind a load balancer to scale horizontally:

All your logic for handling a request runs in a single process, allowing you to use the basic features of your language to divide up the application into classes, functions, and namespaces. With some care, you can run and test the application on a developer's laptop, and use a deployment pipeline to ensure that changes are properly tested and deployed into production. You can horizontally scale the monolith by running many instances behind a load-balancer.

Under this architectural pattern, the application can work well, but there are 2 problems:

  • Constrained change: Even a small change requires rebuilding and redeploying the entire application, and it's difficult to control the scope of change impact

  • Unfavorable for scaling: Cannot scale only the parts of the application that need more resources; can only scale the entire application

These limitations are especially prominent in cloud environments. Thus, microservices architecture takes the stage.

I. What is Microservices Architecture?

Microservices are a software development technique—a variant of the service-oriented architecture (SOA) architectural style that structures an application as a collection of loosely coupled services. In a microservices architecture, services are fine-grained and the protocols are lightweight.

(From Microservices)

A variant of Service-Oriented Architecture (SOA) that designs an application as a series of loosely coupled fine-grained services, organized through lightweight communication protocols.

Building applications as suites of services. As well as the fact that services are independently deployable and scalable, each service also provides a firm module boundary, even allowing for different services to be written in different programming languages. They can also be managed by different teams.

Specifically, build the application as a set of small services. These services can be independently deployed and scaled, each service has a solid module boundary, even allowing different services to be written in different programming languages, and can be managed by different teams

Microservices vs SOA

Service-oriented architecture (SOA) is a style of software design where services are provided to the other components by application components, through a communication protocol over a network.

(From Service-oriented architecture)

In SOA, services are provided by application components to other components through network communication protocols. Therefore, microservices architecture can be considered a variant of SOA, or a special case, specifically referring to SOA designs that satisfy certain characteristics

II. 9 Key Characteristics of Microservices Architecture

Componentization via Services

A component can be understood as a software unit that can be independently replaced and upgraded. A series of components plugged together form a software system, much like what we see in the physical world:

Our definition is that a component is a unit of software that is independently replaceable and upgradeable. There's been a desire to build systems by plugging together components, much in the way we see things are made in the physical world.

In microservices architecture, components are services, communicating through mechanisms like web service requests or RPC. This service-granularity componentization has 2 advantages:

  • Services can be independently deployed

  • Services have explicit external interfaces (PublishedInterface)

Independent deployment means internal changes to a service only require redeploying that service; multiple services need coordinated changes only when service interfaces change. On the other hand, such associations can be minimized through evolution of service boundaries and protocols.

Explicit external interfaces are a strong constraint that ensures component encapsulation and avoids excessive tight coupling between components.

However, compared to in-process calls, RPC has higher performance costs, so RPC interfaces are mostly coarse-grained and often harder to use. On the other hand, refactoring costs are also higher if you want to adjust component responsibilities.

Organized around Business Capabilities

Microservices allow decomposing the system into a series of services based on business functions, so cross-functional teams can be organized around business capabilities. Moreover, this organizational structure helps reinforce service boundaries:

P.S. Conway's Law: design structures will ultimately be consistent with the organization's communication structure:

Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure. -- Melvyn Conway, 1967

An interesting example of communication structure changing design structure is that some teams will shove logic into applications they can control to avoid cross-team collaboration communication costs:

A smart team will optimise around this and plump for the lesser of two evils - just force the logic into whichever application they have access to.

Compared to dividing teams by business lines in a Monolithic application, service boundaries are clearer and have stronger constraints. Because business functions that cross module boundaries easily appear, while service boundaries are relatively more stable.

Products not Projects

Microservices architecture tends toward the development team responsible for a product maintaining/evolving it long-term, rather than handing it off to another maintenance team after project delivery:

Microservice proponents tend to avoid this model, preferring instead the notion that a team should own a product over its full lifetime.

This product philosophy establishes a continuous relationship between the development team and users, letting the development team focus on how software helps users enhance business capabilities:

There is an on-going relationship where the question is how can software assist its users to enhance the business capability.

Smart endpoints and dumb pipes

A typical example of communication mechanisms is the Enterprise Service Bus, where all messages flow through the ESB, which handles message routing, orchestration, transformation, and application of business rules before reaching endpoints for processing. In this model, endpoints can remain dumb because much of the logic is handled in the ESB message pipes. Therefore, this is called smart pipes and dumb endpoints.

Microservices prefer the opposite approach: smart endpoints and dumb pipes:

That the lanes of communication should be stripped of business processing and logic and should literally only distribute messages between components. It's then the components themselves that do processing / logic / validation etc... on those messages.

That is, pipes are only responsible for distributing messages between components, and the services themselves handle corresponding processing of those messages

Decentralized Governance

The biggest problem with centralized governance is its limitations; a unified technology stack isn't necessarily suitable for all scenarios:

Experience shows that this approach is constricting - not every problem is a nail and not every solution a hammer, we prefer using the right tool for the job.

In the microservices context, each service is built separately, creating opportunities to choose different technology stacks, allowing more suitable tools for different tasks. This freedom in technology stacks helps services evolve independently, naturally selecting better patterns.

Of course, "previously no choice, now we can choose" doesn't mean a hundred flowers blooming is good, because maintaining different technology ecosystems may cost more than the benefits:

We don't end up with 20 different languages in the same system because each of them is opinionated and brings their own vision inside the system, maintaining different ecosystem is very expensive and potentially confusing without providing too many benefits.

In trade-off, we can limit choices to a few technology stacks, no longer strongly bound to one technology stack:

But a trade-off could help out, having a limited list of languages or frameworks we can pick from can really help. Suddenly, we are not tightly coupled with one stack only.

Decentralized Data Management

At the most abstract level, decentralized data management means different systems have different conceptual models of the objective world. For example, what salespeople see as customers differs from what developers understand; the same concept may have subtle differences from two perspectives.

An effective measure to handle this situation is Bounded Context from Domain-Driven Design:

You should explicitly define the context in which a model applies. You should also explicitly set boundaries in terms of team organization, usage of specific parts of the application, and physical manifestations like codebases and database schemas. Keep the model strictly consistent within boundaries, unaffected by external problems.

(From A First Look at Bounded Context)

That is, limit model concepts to a context, ensuring strict conceptual consistency within that context. Dividing a complex domain into multiple bounded contexts and mapping out their relationships is decentralization at the conceptual model level:

DDD divides a complex domain up into multiple bounded contexts and maps out the relationships between them.

Specifically for data storage, microservices also adopt similar decentralization strategies, letting each service manage its own database. These databases can be different instances of the same database technology, or entirely different database systems, called Polyglot Persistence:

Microservices prefer letting each service manage its own database, either different instances of the same database technology, or entirely different database systems - an approach called Polyglot Persistence.

P.S. For data consistency issues arising from decentralized data storage, consider using some compensation operations to eventually achieve consistency. See Starbucks Does Not Use Two-Phase Commit for details.

Infrastructure Automation

Compared to Monolithic applications, microservice deployment is more complex. Because in complex network environments, deploying multiple services is more difficult than deploying a single standalone application:

However, the development of cloud computing makes infrastructure automation possible, greatly reducing the complexity of service building, deployment, and operations, allowing us to use infrastructure automation to achieve microservice management in production environments.

Design for failure

In the microservices context, client-side fault tolerance design is more important:

A consequence of using services as components, is that applications need to be designed so that they can tolerate the failure of services. Any service call could fail due to unavailability of the supplier, the client has to respond to this as gracefully as possible.

Compared to Monolithic applications, this fault tolerance design's added complexity is a disadvantage.

On the other hand, to quickly detect failure points and even automatically recover services as much as possible, real-time monitoring is also especially important in microservices architecture.

P.S. For more information on fault tolerance design, see Fault Tolerance in a High Volume, Distributed System

Evolutionary Design

Component division is crucial in microservices architecture, relating to whether change can be reduced. The general principle is whether a component can be independently replaced and upgraded:

The key property of a component is the notion of independent replacement and upgradeability.

However, from another perspective, controlling change doesn't necessarily mean reducing change. If you can ensure these changes happen as quickly as expected, that's also excellent control:

Change control doesn't necessarily mean change reduction - with the right attitudes and tools you can make frequent, fast, and well-controlled changes to software.

Use the decomposition capability provided by microservices architecture as a tool to achieve service-granularity change control:

  • Expect some services to become obsolete in the future, without long-term evolution

  • Put things that change together into the same service, put rarely changing parts into separate services, distinguished from frequently changing parts

III. Key Issues in Microservices Architecture

First, accurately defining component boundaries is not easy:

It's hard to figure out exactly where the component boundaries should lie.

Therefore, evolutionary design settles for making boundaries easier to refactor (reducing trial-and-error costs):

Evolutionary design recognizes the difficulties of getting boundaries right and thus the importance of it being easy to refactor them. But when your components are services with remote communications, then refactoring is much harder than with in-process libraries.

On the other hand, if poorly designed, this only shifts complexity from inside components to connections between components, making it harder to control:

Another issue is If the components do not compose cleanly, then all you are doing is shifting complexity from inside a component to the connections between components. Not just does this just move complexity around, it moves it to a place that's less explicit and harder to control.

Such a situation may arise: individual simple services look better internally, while the messy connections between services are ignored:

It's easy to think things are better when you are looking at the inside of a small, simple component, while missing messy connections between services.

References

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment