Chapter 8. Integration and Communication

Microservices have to be integrated, and they need to communicate. This can be achieved at different levels (see Figure 8.1). Each approach has certain advantages and disadvantages, and at each level different technical implementations of integration are possible.

• Microservices contain a graphical user interface. This means that microservices can be integrated at the UI level. This type of integration is introduced in section 8.1.

• Microservices can also be integrated at the logic level. They can use REST (section 8.2), SOAP, remote-procedure call (RPC); (section 8.3), or messaging (section 8.4) to achieve this.

• Finally, integration can be performed at the database level using data replication (section 8.5).

Image

Figure 8.1 Different Levels of Integration

General rules for the design of interfaces are provided in section 8.6.

8.1 Web and UI

Microservices should bring their own UI along with them. By having the UI included with the relevant microservice, changes to that microservice that affect the UI can be done in one place. It is then necessary to integrate the UIs of the microservices together to form the system as a whole. This can be achieved using different approaches, which are reviewed in the innoQ Blog.1, 2

1. https://www.innoq.com/blog/st/2014/11/web-based-frontend-integration/

2. https://www.innoq.com/en/blog/transclusion/

Multiple Single-Page-Apps

Single-page-apps (SPA)3 implement the entire UI with just one HTML page. The logic is implemented in JavaScript, which dynamically changes parts of the page. The logic can also manipulate the URL displayed in the browser so that bookmarks and other typical browser features can be used. However, SPAs do not conform to the way the web was originally designed; they demote HTML from being the central web technology and have most of their logic implemented in JavaScript. Traditional web architectures implement logic almost exclusively on the server.

3. http://en.wikipedia.org/wiki/Single-page_application

SPAs are particularly useful when complex interactions or offline capability are required. Google’s Gmail is an example that helped to shape the meaning of the term SPA. Traditionally, mail clients have been native applications; however, Gmail as a SPA is able to offer nearly the same user experience.

There are different technologies for the implementation of single-page-apps:

• AngularJS4 is very popular. Among other features, AngularJS has bidirectional UI data-binding: if the JavaScript code assigns a new value to an attribute of a bound model, the view components displaying the value are automatically changed. The binding also works from UI to the code: AngularJS can bind a user input to a JavaScript variable. Furthermore, AngularJS can render HTML templates in the browser. This enables JavaScript code to generate complex DOM structures. The entire front-end logic can be implemented in JavaScript code running on the browser. AngularJS was created by Google, which released the framework under the very liberal MIT license.

4. https://angularjs.org/

• Ember.js5 follows the “convention over configuration” principle and represents essentially the same feature set as AngularJS. Through the supplementary module Ember Data, it offers a model-driven approach for accessing REST resources. Ember.js is made available under the MIT license and is maintained by developers from the open-source community.

5. http://emberjs.com/

• Ext JS6 offers an MVC approach and also components which developers can compose to build a UI similar to the way they would for rich client applications. Ext JS is available as open source under GPL v3.0. However, for commercial development a license has to be bought from the creators Sencha.

6. http://www.sencha.com/products/extjs/

SPA per Microservice

When using microservices with single-page apps each microservice can bring along its own SPA (see Figure 8.2). The SPA can, for instance, call the microservice via JSON/REST. This is particularly easy to implement with JavaScript. Links can then be used to join the different SPAs together.

Image

Figure 8.2 Microservices with Single-Page Apps

This enables the SPAs to be completely separate and independent. New versions of a SPA and of the associated microservice can be rolled out with ease. However, a tighter integration of SPAs is difficult. When the user switches from one SPA to another, the browser loads a new web page and starts a different JavaScript application. Even modern browsers need time to do this, and therefore this approach is only sensible when switching between SPAs is rare.

Asset Server for Uniformity

SPAs can be heterogeneous, and each can bring along its own individually designed UI. This can be a problem as it can lead to a UI that is not uniform across the system as a whole. This issue can be resolved by using an asset server. This type of server is used to provide JavaScript files and CSS files for the applications. When the SPAs of the microservices are only allowed to access these kinds of resources via the asset server, a uniform user interface can be achieved. To accomplish this, a proxy server can distribute requests to the asset server and the microservices. From the web browser’s perspective, it looks as if all resources, as well as the microservices, have a shared URL. This approach avoids security rules that prohibit the use of content that originates from different URLs. Caching can then reduce the time for loading the applications. When only JavaScript libraries, which are stored on the asset server, can be used, the choice of technologies for the microservices is reduced. Uniformity and free technology choice are competing aims.

The shared assets will create code dependencies between the asset server and all microservices. A new version of an asset requires the modification of all micro-services that use this asset—they have to modified in order to use the new version. Such code dependencies endanger independent deployment and should therefore be avoided. Code dependencies in the back end are often a problem (see section 7.3). In fact, such dependencies should also be reduced in the front-end. This can mean that an asset server causes more problems than it solves.

UI guidelines, which describe the design of the application in more detail and help to establish a uniform approach at different levels, can be helpful. This enables the implementation of a uniform UI without a shared asset server and the related code dependencies.

In addition, SPAs need to have a uniform authentication and authorization scheme so that the users do not have to log in multiple times. An OAuth2 or a shared signed cookie can be a solution to this (see also section 7.14).

JavaScript can only access data that is available under the domain from which the JavaScript code originates. This “same-origin policy” prevents JavaScript code from reading data from other domains. When a proxy makes all microservices accessible to the outside world under the same domain, this is no longer a limitation. Otherwise the policy has to be deactivated when the UI of a microservice needs to access data from another microservice. This problem can be solved with CORS (cross-origin resource sharing) where the server delivering the data allows JavaScript from other domains. Another option is to offer all SPA and REST services to the outside via a single domain so that cross-domain access is not required. This also allows access to shared JavaScript code on an asset server to be implemented.

