Chapter 9. Architecture of Individual Microservices

When microservices are implemented, close attention must be paid to a number of key points. First, this chapter addresses the domain architecture of microservices (section 9.1). Next up is CQRS (Command Query Responsibility Segregation) (section 9.2), which can be interesting when implementing a microservice-based system. This approach separates data writes from data reads. Event Sourcing (section 9.3) places events at the center of the modeling. The structure of a microservice can correspond to a hexagonal architecture (section 9.4), which subdivides functionality into a logic kernel and adapters. Section 9.5 focuses on resilience and stability—essential requirements for microservices. Finally, technical approaches for the implementation of microservices, such as Reactive, are discussed in section 9.6.

9.1 Domain Architecture

The domain architecture of a microservice defines how the microservice implements its domain-based functionality. A microservice-based architecture should not aim to predetermine this decision for all microservices. The internal structure of each microservice should be decided independently. This enables the teams to act largely autonomously of each other. It is sensible to adhere to established rules in order to keep the microservice easy to understand, simple to maintain, and also replaceable. However, there is no strict need for regulations at this level.

This section details how to identify potential problems with the domain architecture of a microservice. Once a potential issue has been discovered, the team responsible for the microservice will need to determine whether it constitutes a real problem and how it can be solved.

Cohesion

The domain architecture of the overall system influences the domain architecture of the individual microservices. As presented in section 7.1, microservices should be loosely coupled to each other and have high cohesion internally. This means that a microservice should have only one responsibility with regard to the domain. If microservices are not highly cohesive, then most likely the microservice has more than one responsibility. If the cohesion within the microservice is not high enough, the microservice can be split into several microservices. The split ensures that the microservices remain small and thus are easier to understand, maintain, and replace.

Encapsulation

Encapsulation means that part of the architecture hides internal information from the outside—particularly internal data structures. Access should instead occur only through an interface. This makes sure that the software remains easy to modify, because internal structures can be changed without influencing other parts of the system. For this reason, microservices should never allow other microservices access to their internal data structures. If they do, then these data structures can no longer be modified. In order to use another microservice, only the interface for that microservice needs to be understood. This improves the structure and intelligibility of the system.

Domain-Driven Design

Domain-driven design (DDD) is one way to internally structure microservices. Each microservice can have a DDD domain model. The patterns required from domain-driven design were introduced in section 3.3. When domain-driven design and strategic design define the structure of the overall system (section 7.1), the microservices should also use these approaches. During the development of the overall system strategic design is concerned with the domain models that exist and how these are distributed across the microservices.

Transactions

Transactions bundle multiple actions that should only be executed together or not at all. It is difficult for a transaction to span more than one microservice. Only messaging is able to support transactions across microservices (see section 8.4). The domain-based design within a microservice ensures that each operation at the interface only corresponds to one transaction. By doing this it is possible to avoid having multiple microservices participating in one transaction. This would be very hard to implement technically.

9.2 CQRS

Systems usually save a state. Operations can change data or read it. These two types of operations can be separated: Operations that change data and therefore have side effects (commands) can be distinguished from operations that just read data (queries). It is also possible to stipulate that an operation should not simultaneously change the state and return data. This distinction makes the system easier to understand: When an operation returns a value, it is a query and does not change any values. This leads to additional benefits. For example, queries can be provided by a cache. If read operations can also change data, then the addition of a cache becomes more difficult since operations with side effects still have to be executed. The separation between queries and commands is called CQS (Command Query Separation). This principle is not limited to microservices, but can be applied more generally. For example, classes in an object-oriented system can divide operations in the same manner.

CQRS

CQRS (Command Query Responsibility Segregation)1 is more extreme than CQS and completely separates the processing of queries and commands.

1. https://speakerdeck.com/owolf/cqrs-for-great-good-2

Figure 9.1 shows the structure of a CQRS system. Each command is stored in the command store. In addition, there can be command handlers. The command handler in the example uses the commands for storing the current state of the data in a database. A query handler uses this database to process queries. The database can be adjusted to the needs of the query handler. For example, a database for the analysis of order processes can look completely different from a database that customers use for displaying their own order processes. Entirely different technologies can be employed for the query database. For instance, it is possible to use an in-memory cache, which loses data if there is a server failure. Information persistency is ensured by the command store. In an emergency the contents of the cache can be reconstructed by the command store.

