Communication patterns and observables

All the examples studied up to now were implemented as a single process. But software of a higher complexity, running either on the backend side or on the edge side, is usually split into multiple processes, and on multiple systems or devices. Communicating between processes and systems can be done in many different ways, and many solutions exist. For backend communication, one solution is to expose REST-oriented APIs, such as the audio transcoding service implemented throughout this book. The advantage of this solution is that it is easy to integrate with many existing tools. However, from a reactive point of view, it means that observables cannot be exposed outside the service: an HTTP request completes as soon as the first answer is sent, while an observable can send many items.

In cases where several services are implemented in a reactive way, then it would be nice to expose their APIs as observables so that they integrate more naturally together. Since ReactiveX and reactive programming libraries are available in many programming languages, this still allows us to implement components using different programming languages. Such a solution goes against the trend of isomorphic applications where both the backend and the frontend are implemented in the same programming languages. Isomorphic applications can be great in some cases, however there are many situations where this is not applicable:

  • In practice, JavaScript is the only possible programming language for isomorphic web applications. Java may be a solution for other clients, as well as C++. This leaves out most available programming languages.
  • Different programming languages are better suited for different kinds of task. So restricting the implementation to a single programming language can be more of a restriction than a simplification.

So, using observables to expose APIs allows us to extend the reactive pattern while still being able to use different programming languages for different parts. A typical use-case, where this architecture is interesting, is a machine learning algorithm being continuously fed by a web application:

  • The frontend can be implemented in JavaScript with Cycle.js.
  • The backend can be implemented in Python with AsyncIO and the machine learning framework used for the model (the commonality for almost all these frameworks is that they expose Python bindings).
  • The communication between the frontend and the backend can be based on a WebSocket or HTTP2 connection.

In order to implement such a system, the network link between the peers needs to fulfill the following constraints:

  • It must be persistent, so that many messages can be sent without closing the connection. The persistent connection allows us to maintain a context as long as the connection is open, and release resources when the connection is closed. So a state is shared between the two peers as long as the connection is open. This is a different design from stateless RESTful APIs.
  • It must be bidirectional, so that peers can talk to each other.
  • It must be reliable: the integrity of the messages sent on the network link must be guaranteed (transmission-wise, not cryptographically-wise).
  • It must preserve message ordering: messages sent in one order must be delivered in the same order.

Using a network protocol that does not fulfill these constraints is possible, but adds complexity because the missing properties have to be implemented before a remote observable can be used on such a link.

Fortunately, these constraints are fulfilled in many transport protocols such as TCP, TLS over TCP, WebSockets, and HTTP/2. Before a first implementation tentative, let's first detail three of the most popular communication patterns and how they can be implemented as observables:

  • Publish/subscribe
  • Channel
  • Request/response
..................Content has been hidden....................

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