📜 ⬆️ ⬇️

The book "Distributed Systems. Design Patterns »

image The modern world is simply unthinkable without the use of distributed systems. Even the simplest mobile application has an API through which it connects to the cloud storage. However, the design of distributed systems is still an art, not an exact science. The need to bring a serious basis under it is long overdue, and if you want to gain confidence in the creation, support and operation of distributed systems - start with this book!

Brendan Burns, the most authoritative specialist in cloud technologies and Kubernetes, sets out in this small work the absolute minimum necessary for the proper design of distributed systems. This book describes timeless design patterns of distributed systems. It will help you not only to create such systems from scratch, but also to effectively convert existing ones.


Excerpt Pattern Decorator. Convert request or response


FaaS is ideal when you need simple functions that process input data and then transfer it to other services. This kind of pattern can be used to expand or decorate HTTP requests sent or received by another service. This pattern is shown schematically in fig. 8.1.
')
By the way, in programming languages ​​there are several analogies to this pattern. In particular, Python has function decorators that are functionally similar to request or response decorators. Since the decorating transformations do not store state and are often added after the fact as the service develops, they are ideally suited for implementation as FaaS. In addition, the lightness of FaaS means that you can experiment with different decorators until you find one that is more closely integrated into the implementation of the service.

image

Adding default values ​​to HTTP input parameters RESTful API queries favorably demonstrate the advantages of the Decorator pattern. Many API queries have fields that must be filled with reasonable values ​​if they were not specified by the caller. For example, you want the default field to be true. This is difficult to achieve with classic JSON, since it defaults to null, which is usually interpreted as false. To solve this problem, you can add default substitution logic either in front of the API server, or in the application code itself (for example, if (field == null) field = true). However, both of these approaches are not optimal, because the default value substitution mechanism is conceptually independent of the processing of a request. Instead, we can use the FaaS Decorator pattern, which transforms the path request between the user and the service implementation.

Considering what was said earlier in the section on single-node patterns, you may have wondered why we did not arrange the default value substitution service in the form of a container adapter. This approach makes sense, but it also means that the scaling of the default value substitution service and the scaling of the API service itself become dependent on each other. Default substitution is a computationally easy operation, and most likely it will not require many instances of the service.