Image

Figure 9.1 Overview of CQRS

Microservices and CQRS

CQRS can be implemented with microservices:

• The communication infrastructure can implement the command queue when a messaging solution is used. With approaches such as REST a microservice has to forward the commands to all interested command handlers and implement the command queue that way.

• Each command handler can be a separate microservice and can handle the commands with its own logic. This enables logic to be very easily distributed to multiple microservices.

• A query handler can also be a separate microservice. The changes to the data which the query handler uses can be introduced by a command handler in the same microservice. However, the command handler can also be a separate microservice. In that situation the query handler has to offer a suitable interface for accessing the database so that the command handler can change the data.

Advantages

CQRS has a number of benefits particularly when it comes to the interplay between microservices:

• Reading and writing of data can be separated into individual microservices. This makes possible even smaller microservices. When the writing and reading is so complex that a single microservice for both would get too large and too hard to understand, a split might make sense.

• Also a different model can be used for writing and reading. Microservices can each represent a Bounded Context and therefore use different data models. For instance, in an e-commerce shop a lot of data may be written for an online purchase while statistical evaluations read only a small part of that data for each purchase. From a technical perspective the data can be optimized for reading operations via denormalization or via other means for certain queries.

• Writing and reading can be scaled differently by starting a different number of query handler microservices and command handler microservices. This supports the fine-grained scalability of microservices.

• The command queue helps to handle any load peaks that occur during writing. The queue buffers the changes that are then processed later on. However, this does mean that a change to the data will not immediately be taken into consideration by the queries.

• It is easy to run different versions of the command handlers in parallel. This facilitates the deployment of new versions of the microservices.

CQRS can serve to make microservices even smaller, even when operations and data are very closely connected. Each microservice can independently decide for or against CQRS.

There are different ways to implement an interface that offers operations for changing and reading data. CQRS is only one option. Both aspects can also be implemented without CQRS in just one microservice. The freedom to be able to use different approaches is one of the main benefits of microservice-based architectures.

Challenges

CQRS also brings some challenges:

• Transactions that contain both read and write operations are hard to implement. The read and write operations may be implemented in different micro-services. This may mean it is very difficult to combine the operations into one transaction since transactions across microservices are usually impossible.

• It is hard to ensure data consistency across different systems. The processing of events is asynchronous, meaning that different nodes can finish processing at different points in time.

• The cost for development and infrastructure is higher. More system components and more complex communication technologies are required.

It is not wise to implement CQRS in every microservice. However, the approach can be valuable for microservice-based architectures in many circumstances.

9.3 Event Sourcing

Event Sourcing2 has a similar approach to CQRS. However, the events from Event Sourcing differ from the commands from CQRS. Commands are specific: They define exactly what is to be changed in an object. Events contain information about something that has happened. Both approaches can be combined: A command can change data. This will result in events that other components of the system can react to.

2. http://slideshare.net/mploed/event-sourcing-introduction-challenges

Instead of the maintaining state itself Event Sourcing stores the events that have led to the current state. While the state itself is not saved, it can be reconstructed from the events.

Figure 9.2 gives an overview of Event Sourcing:

• The event queue sends all events to the different recipients. It can, for instance, be implemented with messaging middleware.

• The event store saves all events. This makes it possible to reconstruct the chain of events and the events themselves.

• An event handler reacts to the events. It can contain business logic that reacts to events.

• In such a system it is only the events that are easy to trace. The current state of the system is not easy to follow up on. Therefore, it can be sensible to maintain a snapshot that contains the current state. At each event or after a certain period of time the data in the snapshot will be changed to bring it up-to-date with the new events. The snapshot is optional. It is also possible to reconstruct the state from the events in an ad hoc manner.

Events may not be changed afterwards. Erroneous events have to be corrected by new events.

Image

Figure 9.2 Overview of Event Sourcing

Event Sourcing is based on domain-driven design (see section 3.3). To adhere to the concept of Ubiquitous Language, the events should have names that also make sense in the business context. In some cases, an event-based model makes particular sense from a domain perspective. For instance, bookings to an account can be considered as events. Requirements like auditing are very easy to implement with Event Sourcing. Because the booking is modeled as an event, it is very easy to trace who has performed which booking. In addition, it is relatively easy to reconstruct a historical state of the system and old versions of the data. So Event Sourcing can be a good choice from a domain perspective. Generally, approaches like Event Sourcing make sense in complex domains which also benefit from domain-driven design.