A Single-Page App for All Microservices

The division into multiple SPAs results in a strict separation of the front-ends of the microservices. For instance, if an SPA is responsible for registering orders and another one for a fundamentally different use case like reports, the load time needed to change between SPAs is still acceptable. Potentially, the different SPAs might be used by different sets of users who never need to switch between them at all.

However, there are situations when a tighter integration of the microservices user interfaces is necessary. For example, when viewing an order, details about the items may need to be displayed. Displaying the order is the responsibility of one microservice, displaying the items is performed by another. To tackle this problem, the SPA can be distributed into modules (see Figure 8.3). Each module belongs to another microservice and therefore to another team. The modules should be deployed separately. They can be stored on the server in individual JavaScript files and use separate Continuous Delivery pipelines, for instance. There needs to be suitable conventions for the interfaces. For example, only the sending of events might be allowed. Events uncouple the modules because the modules only communicate changes in state, but not how other modules have to react to them.

Image

Figure 8.3 Close Integration of Microservices Sharing One Single-Page App

AngularJS has a module concept that enables the implementation of individual parts of the SPA in separate units. A microservice could provide an AngularJS module for displaying the user interface of the microservice. The model can then integrate, if necessary, AngularJS modules from other microservices.

However, such an approach has disadvantages:

• Deploying the SPA is often only possible as a complete application. When a module is modified, the entire SPA has to be rebuilt and deployed. This has to be coordinated between the microservices that provide the modules for the application. In addition, the deployment of the microservices on the server has to be coordinated with the deployment of the modules since the modules call the microservices. This requirement to coordinate the deployment of modules of an application should be avoided.

• The modules can call each other. Depending on the way calls are implemented, changes to a module can mean that other modules also have to be changed, for instance, because an interface has been modified. When the modules belong to separate microservices, this again requires a coordination across microservices, which should be avoided.

For SPA modules a much closer coordination is necessary than for links between applications. On the other hand, SPA modules offer the benefit that UI elements from different microservices can be simultaneously displayed to the user. However, this approach closely couples the microservices at the UI level. The SPA modules correspond to the module concepts that exist in other programming languages and cause a simultaneous deployment. This leads to the microservices, which really should be independent of each other, being combined at the UI level in one shared deployment artifact. Therefore, this approach undoes one of the most important benefits of a microservice-based architecture—independent deployment.

HTML Applications

Another way to implement the UI is with HTML-based user interfaces. Every microservice has one or more web pages that are generated on the server. These web pages can also use JavaScript. Here, unlike SPAs, only a new HTML web page and not necessarily an application, is loaded by the server when changing between web pages.

ROCA

ROCA (resource-oriented client architecture)7 proposes a way to arrange the handling of JavaScript and dynamic elements in HTML user interfaces. ROCA views itself as an alternative to SPAs. In ROCA the role of JavaScript is limited to optimizing the usability of the web pages. JavaScript can facilitate their use or can add effects to the HTML web pages. However, the application has to remain usable without JavaScript. It is not the purpose of ROCA that users really use web pages without JavaScript. The applications are only supposed to use the architecture of the web, which is based on HTML and HTTP. Also ROCA makes sure that all logic is actually implemented on the server instead of JavaScript on the client. That way other clients can use the very same logic.

7. http://roca-style.org/

When a web application is divided into microservices, ROCA reduces the dependencies and simplifies the division. Between microservices the coupling of the UI can be achieved by links. For HTML applications links are the usual tool for navigating between web pages and represent a natural integration. There are no foreign bodies as in the case of SPAs.

To help with the uniformity of the HTML user interfaces, the microservices can use a shared asset server the same was as SPAs can (see Figure 8.4). It contains all the CSS and JavaScript libraries. If the teams create design guidelines for the HTML web pages and look after the assets on the asset server, the user interfaces of the different microservices will be largely identical. However, as discussed previously, this will lead to code dependencies between the UIs of the microservices.

Image

Figure 8.4 HTML User Interface with an Asset Server

Easy Routing

To the outside world the microservices should appear like a single web application—ideally with one URL. This also helps with the shared use of assets since the same-origin-policy is not violated. However, user requests from the outside have to be directed to the right microservice. This is the function of the router. It can receive HTTP requests and forward them to one of the microservices. This can be done based on the URL. How individual URLs are mapped to microservices can be decided by rules that can be complex. The example application uses Zuul for this task (see section 13.9). Reverse proxies are an alternative. These can be web servers, like Apache httpd or nginx, that can direct requests to other servers. In the process the requests can be modified, URLs can, for instance, be rewritten. However, these mechanisms are not as flexible as Zuul, which is very easy to extend with home-grown code.

When the logic in the router is very complex, this can cause problems. If this logic has to be changed because a new version of a microservice is brought into production, an isolated deployment is no longer straightforward. This endangers the philosophy of independent development and deployment of the microservices.

Arrange HTML with JavaScript

In some cases, a closer integration is necessary. It might be that information originating from different microservices needs to be displayed on a single HTML web page. For example, a web page might display order data from one microservice and data concerning the ordered items from another microservice. In this situation one router is no longer sufficient. A router can only enable a single microservice to generate a complete HTML web page.

A simple solution that employs the architecture presented in Figure 8.4 is based on links. AJAX (Asynchronous JavaScript and XML) enables content from a link to be loaded from another microservice. JavaScript code calls the microservice. Once the HTML is received from the microservice the link is replaced with it. In the example a link to an item could be transformed into an HTML description of this item. This enables the logic for the presentation of a product to be implemented in one microservice, while the design of the entire web page is implemented in another microservice. The entire web page would be the responsibility of the order microservice, while the presentation of the products would be the responsibility of the product microservice. This enables the continued independent development of both microservices and for content to be displayed from both components. If the presentation of the items has to be changed or new products necessitate a revised presentation, these modifications can be implemented in the product microservice. The entire logic of the order microservice remains unchanged.