In the examples in this chapter, we will use the kubeless FaaS framework (https://github.com/kubeless/kubeless). Kubeless is deployed on top of the Kubernetes container orchestrator service. If you have already prepared the Kubernetes cluster, then proceed to the installation of Kubeless, which can be downloaded from the corresponding site (https://github.com/kubeless/kubeless/releases). As soon as you have a kubeless executable file, you can install it into a cluster using the kubeless install command.

Kubeless is installed as a third-party API add-in Kubernetes. This means that after installation, it can be used within the kubectl command line tool. For example, functions deployed in a cluster can be seen by executing the command kubectl get functions. There are currently no functions deployed in your cluster.

Workshop. Substitution of default values ​​before processing the request


You can demonstrate the utility of the Decorator pattern in FaaS using the example of substituting default values ​​into a RESTful call for parameters whose values ​​were not specified by the user. Using FaaS is pretty easy. The default substitution function is written in Python:

#  -,   #   def handler(context): #    obj = context.json #   "name" ,   #   if obj.get("name", None) is None: obj["name"] = random_name() #    'color',   #   'blue' if obj.get("color", None) is None: obj["color"] = "blue" #  API-     #   #    return call_my_api(obj) 

Save this function to a file called defaults.py. Remember to replace the call to call_my_api with the call to the API you need. This default substitution function can be registered as a kubeless function with the following command:

 kubeless function deploy add-defaults \ --runtime python27 \ --handler defaults.handler \ --from-file defaults.py \ --trigger-http 

To test it, you can use the kubeless tool:

 kubeless function call add-defaults --data '{"name": "foo"}' 

The Decorator pattern shows how easy it is to adapt and expand existing APIs with additional features like validation or default substitution.

Event handling


Most systems are query-oriented — they handle continuous streams of user and API requests. Despite this, there are quite a few event-oriented systems. The difference between a request and an event, it seems to me, lies in the concept of a session. Requests are part of a larger interaction process (session). In general, every user request is part of the process of interacting with a web application or API as a whole. Events seem to me more "disposable", asynchronous in nature. Events are important and must be properly handled, but they are taken out of the main interaction context and the answer to them comes only after a while. An example of an event is a subscription by a user to a certain service, which will cause sending a welcome letter; uploading a file to a shared folder, which will lead to sending notifications to all users of this folder; or even preparing the computer for a reboot, which will result in notifying the operator or the automated system that appropriate actions need to be taken.

Since these events are largely independent and do not have an internal state, and their frequency is highly variable, they are ideally suited to work in event-oriented FaaS architectures. They are often deployed next to the “combat” application server to provide additional capabilities or for background processing of data in response to emerging events. In addition, since new types of processed events are constantly being added to the service, the ease of deploying functions makes them suitable for implementing event handlers. And since each event is conceptually independent of the others, the forced weakening of links within the system built on the basis of functions reduces its conceptual complexity, allowing the developer to focus on the steps necessary to handle only one specific type of event.

A specific example of the integration of an event-oriented component to an existing service is the implementation of two-factor authentication. In this case, the event will be the user login. The service can generate an event for this action and pass it to the handler function. The handler will send the authentication code as a text message based on the transmitted code and user contact information.

Workshop. Implementation of two-factor authentication


Two-factor authentication indicates that a user needs to know something (for example, a password) and something that he has (for example, a phone number) to log in to the system. Two-factor authentication is much better than just a password, because an attacker will have to steal both your password and your phone number to gain access.

When planning the implementation of two-factor authentication, you need to process a request to generate a random code, register it with the login service, and send a message to the user. You can add code that implements this functionality directly into the login service itself. This complicates the system, makes it more monolithic. Sending a message must be performed simultaneously with the code that generates a login web page, which may introduce a certain delay. This delay affects the quality of user interaction with the system.

It would be better to create a FaaS service that would asynchronously generate a random number, register it with the login service and send it to the user's phone. Thus, the login server can simply perform an asynchronous request to the FaaS service, which in parallel will perform the relatively slow task of registering and sending code.
To see how this works, consider the following code:

 def two_factor(context): #     code = random.randint(1 00000, 9 99999) #        user = context.json["user"] register_code_with_login_service(user, code) #      Twillio account = "my-account-sid" token = "my-token" client = twilio.rest.Client(account, token) user_number = context.json["phoneNumber"] msg = ", {},   : {}.".format(user, code) message = client.api.account.messages.create(to=user_number, from_="+1 20652 51212", body=msg) return {"status": "ok"} 

Then, register FaaS with kubeless:

 kubeless function deploy add-two-factor \ --runtime python27 \ --handler two_factor.two_factor \ --from-file two_factor.py \ --trigger-http 

An instance of this function can be asynchronously generated from client-side JavaScript code after the user has entered the correct password. The web interface can immediately display a page for entering the code, and the user, as soon as he receives the code, can inform his login service, in which this code is already registered.

So, the FaaS approach has greatly facilitated the development of a simple, asynchronous, event-oriented service, which is initiated when a user logs into the system.

Event Conveyors


There are a number of applications that are, in fact, easier to treat as a pipeline of weakly coupled events. Event conveyors often resemble good old flowcharts. They can be represented as a directed synchronization graph of related events. Within the Event Pipeline pattern, the nodes correspond to functions, and the arcs connecting them are HTTP requests or other kinds of network calls.

As a rule, there is no common state between the elements of the container, but there may be a general context or another point of reference, on the basis of which the search in the storage will be performed.

What is the difference between such a pipeline and microservice architecture? There are two important differences. The first and most important difference between service-functions and constantly running services is that event-based conveyors, in fact, are driven by events. Microservice architecture, on the contrary, implies a set of constantly running services. In addition, event pipelines can be asynchronous and link various events. It is difficult to imagine how you can integrate the approval of the application in the Jira system into the microservice application. At the same time, it is not difficult to imagine how it integrates into the event pipeline.

As an example, consider the pipeline in which the source event will be loading the code into the version control system. This event causes a rebuild of the code. The build may take several minutes, after which an event is generated that triggers the test function of the assembled application. Depending on the success of the build, the testing function takes different actions. If the build is successful, an application is created, which must be approved by a person in order for the new version of the application to go online. Closing an application serves as a signal to put a new version into operation. If the build fails, a bug is reported to Jira and the pipeline ends.

Workshop. Implementing a pipeline for registering a new user


Consider the task of implementing a sequence of actions for registering a new user. When creating a new account, a number of actions are always performed, for example, sending a welcome email. There are also a number of actions that may not be performed every time, for example, subscribing to an e-mail newsletter about new product versions (also known as spam).

One approach involves the creation of a monolithic service to create new accounts. With this approach, one development team is responsible for the entire service, which is also deployed as a whole. This makes it difficult to conduct experiments and make changes to the process of user interaction with the application.

Consider the user login implementation as an event pipeline from several FaaS services. With this separation, the user creation function has no idea what happens when a user logs on to the system. She has two lists:


Each of these actions is also implemented in the form of FaaS, and the list of actions is nothing more than a list of HTTP callback functions. Therefore, the user creation function has the following form:

 def create_user(context): #      for key, value in required.items(): call_function(value.webhook, context.json) #    #     for key, value in optional.items(): if context.json.get(key, None) is not None: call_function(value.webhook, context.json) 

Each of the handlers can now also be implemented using the FaaS principle:

 def email_user(context): #    user = context.json['username'] msg = ', {}, ,     !".format(user) send_email(msg, contex.json['email]) def subscribe_user(context): #    email = context.json['email'] subscribe_user(email) 

A FaaS service decomposed in this way becomes much simpler, contains fewer lines of code, and focuses on the implementation of one particular function. The microservice approach simplifies writing code, but can lead to difficulties in deploying and managing three different microservices. Here, the FaaS approach manifests itself in all its glory, because as a result of its use, it becomes very easy to manage small code fragments. The visualization of the process of creating a user in the form of an event pipeline also allows you to understand in general what exactly happens when a user logs on to the system, simply by following the context change from function to function within the pipeline.

»More information about the book can be found on the publisher's website.
» Table of Contents
» Excerpt

For Habrozhiteley 20% discount coupon - Design patterns

Upon payment of the paper version of the book, an electronic version of the book is sent to the e-mail.

Source: https://habr.com/ru/post/445362/


All Articles