Event Sourcing has similar advantages and disadvantages to CQRS, and both approaches can easily be combined. Event Sourcing makes particular sense when the overall system works with an event-driven architecture (section 7.8). In this type of system, the microservices already send events relating to changes of state, and it is logical to also use this approach in the microservices.


Try and Experiment

Choose a project you know.

• In which places would Event Sourcing make sense? Why? Would Event Sourcing be usable in an isolated manner in some places, or would the entire system have to be changed to events?

• Where could CQRS be helpful? Why?

• Do the interfaces adhere to the CQR rule? If they do, then the read and write operations would have to be separate in all interfaces.


9.4 Hexagonal Architecture

A hexagonal architecture3 focuses on the logic of the application (see Figure 9.3). The logic contains only business functionality. It has different interfaces, each of which are represented by an edge of the hexagon. In the example shown, these are the interfaces for the interaction with users and the interface for administrators. Users can utilize these interfaces via a web interface implemented by HTTP adapters. For tests there are special adapters enabling the tests to simulate users. Finally, there is an adapter that makes the logic accessible via REST. This enables other microservices to call the logic.

3. http://alistair.cockburn.us/Hexagonal+architecture

Image

Figure 9.3 Overview of Hexagonal Architecture

Interfaces don’t just take requests from other systems; they are also used to initiate contact with other systems. In the example the database is accessed via the DB adapter—an alternative adapter is provided for test data. Another application can be contacted via the REST adapter. Instead of these adapters a test adapter can be used to simulate the external application.

Another name for hexagonal architecture is “ports and adapters.” Each facet of the application like user, admin, data, or event is a port. The adapters implement the ports based on technologies like REST or web user interfaces. Through the ports on the right side of the hexagon the application fetches data, while the ports on the left side offer the system’s functionality to users and other systems.

The hexagonal architecture divides a system into a logic kernel and adapter. Only the adapters can communicate with the outside.

Hexagons or Layers?

A hexagonal architecture is an alternative to a layered architecture. In a layered architecture there is a layer in which the UI is implemented and a layer in which the persistence is implemented. In a hexagonal architecture there are adapters that are connected to the logic via ports. A hexagonal architecture enables more ports than just persistence and UI. The term “adapter” illustrates that the logic and the ports are supposed to be separate from the concrete protocols and implementations of the adapter.

Hexagonal Architectures and Microservices

It is very natural for hexagonal architectures to offer logic not only to other micro-services via a REST interface but also to users via a web UI. This concept is also the basis of microservices. They are supposed to not only provide logic for other microservices but should also support direct interaction by users through a UI.

Since individual test implementations can be implemented for all ports, the isolated testing of a microservice is easier with a hexagonal architecture. For this purpose, test adapters just have to be used instead of the actual implementation. The independent testing of individual microservices is an important prerequisite for the independent implementation and the independent deployment of microservices.

The logic required for resilience and stability (see section 9.5) or Load Balancing (section 7.12) can also be implemented in the adapter.

It is also possible to distribute the adapters and the actual logic into individual microservices. This will result in more distributed communication with its associated overhead. However, this does mean that the implementation of the adapter and kernel can be distributed to different teams. For instance, a team developing a mobile client can implement a specific adapter that is adapted to the bandwidth restrictions of mobile applications (see also section 8.1).

An Example

As an example of a hexagonal architecture, consider the order microservice shown in Figure 9.4. The user can make use of the microservice by placing orders through the web UI. There is also a REST interface, which gives other microservices or external clients use of the user functionality. The web UI, the REST interface, and the test adapter are three adapters for the user functionality of the microservice. The implementation with three adapters emphasizes that REST and web UI are just two ways to use the same functionality. It also leads to microservices that are implemented to integrate UI and REST. Technically the adapters can still be implemented in separate microservices.

Image

Figure 9.4 The Order Microservice as an Example for Hexagonal Architecture

Another interface is the order events. They announce to the Delivery microservice whenever new orders arrive so that the orders can be delivered. Through this interface the Delivery microservice also communicates when an order has been delivered or when delays have occurred. In addition, this interface can be served by an adapter for tests. This means that the interface to the delivery microservice does not just write data but can also introduce changes to the orders. This means the interface works in both directions: It calls other microservices but can also be used by other microservices to change data.