Another example for this approach is Facebook’s BigPipe.8 It optimizes not only the load time, but also enables the composition of web pages from pagelets. A custom implementation can use JavaScript to replace certain elements of the web page by other HTML. This can be links or div-elements like the ones commonly used for structuring web pages that can be replaced by HTML code.

8. https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919

However, this approach causes relatively long load times. It is mainly beneficial when the web UI already uses a lot of JavaScript and when there are not many transitions between web pages.

Front-End Server

Figure 8.5 shows an alternative way to achieve tight integration. A front-end server composes the HTML web page from HTML snippets, each of which are generated by a microservice. Assets like CSS and JavaScript libraries are also stored in the front-end server. Edge Side Includes (ESI) is a mechanism to implement this concept. ESI offers a relatively simple language for combining HTML from different sources. With ESI, caches can supplement static content—for instance, the skeleton of a web page—with dynamic content. This means that caches can help with the delivery of web pages, even ones that contain dynamic content. Proxies and caches like Varnish9 or Squid10 implement ESI. Another alternative is Server Side Includes (SSI). They are very similar to ESIs; however, they are not implemented in caches, but in web servers. With SSIs web servers can integrate HTML snippets from other servers into HTML web pages. The microservices can deliver components for the web page that are then assembled on the server. Apache httpd supports SSIs with mod_include.11 nginx uses the ngx_http_ssi_module12 for the support of SSIs.

9. https://www.varnish-cache.org/

10. http://www.squid-cache.org/

11. http://httpd.apache.org/docs/2.2/mod/mod_include.html

12. http://nginx.org/en/docs/http/ngx_http_ssi_module.html

Image

Figure 8.5 Integration Using a Front-End Server

Portals also consolidate information from different sources on one web page. Most products use Java portlets that adhere to the Java standard JSR 168 (Portlet 1.0) or JSR 286 (Portlet 2.0). Portlets can be brought into production independently of each other and therefore solve one of the major challenges surrounding microservice-based architectures. In practice these technologies frequently result in complex solutions. Portlets behave very differently to normal Java web applications technically making the use of many technologies from the Java environment either difficult or impossible. Portlets enable the user to compose a web page from previously defined portlets. In this way the user can assemble, for instance, their most important information sources on one web page. However, this is not really necessary for creating a UI for microservices. The additional features result in additional complexity. Therefore, portal servers that are based on portlets are not a very good solution for the web user interfaces of microservices. In addition, they restrict the available web technologies to the Java field.

Mobile Clients and Rich Clients

Web user interfaces do not need any software to be installed on the client. The web browser is the universal client for all web applications. On the server side the deployment of the web user interface can easily be coordinated with the deployment of the microservice. The microservice implements a part of the UI and can deliver the code of the web user interface via HTTP. This makes possible a relatively easy coordinated deployment of client and server.

For mobile apps, rich clients, and desktop applications the situation is different: software has to be installed on the client. This client application is a deployment monolith that has to offer an interface for all microservices. If the client application delivers functionality from different microservices to the user, it would technically have to be modularized, and the individual modules, like the associated micro-services, would have to be brought into production independently of each other. However, this is not possible since the client application is a deployment monolith. A SPA can also easily turn into a deployment monolith. Sometimes an SPA is used to separate the development of client and server. In a microservices context such a use of SPAs is undesirable.

When a new feature is implemented in a microservice, that also requires modifications of the client application. This change cannot be rolled out solely via a new version of the microservice. A new version of the client application also has to be delivered. However, it is unrealistic to deliver the client application over and over again for each small change of a feature. If the client application is being made available in the app store of a mobile platform, an extensive review of each version is necessary. If multiple changes are supposed to be delivered together, the change has to be coordinated. Additionally, the new version of the client application has to be coordinated with the microservices so that the new versions of the microservices are ready in time. This results in deployment dependencies between the microservices, which should ideally be avoided.

Organizational Level

At an organizational level there is often a designated team for developing the client application. In this manner the division into an individual module is also implemented at the organizational level. Especially when different platforms are supported, it is unrealistic to have one developer in each microservice team for each platform. The developers are going to form one team for each platform. This team has to communicate with all the microservice teams that offer microservices for mobile applications. This can necessitate a lot of communication, which microservices-based architecture sets out to avoid. Therefore, the deployment monolith poses a challenge for client applications at the organizational level (see Figure 8.6).

Image

Figure 8.6 Mobile Apps and Rich Client are Deployment Monoliths that Integrate Multiple Microservices

One possible solution is to develop new features initially for the web. Each micro-service can directly bring functionality onto the web. With each release of the client application these new features can then be included. However, this means that each microservice needs to support a certain set of features for the web application and, where required, another set for the client application. In exchange this approach can keep the web application and the mobile application uniform. It supports an approach where the domain-based teams provide features of the microservices to mobile users as well as to web users. Mobile applications and web applications are simply two channels to offer the same functionality.

Back-End for Each Front-End

However, the requirements can also be entirely different. For instance, the mobile application can be a largely independent application which is supposed to be developed further as independently of the microservices and the web user interface as possible. Often the use cases of the mobile application are so different from the use cases of the web application that a separate development is required due to the differences in features.

In this situation, the approach depicted in Figure 8.7 can be sensible: the team responsible for the mobile app or the rich client application has a number of developers who implement a special back-end. This enables functionality for the mobile app to be developed independently to the back-end, because at least a part of the requirements for the microservices can be implemented by developers from the same team. This should avoid logic for the mobile app being implemented in the microservice, when it really belongs in a back-end microservice. The back-end for a mobile application may differ from other APIs. Mobile clients have little bandwidth and a high latency. Therefore, APIs for mobile devices are optimized to operate with as few calls as possible and to only transfer really essential data. This is also true for rich clients—however not to the same extent. The adaption of an API to the specific requirements of a mobile application can be implemented in a microservice, which is built by the front-end team.

