Methodology of Microservice
A basic definition of Microservices Architecture
MSA is a software architecture pattern in which applications are composed of small, independently deployable processes communicating with each other using language-agnostic APIs and protocols. Microservices have the following attributes that distinguish them from monolithic applications and traditional service-oriented architecture (SOA) services:
- Tightly scoped: Microservices should implement a single functional responsibility with the goal of reducing development and maintenance complexity for each individual service.
- Tightly encapsulated behind concrete programmatic interfaces: Microservice implementations should be fully encapsulated behind well-defined and concrete APIs whether they are based on request-response, message or event-based patterns. Implementations using HTTP/REST are common due to their simplicity and ubiquity, but interfaces can also be exposed over other protocols such as Advanced Message Queuing Protocol (AMQP), Apache Thrift, gRPC, Protocol Buffers or Apache Avro.
- Highly cohesive: The capabilities provided by a single microservice should be closely related and should share a common purpose and common data on which they act. The goal of this cohesion is to prevent ripple effects as changes are made to a system. Service boundaries should be defined to minimize the chances of any given feature change requiring coordination between service implementation teams.
- Responsible for managing their own data: A microservice should manage its own data models and persistence. This helps microservices maintain encapsulation and independence from other services and supports cohesion (code and data structures often change together). Monolithic applications commonly have multiple services that share a single data model, which breaks encapsulation and creates coupling of services at the data layer.
- Highly decoupled: Microservices should be very loosely coupled with each other. When they interact, they should discover each other through the configuration of the service or the runtime environment and then only interact through the published APIs (whether request- response, event-based or message-based). Event-driven, publish-subscribe models of interaction can be valuable for implementing extensible decoupled interactions between services because an event source does not need to be aware of the specific event sinks listening for its event messages.
- Independently deployable, self-contained and autonomous: Microservices should be deployable individually with as few external dependencies as possible, and they should be able to function independently of other services. It should also be possible to start and stop instances of individual services independently of all others. To be independently deployable, microservices are often self- hosting executables, packages or container images, bundling their own lightweight web and/or application server frameworks, rather than relying on server application platforms preinstalled on their host environments.
What a Microservice Is Not
There can be some misunderstanding of and ambiguity surrounding the term "microservice." For example:
- A simple API to a more complex service implemented as part of a monolithic application
- A service implemented with a small amount of code
- A service built and delivered without automation of testing and deployment and operations
- A service built on mutable compute infrastructure that is updated and patched separately from software deployment
- A service that has dependencies on its peers that prevent it from being changed and updated independently
- A large, coarse-grained service or monolithic set of services packaged in a Docker container
- A service exposed via API by another party
- A component, module, service or capability, labeled as a "microservice" by a vendor, over which you do not have deployment and management control consistent with your other microservices
- A published API (microservice architecture is an implementation detail that should be separated from the published interface specification)
The question now is how to identify these independent services in order to implement them as decoupled micro-services? One option is to apply a Domain Driven Desing to your business case.
Domain-Driven Design
DDD is about designing software based on models of the underlying domain. It deals with large models by dividing them into different concepts and being explicit about their interrelationships.
There are a number of questions to ask yourself when determining the boundaries for splitting up your application:
-
Will the entire business domain be built as a service?
-
Will the services be accessed by other applications?
-
Are there existing services or applications that are part of the transactions or that require integration?
-
Are there compliance requirements for keeping operations together?
-
What are the data consistency requirements (none, strong or eventual)?
Bounded context is a pragmatic approach for splitting up your application into business domains with the goal of achieving high cohesion. High cohesion means grouping together the elements of your architecture that are strongly related.
At a high level, think in terms of both verbs (services) and nouns (entities) as possible techniques for splitting an application into business domains. Although both the verb and noun business domains will act as services, the easiest way to visualize how to differentiate them is that the verbs lend themselves to more complex processes, while the nouns lend themselves well to models shared between processes.
SPLIT BY SERVICE (VERB)
These are the services (and underlying processes) representing the actions we take. For instance, a user will typically be shopping. Several examples of the services involved in this shopping process involve searching for a product, viewing the product details, adding the product to a cart and checking out. These process models will translate into having robust, feature-rich interfaces and will typically have dependencies on and manipulate shared, entity models (nouns).
SPLIT BY ENTITY (NOUN)
These are the entities upon which our verbs take action and are shared between services. There will also be entities residing within your bounded contexts (or services), but they remain opaque to other consumers because these are internal details that need to remain encapsulated. These entities are shared between services and expose a basic CRUD interface of the entity.
This figure represents both services and entities identified in the sales domain (source: Gartner Group).
Once our design is ready, the services have been identified and their specific functionality is clear, a new decision needs to be made - which communication style to use for the inter-service communication? There are several architectures that implement these communication styles. Currently, the main approaches are message and event-based. Where even-based uses most of the messaging concepts.
Request/response communication
Sync response: A client makes a request to a service and waits for a response. The client expects the response to arrive in a timely fashion. In a thread-based application, the thread that makes the request might even block while waiting.
Async response: A client sends a request to a service, which replies asynchronously. The client does not block while waiting and is designed with the assumption that the response might not arrive for a while.
Publish/subscribe communication
There are two possibilities: publish a notification, or publish a request.
A client publishes a notification message, which is consumed by zero or more interested services.
A client publishes a request message and then waits a certain amount of time for responses from interested services.
Message-based Architecture
When using messaging, processes communicate by asynchronously exchanging messages. A client makes a request to a service by sending it a message. If the service is expected to reply, it does so by sending a separate message back to the client. Since the communication is asynchronous, the client does not block waiting for a reply. Instead, the client is written assuming that the reply will not be received immediately.
This type of architecture is very good for systems where the producers and consumers are well known. Either the producer of a message knows who must receive it, or the consumer must know from who to get the message from.
A message consists of headers (metadata such as the sender) and a message body. Messages are exchanged over channels. Any number of producers can send messages to a channel. Similarly, any number of consumers can receive messages from a channel. There are two kinds of channels, point‑to‑point and publish‑subscribe.
A point‑to‑point channel delivers a message to exactly one of the consumers that is reading from the channel.
A publish‑subscribe channel allows a sender to emit (publish) messages without explicit knowledge of which subscribing services will receive and process them. Publish-subscribe patterns give you the flexibility to add and remove subscribing services with little or no impact. This type of channel is the base of the event-driven architecture (read the next section).
There are many messaging systems to chose from. Some support standard protocols such as AMQP and STOMP. Other messaging systems have proprietary but documented protocols. There are a large number of open source messaging systems, including RabbitMQ, Apache Kafka, Apache ActiveMQ, and NSQ. At a high level, they all support some form of messages and channels. However, there are significant differences in the details of each broker’s messaging model.
Event-based Architecture
For most applications, the way to make microservices work and to manage distributed data successfully is to adopt an event-driven architecture.
In the event-driven or event-based architecture, a microservice publishes an event when something notable happens, such as when it updates a business entity. Other microservices subscribe to those events. When a microservice receives an event it can update its own business entities, which might lead to more events being published. The request-driven and event-driven application design models are complementary. Both can be useful and appropriate, depending on the type of business process being implemented.
The event-driven architecture is very good for systems where the producer does not care about who consumes the event and the consumer doesn't really care about who produced the event.
There are three main parts that make up the event driven system: events, tasks, and a message broker.
Events are notifications that tell subscribed applications when something has happened. Applications subscribe to certain events and respond by creating tasks for themselves. Events should never modify the state directly.
Tasks are actions which modify the state. The only thing that can create a task for a given application is the application itself. This way, services cannot directly modify each other‘s states.
Strict naming conventions help maintain consistency and clarity when it comes to naming events and tasks. Tasks start with the application name to ensure they‘re only handled by the intended application. Next, comes the model whose state is to be modified by the task, followed by a descriptive present-tense verb. An example of a task would be api.user.authorize. Based on the convention we know this task is handled by the api service, and it wants to perform an authorize on a user object.
Events have no application name because they can be subscribed to by multiple applications. They start with the model and end with a past-tense verb that describes what has happened. An example of an event would be user.authorized.
The message broker mediates communication amongst applications, minimizing the mutual awareness that applications should have of each other in order to be able to exchange messages, effectively implementing decoupling.
This picture represents a micro-service architecture that combines event-based messaging and request/response communication.
Handling failures
In a distributed system there is the ever-present risk of partial failure. Since clients and services are separate processes, a service might not be able to respond in a timely way to a client’s request. A service might be down because of a failure or for maintenance. Or the service might be overloaded and responding extremely slowly to requests. Here are some suggestions to handle and troubleshoot the failures:
- Implement a circuit breaker pattern by cutting off the listener of an event until it is healthy again. Only the worker needs to change, not all the callers of the service.
- Keep detailed logs which allow us to trace what happened in the system leading up to an error situation. The "What happened" will often span several microservices.
- Use correlation tokens that are passed along from microservice to microservice in any communication that stems from that end-user request. The microservice logs should include the correlation token to make it easy to group them during troubleshooting.
- Roll forward instead of backward. Microservices are supposed to be continuously deployed, this makes them easy to fix and deliver.
- Don't propagate failure.
- Embed failover scenarios into your microservices code.
Best practices
The Netflix development team suggests the following set of best practices:
- Create a Separate Data Store for Each Microservice
- Keep the Code at a Similar Level of Maturity
- Do a Separate Build for Each Microservice
- Deploy in Containers
- Treat Servers as Stateless
The 12-factor applications
The twelve-factor app is a methodology for building software-as-a-service apps that:
- Use declarative formats for setup automation, to minimize time and cost for new developers joining the project;
- Have a clean contract with the underlying operating system, offering maximum portability between execution environments;
- Are suitable for deployment on modern cloud platforms, obviating the need for servers and systems administration;
- Minimize divergence between development and production, enabling continuous deployment for maximum agility;
- And can scale up without significant changes to tooling, architecture, or development practices.
Recommended reads
https://en.wikipedia.org/wiki/Microservices
https://martinfowler.com/bliki/BoundedContext.html
https://martinfowler.com/articles/microservice-trade-offs.html
http://www.slideshare.net/ConfluentInc/microservices-in-the-apache-kafka-ecosystem
http://www.slideshare.net/ewolff/rest-vs-messaging-for-microservices
http://blog.runnable.com/post/150022242931/event-driven-microservices-using-rabbitmq
https://blog.codecentric.de/2016/08/event-driven-microservices-event-processing/
https://capgemini.github.io/architecture/is-rest-best-microservices/
https://www.nginx.com/blog/microservices-at-netflix-architectural-best-practices/




浙公网安备 33010602011771号