The hexagonal architecture has a domain-based distribution into an interface for user functionality and an interface for order events. That way, the architecture underlines the domain-based design.

The state of the orders is saved in a database. There is also an interface where test data can be used for tests instead of the database. This interface corresponds to the persistence layer of a traditional architecture.

Finally, there is an interface that uses data replication to transmit order information to reporting. There statistics can be generated from the orders. Reporting appears to be a persistence interface but is really more: The data is not just stored, but changed to enable quick generation of statistics.

As the example shows, a hexagonal architecture creates a good domain-based distribution into different domain-based interfaces. Each domain-based interface and each adapter can be implemented as a separate microservice. This makes possible the division of the application into numerous microservices, if necessary.


Try and Experiment

Choose a project you know.

• Which individual hexagons would there be?

• Which ports and adapters would the hexagons have?

• Which advantages would a hexagonal architecture offer?

• What would the implementation look like?


9.5 Resilience and Stability

In a well-designed microservices-based system, the failure of a single microservice should have a minimal impact on the availability of other microservices in the system. As microservice-based systems are, by their very nature, distributed, the danger of a failure is fundamentally higher than with other architectural styles: Networks and servers are unreliable. As microservices are distributed onto multiple servers, the number of servers is higher per system, and this also increases the chances of a failure. When the failure of one microservice results in the failure of additional microservices, a cascade effect can result in the entire system breaking down. This should be avoided.

For this reason, microservices have to be shielded from the failure of other microservices. This property is called resilience. The necessary measures to achieve resilience have to be part of the microservice. Stability is a broader term that denotes high software availability. Release It!4 lists several patterns on this topic.

4. Michael T. Nygard. 2007. Release It!: Design and Deploy Production-Ready Software. Raleigh, NC: Pragmatic Programmers.

Timeout

Timeouts help to detect when a target system is unavailable during a communication with that system. If no response has been returned after the timeout period, the system being called is considered to be unavailable. Unfortunately, many APIs do not have methods to define timeouts, and some default timeouts are very high. For example, at the operating system level, default TCP timeouts can be as high as five minutes. During this time the microservice cannot respond to callers since the service is waiting for the other microservice. This may lead to requests to the calling microservice appearing to have failed. It is also possible that the request can block a thread during this time. At some point all threads are blocked, and the microservice can no longer receive any further requests. This type of cascade effect needs to be avoided. When the API intends a timeout to be used for accessing another system or a database, this timeout should be set. An alternative option is to let all requests to external systems or databases take place in an extra thread and to terminate this thread after a timeout.

Circuit Breaker

A circuit breaker is a safety device used in electrical circuits. In the event of a short circuit the circuit breaker interrupts the flow of electricity to avoid a dangerous situation occurring, such as overheating or fire. This idea can be applied to software as well: When another system is no longer available or returns errors, a Circuit Breaker design feature prevents calls going to that system. After all, there is no point in making calls to a broken system.

Normally the Circuit Breaker is closed and calls are forwarded to the target system. When an error occurs, depending on the error frequency, the Circuit Breaker will be opened. Calls will no longer be sent on to the target system but will instead return an error. The Circuit Breaker can also be combined with a timeout. When the timeout parameters are exceeded, the Circuit Breaker is opened.

This takes load off the target system and means that the calling system does not need to wait for a timeout to occur, as the error is returned immediately. After some set period, the Circuit Breaker will close again. Incoming calls will once again be forwarded to the target system. If the error persists, the Circuit Breaker will open again.

The state of the Circuit Breakers in a system can highlight where problems are currently occurring to operations staff. An open Circuit Breaker indicates that a microservice is no longer able to communicate with another microservice. Therefore, the state of the Circuit Breaker should be part of the monitoring done by operations.

When the Circuit Breaker is open, an error does not necessarily have to be generated. It is also possible to simply degrade the functionality. Let us assume that an automated teller machine (ATM) cannot verify whether an account contains enough money for the desired withdrawal because the system that is responsible is not reachable. Nevertheless, cash withdrawals can be permitted up to a certain limit so that customers do not get annoyed by the failure, and the bank can continue to make the associated withdrawal fees. Whether a cash withdrawal is allowed and up to what limit is a business decision. The possible damage has to be balanced against the potential for profit. There can also be other rules applied in case of the failure of a system. Calls can be answered from a cache, for instance. More important than the technical options is the domain-based requirement for deciding on the appropriate handling of a system failure.