Image

Figure 8.7 Mobile Apps or Rich Clients with Their Own Back-End

A mobile app should be highly responsive to user interaction. This can be difficult to achieve when the user interaction means a microservice call, with its associated latency, is required. If there are multiple calls, the latency will increase further. Therefore, the API for a mobile app should be optimized to deliver the required data with as few calls as possible. These optimizations can also be implemented by a back-end for the mobile app.

The optimizations can be implemented by the team that is responsible for the mobile app. Doing this enables the microservices to offer universally valid interfaces while the teams responsible for the mobile apps can assemble their own special APIs by themselves. This leads to the mobile app teams not being so dependent on the teams that are responsible for the implementation of the microservices.

Modularizing web applications is simpler than modularizing mobile apps, especially when the web applications are based on HTML and not on SPAs. For mobile apps or rich client apps it is much more difficult since they form an individual deployment unit and cannot be easily divided.

The architecture shown in Figure 8.7 has a number of advantages and disadvantages. It makes it possible to reuse microservices for different clients and at the same time acts as an entry point into the layered architecture. However, the UI layer is now separated from the microservices and is implemented by another team. This leads to a situation where requirements have to be implemented by multiple teams. Microservices were meant to avoid exactly this situation. This architecture also risks logic being implemented in the services for the client application, when it really belongs in the microservices.


Try and Experiment

• This section presented alternative ways to implement web applications: an SPA per microservice, an SPA with modules per microservice, an HTML application per microservice, and a front-end server with HTML snippets. Which of these approaches would you choose? Why?

• How would you deal with mobile apps? One option would be a mobile app team with back-end developers—or would you rather choose a team without back-end developers?


8.2 REST

Microservices have to be able to call each other in order to implement logic together. This can be supported by different technologies.

REST (representational state transfer) is one way to enable communication between microservices. REST is the term for the fundamental approaches of the World Wide Web (WWW):

• There are a large number of resources which can be identified via URIs. URI stands for uniform resource identifier. It unambiguously and globally identifies resources. URLs are practically the same as URIs.

• The resources can be manipulated via a fixed set of methods. For instance, in the case of HTTP these are GET for requesting a resource, PUT for storing a resource and DELETE for deleting a resource. The methods’ semantics are rigidly defined.

• There can be different representations for resources—for instance as a PDF or HTML. HTTP supports the so-called content negotiation via the Accept header. This means that the client can determine which data representation it can process. The content negotiation enables resources to be made available in a way that is readable to humans and to provide them at the same time under the same URL in a machine-readable manner. The client can communicate via an Accept header whether it only accepts human-readable HTML or only JSON.

• Relationships between resources can be represented by links. Links can point to other microservices enabling the logic of different microservices to be integrated.

• The servers in a REST system are supposed to be stateless. Therefore, HTTP implements a stateless protocol.

The limited vocabulary represents the exact opposite of what object-oriented systems employ. Object-orientation focuses on a specific vocabulary with specific methods for each class. The REST vocabulary can also execute complex logic. When data validations are necessary, this can be checked at the POST or PUT of new data. If complex processes need to be represented, a POST can start the process, and subsequently the state can be updated. The current state of the process can be fetched by the client under a known URL via GET. Likewise, POST or PUT can be used to initiate the next state.

Cache and Load Balancer

A RESTful HTTP interface can be very easily supplemented with a cache. Because RESTful HTTP uses the same HTTP protocol as the web, a simple web cache is sufficient. Similarly, a standard HTTP load balancer can also be used for RESTful HTTP. The power of these concepts is impressively illustrated by the size of the WWW. This size is only possible due to the properties of HTTP. HTTP, for instance, possesses simple and useful mechanisms for security—not only encryption via HTTPS but also authentication with HTTP Headers.

HATEOAS

HATEOAS (Hypermedia as the Engine of Application State) is another important component of REST. It enables the relationships between resources to be modeled with links. Therefore, a client only has to know an entry point, and from there it can go on navigating at will and locate all data in a step-by-step manner. In the WWW it is, for instance, possible to start from Google and from there to reach practically the entire web via links.

REST describes the architecture of the WWW and therefore the world’s largest integrated computer system. However, REST could also be implemented with other protocols. It is an architecture that can be implemented with different technologies. The implementation of REST with HTTP is called RESTful HTTP. When RESTful HTTP services exchange data using JSON or XML instead as HTML, they can exchange data and not just access web pages.

Microservices can also benefit from HATEOAS. HATEOAS does not have central coordination, just links. This fits very well with the concept that microservices should have as little central coordination as possible. REST clients need know only entry points based on which they can discover the entire system. Therefore, in a REST-based architecture, services can be moved in a way that is transparent for the client. The client simply gets new links. Central coordination is not necessary for this—the REST service just has to return different links. In the ideal case the client only has to understand the fundamentals of HATEOAS and can then navigate via links to any data in the microservice system. The microservice-based systems, on the other hand, can modify their links and therefore change the distribution of functionality between microservices. Even extensive architecture changes can be kept transparent.

HAL

HATEOAS is a concept, and HAL13 (Hypertext Application Language) is a way to implement it. It is a standard for describing how the links to other documents should be contained in a JSON document. HATEOAS is particularly easy to implement in JSON/RESTful HTTP services. The links are separate from the actual document, enabling links to details or to independent data sets.

13. http://stateless.co/hal_specification.html

XML

XML has a long history as a data format. It is easy to use with RESTful HTTP. There are different types of systems for XML that can determine whether an XML document is valid. This is very useful for the definition of an interface. Among the languages for the definition of valid data is XML Schema (XSD)14 or RelaxNG.15 Some frameworks make possible the generation of code in order to administer XML data that corresponds to such a schema. Via XLink16 XML documents can contain links to other documents. This enables the implementation of HATEOAS.

14. http://www.w3.org/XML/Schema

15. http://relaxng.org/

16. http://www.w3.org/TR/xlink11/

HTML

XML was designed to transfer data and documents. To display the information is the task of different software. HTML has a similar approach to XML: HTML defines only the structures, with display occurring via CSS. For communication between processes HTML documents can be sufficient because in modern web applications, HTML documents contain only data—just like XML. In a microservices world this approach has the advantage that the communication to the user and between the microservices employs the same format. This reduces effort and makes it even easier to implement microservices that contain a UI and a communication mechanism for other microservices.

JSON

JSON (JavaScript Object Notation) is a representation of data that is well suited to JavaScript. Like JavaScript, the data is dynamically typed. There are suitable JSON libraries for all programming languages. In addition, there are type systems, such as JSON Schema,17 that supplement JSON with validation concepts. With this addition JSON is no longer inferior to data formats like XML.

17. http://json-schema.org/

Protocol Buffer

Binary protocols such as Protocol Buffer18 can be used instead of text-based data representations. This technology has been designed by Google to represent data more efficiently and to achieve higher performance. There are implementations for many different programming languages so Protocol Buffer can be used universally, similar to JSON and XML.

18. https://developers.google.com/protocol-buffers/

RESTful HTTP Is Synchronous

RESTful HTTP is synchronous: typically, a service sends out a request and waits for a response, which is then analyzed in order to continue with the program sequence. This can cause problems if there are long latency times within the network. It can lengthen the processing of a request since responses from other services have to be waited for. After waiting for a certain period of time the request has to be aborted because it is likely that the request is not going to be answered at all. Possible reasons for a failure are that the server is not available at the moment or that the network has a problem. Correctly handled timeouts increase the stability of the system (section 9.5).

The timeout should be used to ensure that the calling service does not fail simply because it does not get a response from the system it is calling. This ensures that a failure does not propagate through the system as a whole.

8.3 SOAP and RPC

It is possible to build a microservices-based architecture using SOAP. Like REST, SOAP uses HTTP, but it only uses POST messages to transfer data to a server. Ultimately, a SOAP call runs a method on a certain object on the server and is therefore an RPC mechanism (remote-procedure call).

SOAP lacks concepts such as HATEOAS that enable relationships between microservices to be handled flexibly. The interfaces have to be completely defined by the server and known on the client.

Flexible Transport

SOAP can convey messages using different transport mechanisms. For instance, it’s possible to receive a message via HTTP and to then send it on via JMS or as an email via SMTP/POP. SOAP-based technologies also support forwarding of requests. For example, the security standard WS-Security can encrypt or sign parts of a message. After this has been done, the parts can be sent on to different services without having to be decrypted. The sender can send a message in which some parts are encrypted. This message can be processed via different stations. Each station can process a part of the message or send it to other recipients. Finally, the encrypted parts will arrive at their final recipients—and only there do they have to be decrypted and processed.

SOAP has many extensions for special use contexts. For instance, the different extensions from the WS-*-environment cater for transactions and the coordination of web services. This enables a complex protocol stack to arise. The interoperability between the different services and solutions can suffer due to this complexity. Also, some technologies are not well suited for microservices. For example, a coordination of different microservices is problematic as this will result in a coordination layer, and modifications of a business process will probably impact the coordination of the microservices and also the microservices themselves. When the coordination layer consists of all microservices, a monolith is created that needs to be changed upon each modification. This contradicts the microservices concept of independent deployment. WS-* is better suited to concepts such as SOA.

Thrift

Another communication option is Apache Thrift.19 It uses very efficient binary encodings such as Protocol Buffer. Thrift can also forward requests from a process via the network to other processes. The interface is described in an interface definition specific to Thrift. Based on this definition different client and server technologies can communicate with each other.

19. https://thrift.apache.org/

8.4 Messaging

Another way for microservices to communicate is using messages and messaging systems. As the name suggests, these systems are based on the sending of messages. A message may result in a response that is sent as a message again. Messages can go to one or multiple recipients.

The use of messaging solutions is particularly advantageous in distributed systems:

• Message transfer is resilient to network failures. The messaging system buffers them and delivers them when the network is available again.

• Guarantees can be strengthened further: the messaging system can guarantee not only the correct transfer of the messages but also their processing. If there was a problem during the processing of the message, the message can be transferred again. The system can attempt to handle the message a number of times until either the message is correctly processed or discarded because it cannot be processed successfully.

• In a messaging architecture responses are transferred and processed asynchronously. This approach is well suited to the high latency times that can occur in the network. Waiting a period of time for a response is normal with messaging systems and therefore the programming model works on the assumption of high latency.

• A call to another service does not block further processing. Even if the response has not been received yet, the service can continue working and potentially call other services.

• The sender does not know the recipient of the message. The sender sends the message to a queue or a topic. There the recipient registers. This means that the sender and recipient are decoupled. There can even be multiple recipients without the sender being aware of this. Also, the messages can be modified on their way—for instance, data can be supplemented or removed. Messages can also be forwarded to entirely different recipients.

Messaging works well with certain architectures of microservice-based systems such as Event Sourcing (see section 9.3) or event-driven architecture (section 7.8).

Messages and Transactions

Messaging is an approach that can be implemented in transactional systems that use microservices. It can be difficult to guarantee transactions when microservices call each other in a microservice-based system. When multiple microservices have to participate in a transaction, they can only be allowed to write changes when all microservices in the transaction have processed the logic without errors. This means that changes have to be held back for a long time. That is bad for performance since no new transactions can change the data in the meantime. Also, in a network it is always possible that a participant fails. When this happens the transaction could remain open for a long time or might not be closed at all. This will potentially block changes to the data for a long period of time. Such problems arise, for instance, when the calling system crashes.