Bulkhead

A bulkhead is a special door on a ship which can be closed in a watertight manner. It divides the ship into several areas. When water gets in, only a part of the ship should be affected, and therefore the ship stays afloat.

Similar approaches are applicable to software: the entire system can be divided into individual areas. A breakdown or a problem in one area should not affect the other areas. For example, there can be several different instances of a microservice for different clients. If a client overloads the microservices, the other clients will not be negatively affected. The same is true for resources like database connections or threads. When different parts of a microservice use different pools for these resources, one part cannot block the other parts, even if it uses up all its resources.

In microservices-based architectures the microservices themselves form separate areas. This is particularly true when each microservice brings its own virtual machine along. Even if the microservice causes the entire virtual machine to crash or overloads it, the other microservices will not be affected. They run on different virtual machines and are therefore separate.

Steady State

The term steady state is used to describe systems that are built in a way that makes possible their continuous operation. For instance, this would mean that a system should not store increasing amounts of data. Otherwise the system will have used up its entire capacity at some point and break down. Log files, for example, have to be deleted at some point. Usually they are only interesting during a certain time interval anyway. Another example is caching: when a cache keeps growing, it will at some point fill all available storage space. Therefore, values in the cache have to be flushed at some point to keep the cache size from continuously increasing.

Fail Fast

Timeouts are necessary only because another system may need a long time to respond. The idea behind Fail Fast is to address the problem from the other side: Each system should recognize errors as quickly as possible and indicate them immediately. When a call requires a certain service and that service is unavailable at the moment, the call can be directly answered with an error message. The same is true when other resources are not available at the time. Also, a call should be validated right at the start. When it contains errors, there is nothing to be gained by processing it and an error message can be returned immediately. The benefits of Fail Fast are identical to those offered by timeouts: A rapid failure uses up less resources and therefore results in a more stable system.

Handshaking

Handshaking in a protocol serves to initiate communication. This feature of protocols gives servers the opportunity to reject additional calls when the server is overloaded. This can help to avoid additional overload, a breakdown, or responses that are too slow. Unfortunately, protocols like HTTP do not support this. Therefore, the application has to mimic the functionality with, for instance, health checks. An application can signal that it is, in principle, reachable but has so much load at the moment that it is unable to handle further calls. Protocols that build on socket connections can implement these type of approaches by themselves.

Test Harness

A Test Harness can be used to find out how an application behaves in certain error situations. Those problems might be at the level of TCP/IP or, say, responses of other systems that contain an HTTP header but no HTTP body. Theoretically, something like that should never occur since the operating system or network stack should deal with it. Nevertheless, such errors can occur in practice and have dramatic consequences if applications are not prepared to handle them. A Test Harness can be an extension of the tests that are discussed in section 10.8.

Uncoupling via Middleware

Calls in a single program only ever function on the same host at the same time in the same process. Synchronous distributed communication (REST) enables communication between different hosts and different processes at the same time. Asynchronous communication via messaging systems (section 8.4) also enables an uncoupling over time. A system should not wait for the response of an asynchronous process. The system should continue working on other tasks instead of just waiting for a response. Errors that cause one system after another to break down like dominoes are much less likely when using asynchronous communication. The systems are forced to deal with long response times since asynchronous communication often means long response times.

Stability and Microservices

Stability patterns like Bulkheads restrict failures to a unit. Microservices are the obvious choice for a unit. They run on separate virtual machines and are therefore already isolated with regard to most issues. This means that the bulkhead pattern arises very naturally in a microservices-based architecture. Figure 9.5 shows an overview: A microservice using Bulkheads, Circuit Breakers, and Timeouts can safeguard the use of other microservices. The used microservice can additionally implement fail fast. The safeguarding can be implemented via patterns in those parts of a microservice that are responsible for communicating with other microservices. This enables this aspect to be implemented in one area of the code and not distributed across the entire code.

Image

Figure 9.5 Stability in the Case of Microservices

On a technical level the patterns can be implemented in different ways. For micro-services there are the following options:

Timeouts are easy to implement. When another system is accessed, an individual thread is started that is terminated after a timeout.

• At first glance Circuit Breakers are not very complex and can be developed in your own code. However, any implementation must work under high load and has to offer an interface for operations to enable monitoring. This is not trivial. Therefore, a home-grown implementation is often not sensible.

Bulkheads are an inherent feature of microservices since a problem is, in many cases, already limited to just one microservice. For instance, a memory leak will only cause one microservice to fail.

Steady State, Fail Fast, Handshaking and Test Harness have to be implemented by each microservice.

• Uncoupling via middleware is an option for shared communication of microservices.

Resilience and Reactive

The Reactive Manifesto5 lists resilience as an essential property of a Reactive application. Resilience can be implemented in an application by processing calls asynchronously. Each part of an application which processes messages (actor) has to be monitored. When an actor does not react anymore, it can be restarted. This enables errors to be handled and makes applications more resilient.

5. http://www.reactivemanifesto.org/

Hystrix

Hystrix6 implements Timeout and Circuit Breaker. To achieve this, developers have to encapsulate calls in commands. Alternatively, Java annotations can be used. The calls take place in individual thread pools, and several thread pools can be created. If there is one thread pool per called microservice, the calls to the microservices can be separated from each other in such a manner that a problem with one microservice does not affect the use of the other microservices. This is in line with the Bulkhead concept. Hystrix is a Java library that is made available under the Apache license and originates from the Netflix stack. The example application uses Hystrix together with Spring Cloud (see section 13.10). In combination with a sidecar, Hystrix can also be used for applications that are not written in Java (see section 7.9). Hystrix supplies information about the state of the thread pools and the Circuit Breaker for monitoring and operational purposes. This information can be displayed in a special monitoring tool—the Hystrix dashboard. Internally, Hystrix uses the Reactive Extensions for Java (RxJava). Hystrix is the most widely used library in the area of resilience.

6. https://github.com/Netflix/Hystrix/


Try and Experiment

• This chapter introduced eight patterns for stability. Prioritize these patterns. Which properties are indispensable? Which are important? Which are unimportant?

• How can it be verified that the microservices actually implement the patterns?


9.6 Technical Architecture

The technical architecture of each microservice can be individually designed. Frameworks or programming languages do not have to be uniform for all microservices. Therefore, each microservice may well use different platforms. However, certain technical infrastructures fit microservices better than others.

Process Engines

Process engines, which typically serve to orchestrate services in an SOA (section 6.1), can be used within a microservice to model a business process. The important point is that one microservice should implement only one domain—that is, one Bounded Context. A microservice should not end working purely to integrate or orchestrate other microservices without its own logic. When this happens, changes will affect not just the responsible microservice but also the microservice responsible for integration/orchestration. However, it is a central objective of microservice-based architectures that changes should be limited to one microservice whenever possible. If multiple business processes have to be implemented, different microservices should be used for these. Each of these microservices should implement one business process together with the dependent services. Of course, it will not always be possible to avoid other microservices having to be integrated to implement a business process. However, a microservice that just represents an integration is not sensible.

Statelessness

Stateless microservices are very beneficial. To put it more clearly, microservices should not save any state in their logic layer. States held in a database or on the client side are acceptable. When using a stateless approach, the failure of an individual instance does not have a big impact. The instance can just be replaced by a new instance. In addition, the load can be distributed between multiple instances without having to take into consideration which instance processed the previous calls of the user. Finally, the deployment of a new version is easier since the old version can just be stopped and replaced without having to migrate its state.

Reactive

Implementing microservices with Reactive7 technologies can be particularly useful. These approaches are comparable to Erlang (see section 14.7): Applications consist of actors. In Erlang they are called processes. Work in each actor is sequential; however, different actors can work in parallel on different messages. This enables the parallel processing of tasks. Actors can send messages to other actors that end up in the mailboxes of these actors. I/O operations are not blocking in Reactive applications: A request for data is sent out. When the data is there, the actor is called and can process the data. In the meantime, the actors can work on other requests.

7. http://www.reactivemanifesto.org/

Essential properties according to the Reactive Manifesto:

• Responsive: The system should react to requests as fast as possible. This has among others advantages for fail fast and therefore for stability (see section 9.5). Once the mailbox is filled to a certain predetermined degree, the actor can, for instance, reject or accept additional messages. This results in the sender being slowed down and the system does not get overloaded. Other requests can still be processed. The aim of being responsive is also helped if blocking I/O operations are not used.