In a messaging system, transactions can be treated differently. The sending and receiving of messages is part of a transaction—just as, for instance, the writing to and reading from the database (see Figure 8.8). When an error occurs during the processing of the message, all outgoing messages are canceled, and the database changes are rolled back. In the case of success all these actions take place. The recipients of the messages can be similarly safeguarded transactionally. To achieve this the processing of the outgoing messages is subject to the same transactional guarantees.

Image

Figure 8.8 Transactions and Messaging

The important point is that the sending and receiving of messages and the associated transactions on the database can be combined in one transaction. The coordination is then taken care of by the infrastructure—no extra code needs to be written. For the coordination of messaging and databases the two-phase commit (2PC) protocol can be employed. This protocol is the normal method for coordinating transactional systems like databases and messaging systems with each other. Alternatives are products like Oracle AQ or ActiveMQ, which store messages in a database. By storing messages in a database, the coordination between database and messaging can be achieved simply by writing the messages as well as the data modifications in the same database transaction. Ultimately, messaging and database are the same systems in this setup.

Messaging enables the implementation of transactions without the need for a global coordination. Each microservice is transactional. The transactional sending of messages is ensured by the messaging technology. However, when a message cannot be processed, for instance because it contains invalid values, there is no way to roll back the messages that have already been processed. Therefore, the correct processing of transactions is not guaranteed in all circumstances.

Messaging Technology

In order to implement messaging a technology has to selected:

• AMQP (Advanced Message Queuing Protocol)20 is a standard. It defines a protocol with which messaging solutions can communicate on the wire with each other and with clients. An implementation of this standard is RabbitMQ,21 which is written in Erlang and is made available under the Mozilla license. Another implementation is Apache Qpid.

20. https://www.amqp.org/

21. https://www.rabbitmq.com/

• Apache Kafka22 focuses on high throughput, replication, and fault-tolerance. Therefore, it is well suited for distributed systems like microservices, especially the fault-tolerance, which is very helpful in this context.

22. http://kafka.apache.org/

• 0MQ23 (also called ZeroMQ or ZMQ) operates without a server and is therefore very lightweight. It has some primitives that can be assembled into complex systems. 0MQ is released under the LGPL license and written in C++.

23. http://zeromq.org/

• JMS (Java Messaging Service)24 defines an API that a Java application can use to receive messages and send them. In contrast to AMQP the specification does not define how the technology transfers messages on the wire. Since it is a standard, Java-EE server implements this API. Well-known implementations are ActiveMQ25 and HornetQ.26

24. https://jcp.org/en/jsr/detail?id=343

25. http://activemq.apache.org/

26. http://hornetq.jboss.org/

• Azure Service Bus27 is Microsoft’s hosted messaging system. SDKs are provided for Java, Node.js, and also .NET.

27. https://azure.microsoft.com/services/service-bus

• It is also possible to use ATOM28 Feeds29 for messaging. This technology is normally used to transfer blog content enabling clients a relatively easily way to request new entries on a blog. In the same way a client can use ATOM to request new messages. ATOM is based on HTTP and therefore fits well in a REST environment. However, ATOM only has functionality for delivering new information. It does not support more complex techniques like transactions.

28. http://tools.ietf.org/html/rfc4287

29. http://tools.ietf.org/html/rfc5023

For many messaging solutions a messaging server and therefore additional infrastructure is required. This infrastructure has to be operated in a way that prevents failures because these would cause communication in the entire microservice-based system to fail. However, messaging solutions are normally designed to achieve high availability via clustering or other techniques.

For many developers messaging is a somewhat unfamiliar concept since it requires asynchronous communication, making it appear rather complex. In most cases the calling of a method in a different process is easier to understand. With approaches like Reactive (see section 9.6) asynchronous development is introduced into the microservices themselves. Also the AJAX model from JavaScript development resembles the asynchronous treatment of messages. More and more developers are therefore becoming familiar with the asynchronous model.


Try and Experiment

• REST, SOAP/RPC, and messaging each have advantages and disadvantages. List the advantages and disadvantages and make up your mind which of the alternatives to use.

• In a microservice-based system there can be different types of communication; however, there should be one predominant communication type. Which would you choose? Which others would be allowed in addition? In which situations?


8.5 Data Replication

At the database level microservices could share a database and all access the same data. This type of integration is something that has been used in practice for a long time: it is not unusual that a database is used by several applications. Often databases last longer than applications, leading to a focus on the database rather than the applications that sit on top of it. Although integration via a shared database is widespread, it has major disadvantages:

• The data representation cannot be modified easily since several applications access the data. A change could cause one of the applications to break. This means that changes have to be coordinated across all applications.

• This makes it impossible to rapidly modify applications in situations where database changes are involved. However, the ability to rapidly change an application is exactly the benefit that microservices should bring.

• Finally, it is very difficult to tidy up the schema—for example, to remove columns that are no longer needed—because it is unclear whether any system is still using these columns. In the long run the database will get more and more complex and harder to maintain.

Ultimately, the shared use of a database is a violation of an important architectural rule. Components should be able to change their internal data representation without other components being affected. The database schema is an example of an internal data representation. When multiple components share the database, it is no longer possible to change the data representation. Therefore, microservices should have strictly separate data storage and not share a database schema.

A database instance can be used by multiple microservices when the data sets of the individual microservices are completely separate. For instance, each microservice can use its own schema within a shared database. However, in that situation there shouldn’t be any relationships between the schemas.

Replication

Replicating data is an alternative method for integrating microservices. But care should be taken that the data replication does not introduce a dependency on the database schemas by the back door. When the data is just replicated and the same schema is used, the same problem occurs as with a shared use of the database. A schema change will affect other microservices, and the microservices become coupled again. This has to be avoided.

The data should be transferred into another schema to ensure the independence of the schemas and therefore the microservices. In most cases, Bounded Context means that different representations or subsets of data are relevant for different microservices. Therefore, when replicating data between microservices it will often be necessary to transform the data or to replicate just subsets of the data anyway.

A typical example for the use of replication in traditional IT is data warehouses. They replicate data but store it differently. This is because the data access requirement for a data warehouse is different: the aim is to analyze lots of data. The data is optimized for read access and often also combined, as not every single data set is relevant for statistics.

Problems: Redundancy and Consistency

Replication causes a redundant storage of the data. This means that the data is not immediately consistent: it takes time until changes are replicated to all locations.

However, immediate consistency is often not essential. For analysis tasks such as those carried out by a data warehouse, an analysis that does not include orders from the last few minutes can be sufficient. There are also cases in which consistency is not that important. When an order takes a little bit of time until it is visible in the delivery microservice, this can be acceptable because nobody will request the data in the meanwhile.

High consistency requirements make replication difficult. When system requirements are determined, it is often not clear how consistent the data really has to be. This limits the options when it comes to data replication.

When designing a replication mechanism there should ideally be a leading system that contains the current data. All other replicas should obtain the data from this system. This makes it clear which data is really up-to-date. Data modifications should not be stored in different systems as this easily causes conflicts and makes for a very complex implementation. Such conflicts are not a problem when there is just one source for changes.

Implementation

Some databases offer replication as a feature. However, this is often not helpful with the replication of data between microservices because the schemas of the microservices should be different. The replication has to be self-implemented. For this purpose, a custom interface can be created. This interface should enable high performance access even to large data sets. To achieve the necessary performance, one can also directly write into the target schema. The interface does not necessarily have to use a protocol like REST, but can employ faster alternative protocols. To achieves this, it may be necessary to use another communication mechanism than the one normally used by the microservices.

Batch

The replication can be activated in a batch. In this situation the entire data set—or at least changes from the last run—can be transferred. For the first replication run the volume of data can be large, meaning that the replication takes a long time. However, it can still be sensible to transfer all the data each time. This makes possible the correction of mistakes that occurred during the last replication run.

A simple implementation can assign a version to each data set. Based on the version, data sets that have changed can specifically be selected and replicated. This approach means that the process can be easily restarted if it is interrupted for some reason, as the process itself does not hold a state. Instead the state is stored with the data itself.

Event

An alternative method is to start the replication on certain events. For instance, when a data set is newly generated, the data can be immediately copied into the replicas. This approach is particularly easy to implement with messaging (section 8.4).

Data replication is an especially good choice where high-performance access is required to large amounts of data. Many microservice-based systems get along without replicating data. Even those systems that use data replication can also employ other integration mechanisms.


Try and Experiment

Would you use data replication in a microservice-based system? In which areas? How would you implement it?


8.6 Interfaces: Internal and External

Microservice-based systems have different types of interfaces:

• Each microservice can have one or more interfaces to other microservices. A change to the interface can require coordination with other microservice teams.

• The interfaces between microservices that are developed by the same team are a special case. Team members can closely work together so that these interfaces are easier to change.

• The microservice-based system can offer interfaces to the outside world, making the system accessible beyond just the organization. In extreme cases this can potentially be every Internet user if the system offers a public interface on the Internet.

These interfaces vary in how easy they are to change. It is very easy to ask a colleague in the same team for a change. This colleague is potentially in the same room, so it is very easy to communicate with him.

A change to an interface of a microservice belonging to another team is more difficult. The change has to compete against other changes and new features that team may be implementing. When the change has to be coordinated with other teams, additional effort arises.

Interface changes between microservices can be safeguarded by appropriate tests (consumer-driven contract tests, section 10.7). These tests examine whether the interface still meets the expectations of the interface users.

External Interfaces

When considering interfaces to the outside, coordination with users is more complicated. There may be very many users, and for public interfaces the users might even be unknown. This makes techniques like consumer-driven contract tests hard to implement. However, for interfaces to the outside, rules can be defined that determine, for instance, how long a certain version of the interface will be supported. A stronger focus on backwards compatibility can make sense for public interfaces.

For interfaces to the outside it can be necessary to support several versions of the interface in order to not force all users to perform changes. Between microservices it should be an aim to accept multiple versions only for uncoupling deployments. When a microservice changes an interface, it should still support the old interface. In that case the microservices that depend on the old interface do not have to be instantly deployed anew. However, the next deployment should use the new interface. Afterwards the old interface can be removed. This reduces the number of interfaces that have to be supported and therefore the complexity of the system.

Separating Interfaces

Since interfaces vary in how easy they are to change, they should be implemented separately. When the interface of a microservice is to be used externally, it can subsequently only be changed when this change is coordinated with the external users. However, a new interface for internal use can be split off. In this situation the interface that is exposed to the outside is the starting point for a separate internal interface that can be more easily changed.

Also several versions of the same interface can be implemented together internally. New parameters on a new version of the interface can be set to default values when the old interface is called so that internally both interfaces use the same implementation.

Implementing External Interfaces

Microservice-based systems can offer interfaces to the outside in different ways. On top of a web interface for users there can also be an API, which can be accessed from outside. For the web interface section 8.1 described how the microservices can be integrated in a way that enables all microservices to implement part of the UI.

When the system offers a REST interface to the outside world, the calls from outside can be forwarded to a microservice with the help of a router. In the example application the router Zuul is used for this (section 13.9). Zuul is highly flexible and can forward requests to different microservices based on very detailed rules. However, HATEOAS gives the freedom to move resources and makes routing dispensable. The microservices are accessible from the outside via URLs, but they can be moved at any time. In the end the URLs are dynamically determined by HATEOAS.

It would also be possible to offer an adaptor for the external interface that modifies the external calls before they reach the microservices. However, in that case a change to the logic cannot always be limited to a single microservice because it could also affect the adaptor.

Semantic Versioning

To denote changes to an interface a version number can be used. Semantic Versioning30 defines possible version number semantics. The version number is split into MAJOR, MINOR, and PATCH. The components have the following meaning:

30. http://semver.org/

• A change in MAJOR indicates that the new version breaks backwards compatibility. The clients have to adjust to the new version.

• The MINOR version is changed when the interface offers new features. However, the changes should be backwards compatible. A change of the clients is only necessary if they want to use the new features.

• PATCH is increased in the case of bug fixes. Such changes should be completely backwards compatible and should not require any modifications to the clients.

When using REST one should keep in mind that it is not wise to encode the version in the URL. The URL should represent a resource—independent of which version of the API version is called. The version can be defined, for instance, in an Accept header of the request.

Postel’s Law or the Robustness Principle

Another important principle for the definition of interfaces is Postel’s Law,31 which is also known as the Robustness Principle. It states that components should be strict with regard to what they are passing on and liberal with regard to what they are accepting from others. Put differently, each component should adhere as closely as possible to the defined interface when using other components but should, whenever possible, compensate for errors that arise during the use of its own interface.

31. http://tools.ietf.org/html/rfc793#section-2.10

When each component behaves according to the Robustness Principle interoperability will improve: in fact, if each component adheres exactly to the defined interfaces, interoperability should already be guaranteed. If a deviation does happen, then the component being used will try to compensate for it and thereby attempt to “save” the interoperability. This concept is also known as Tolerant Reader.32

32. http://martinfowler.com/bliki/TolerantReader.html

In practice a called service should accept the calls as long as this is at all possible. One way to achieve this is to only read out those parameters from a call that are really necessary. On no account should a call be rejected just because it does not formally conform to the interface specification. However, the incoming calls should be validated. Such an approach makes it easier to ensure smooth communication in distributed systems like microservices.

8.7 Conclusion

The integration of microservices can occur at different levels.

Client

One possible level for the integration is the web interface (section 8.1):

• Each microservice can bring along its own single-page-app (SPA). The SPAs can be developed independently. The transition between the microservices, however, starts a completely new SPA.

• There can be one SPA for the entire system. Each microservice supplies one module for the SPA. This makes the transitions between the microservices very simple in the SPA. However, the microservices get very tightly integrated, meaning that coordination of deployments can become necessary.

• Each microservice can bring along an HTML application, and integration can occur via links. This approach is easy to implement and enables a modularization of the web application.

• JavaScript can load HTML. The HTML can be supplied by different micro-services so that each microservice can contribute a representation of its data. Using this technique an order can, for example, load the presentation of a product from another microservice.

• A skeleton can assemble individual HTML snippets. This would enable, say, an e-commerce landing page to display the last order from one microservice and recommendations from another microservice. ESI (Edge Side Includes) or SSI (Server Side Includes) can be useful for this.

In the case of a rich client or a mobile app the integration is difficult because the client application is a deployment monolith. Therefore, changes to different micro-services can only be deployed together. The teams can modify the microservices and then deliver a certain amount of matching UI changes together for a new release of the client application. There can also be a team for each client application that adopts new functionality of the microservices into the client application. From an organizational perspective there can even be developers in the team of the client application that develop a custom service that can, for instance, implement an interface that enables the client application to use it in a high-performance way.

Logic Layer

REST can be used for communication at the logic layer (section 8.2). REST uses the mechanisms of the WWW to enable communication between services. HATEOAS means that the relationships between systems are represented as links. The client only needs to know an entry URL. All the other URLs can be changed because they are not directly contacted by the clients but are found by them via links starting at the entry URL. HAL defines how links can be expressed and supports the implementation of REST. Other possible data formats for REST are XML, JSON, HTML, and Protocol Buffer.

Classical protocols like SOAP or RPC (section 8.3) can also be used for the communication between microservices. SOAP offers ways for messages to be forwarded to other microservices. Thrift has an efficient binary protocol and can also forward calls between processes.

Messaging (section 8.4) has the benefit that it can handle network problems and high latency times very well. In addition, transactions are also very well supported by messaging.

Data Replication

At the database level a shared schema is not recommended (section 8.5). This would couple microservices too tightly since they would have a shared internal data representation. The data has to be replicated into another schema. The schema should meet the requirements of the respective microservice. As microservices are Bounded Contexts, it is very unlikely that the microservices will use the same data model.

Interfaces and Versions

Finally, interfaces are an important foundation for communication and integration (section 8.6). Not all interfaces are equally easy to change. Public interfaces can be practically impossible to change because too many systems depend on them. Internal interfaces can be changed more easily. In the simplest case public interfaces just route certain functionality to suitable microservices. Semantic Versioning is useful for giving a meaning to version numbers. To ensure a high level of compatibility the Robustness Principle is helpful.

This section has hopefully shown that microservices are not just services that use RESTful HTTP. This is just one way for microservices to communicate.

Essential Points

• At the UI level the integration of HTML user interfaces is particularly straightforward. SPAs, desktop applications, and mobile apps are deployment monoliths where changes to the user interface for a microservice have to be closely coordinated with other changes.

• Though REST and RPC approaches offer a simple programming model at the logic level, messaging makes a looser coupling possible and can cope better with the challenges of distributed communication via the network.

• Data replication enables high-performance access to large amounts of data. However, microservices should never use the same schema for their data since this means the internal data representation can no longer be changed.

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

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