• Resilience and its relationship with Reactive applications has already been discussed in section 9.5.

• Elastic means that new systems can be started at run times that share the load. To achieve this, the system has to be scalable, and it has to be possible to change the system at run time in such a way that the load can be distributed to the different nodes.

• Message Driven means that the individual components communicate with each other via messages. As described in section 8.4, this communication fits well with microservices. Reactive applications also use very similar approaches within the application itself.

Reactive systems are particularly easy to implement using microservices and the concepts from Reactive fit neatly with microservices’ concepts. However, similarly good results can also be achieved by the use of more traditional technologies.

Some examples of technologies from the Reactive arena are:

• The programming language Scala8 with the Reactive framework Akka9 and the web framework Play10 is based on it. These frameworks can also be used with Java.

8. http://www.scala-lang.org/

9. http://akka.io/

10. https://www.playframework.com/

• There are Reactive extensions11 for practically all popular programming languages. Among them are RxJava12 for Java or RxJS13 for JavaScript.

11. http://reactivex.io/

12. https://github.com/ReactiveX/RxJava

13. https://github.com/Reactive-Extensions/RxJS

• Similar approaches are also supported by Vert.x14 (see also section 14.6). Even though this framework is based on the JVM, it supports many different programming languages like Java, Groovy, Scala, JavaScript, Clojure, Ruby, and Python.

14. http://vertx.io/

Microservices without Reactive?

Reactive is only one way to implement a system with microservices. The traditional programming model with blocking I/O, without actors, and with synchronous calls is also suitable for this type of system. As previously discussed, resilience can be implemented via libraries. Elasticity can be achieved by starting new instances of the microservices, for instance, as virtual machines or Docker containers. Additionally, traditional applications can also communicate with each other via messages. Reactive applications have benefits for responsiveness. However, in that case it has to be guaranteed that operations really do not block. For I/O operations Reactive solutions can usually ensure that. However, a complex calculation can block the system. This may mean that no messages can be processed anymore, and the entire system is blocked. A microservice does not have to be implemented with Reactive technologies, but they are certainly an interesting alternative.


Try and Experiment

Get more information about Reactive and microservices.

• How exactly are the benefits achieved and implemented?

• Is there a Reactive extension for your preferred programming language? Which features does it offer? How does this help with implementing microservices?


9.7 Conclusion

The team implementing a particular microservice is also responsible for its domain-based architecture. There should be only a small number of guidelines restricting team decisions so that the independence of the teams is maintained.

Low cohesion can be an indication of a problem with the domain-based design of a microservice. Domain-driven design (DDD) is an interesting way to structure a microservice. Transactions can also provide clues for an optimized domain-based division: An operation of a microservice should be a transaction (section 9.1).

CQS (command–query separation) divides operations of a microservice or a class into read operations (queries) and write operations (commands). CQRS (command–query responsibility segregation) (section 9.2) separates data changes via commands from query handlers, which process requests. This means that microservices or classes are created that can only implement reading or writing services. Event Sourcing (section 9.3) stores events and does not focus on the current state but on the history of all events. These approaches are useful for building up microservices because they enable the creation of smaller microservices that implement only read or write operations. This enables independent scaling and optimizations for both types of operations.

Hexagonal architecture (section 9.4) focuses on a kernel that can be called via adapters, for instance, by a UI or an API, as the center point of each microservice. Likewise, adapters can enable the use of other microservices or of databases. For microservices this results in an architecture that supports a UI and a REST interface in a microservice.

Section 9.5 presented patterns for Resilience and Stability. The most important of those are Circuit Breaker, Timeout and Bulkhead. A popular implementation is Hystrix.

Section 9.6 introduced certain technical choices for microservices: For instance, the use of process engines is a possibility for a microservice. Statelessness is beneficial. And finally, reactive approaches are a good basis for the implementation of microservices.

In summary, this chapter explained some essential considerations for the implementation of individual microservices.

Essential Points

• Microservices within a microservice-based system can have different domain-based architectures.

• Microservices can be implemented internally with Event Sourcing, CQRS, or hexagonal architectures.

• Technical properties like stability can only be implemented individually by each microservice.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset