📜 ⬆️ ⬇️

Prepare a simple blog on microservices, write your microframe in php and run everything on Docker with examples

But what if I tell you that a new product can immediately begin to write on microservice architecture, and not engage in the cutting of a monolith? Is this generally normal? Conveniently? Want to know the answer?


Task: you need to write for the weekend (time limited to 10-15 hours) spherical blog on microservices, in php, without using any frameworks. You can use common sense. And let's forget about what the frontend is and remember that we cannot live without virtualization. Choose a Docker. Interesting? Forward under the cat.


image

Microservices


If you are interested in a microservice approach, but you don’t know where to start, start with Sam Newman’s book Building Microservices. I will try to describe a little the main points of this approach, if you have any additions, please write in the comments. And in general, for any reason to write, I do not claim the truth of any of the approaches described below, especially in your particular case.


We will consider everything on the example of the above blog. Of course, this is a task for the sake of the task, but I would like to note that even in this version, it will work and will work well (quickly and without problems).


The essence of microservices is easy to understand in comparison with the monolithic architecture. What does a regular blog engine look like? Roughly speaking, this is just one application. Work with articles, comments, pages, users and other functional units is enclosed in a single package of source code, which is not divided in any way.



Where all the connections between components are calls within the code, some kind of relationships between classes, patterns, etc. or even just govnokod, if you can not separate one from another.


What will our blog look like? Yes, about the same, to be honest.



The only difference is that the squares with components are no longer components enclosed in the code of one application, but the arrows are no longer system calls to classes within this code. Now - these are separate components, and arrows - the usual requests for http.


Why do you need it? We will immediately determine that probably not everyone needs this. This should be very convenient if you are a large enough company capable of allocating a development team for each service. I think even medium-sized companies, if you select a person for each service, it will be good too. However, even if you are alone for the whole company, you can find something interesting in microservices.


How big should the service be? Borders are difficult to carry out, the error will cost you dearly, but, in short, service is a certain unit of your system that you can completely rewrite in a short time. Empirically let the week you or your team should cope with the service. The main idea here - services should be small. They should not be turned into a pile of monoliths.


So, the positive things that I was able to highlight for myself, in general, they all pass under the same trend: Incredible convenience for development:



What should be able to our application? So it is not very much.
Four pages:



The functional is simple:



Docker



Everything, we will not be more about the theory, let's saw application. We will have it on docker. It is almost impossible to develop such a distributed application on a single machine without virtualization. The job description of the docker will be represented by scraps, since it goes beyond the topic of this article. It is assumed that you know something about him.


By the way, here is a link to a turnip from which you can download and start a blog, see something by the code below. https://github.com/gregory-vc/blog


How many containers will there be in our simplest blog? A container is, by the way, essentially a virtualization of a separate server that communicates over the network with other containers, though if you carry out a hard analogy container = server, some containers will have to be abandoned, but nonetheless. For the simplest implementation of a blog on microservices, I counted 24 containers. Let's get a look.



Why on two copies of some services? Because one will not be interesting and not clear.


The docker-compose file that will deploy all of this with one command looks like this:
https://github.com/gregory-vc/blog/blob/master/host/docker-compose.yml
From the most interesting, let's consider the php container settings of our gateway.


'php_gate': image: 'tattoor/blog_php' container_name: 'php_gate' volumes_from: ['source_gate'] volumes: ['./logs/php/gate/:/var/log/dev_php'] links: - nginx_post_1:post1.blog - nginx_post_2:post2.blog - nginx_comment_1:comment1.blog - nginx_comment_2:comment2.blog - nginx_auth_1:auth1.blog - nginx_auth_2:auth2.blog - redis environment: - POST_1_HOST=post1.blog - POST_1_PORT=80 - POST_2_HOST=post2.blog - POST_2_PORT=80 - COMMENT_1_HOST=comment1.blog - COMMENT_1_PORT=80 - COMMENT_2_HOST=comment2.blog - COMMENT_2_PORT=80 - AUTH_1_HOST=auth1.blog - AUTH_1_PORT=80 - AUTH_2_HOST=auth2.blog - AUTH_2_PORT=80 

The links container description section is basically just editing / etc / hosts /


 docker exec php_gate cat /etc/hosts 172.17.0.36 auth1.blog 86b8b266477d nginx_auth_1 172.17.0.36 nginx_auth_1 86b8b266477d 172.17.0.21 comment1.blog 836bacb42e78 nginx_comment_1 172.17.0.19 comment2.blog c554a8888801 nginx_comment_2 172.17.0.20 post2.blog 37f81921419c nginx_post_2 172.17.0.7 redis a1932016be87 172.17.0.37 auth2.blog 5715045a213b nginx_auth_2 172.17.0.37 nginx_auth_2 5715045a213b 172.17.0.21 nginx_comment_1 836bacb42e78 172.17.0.19 nginx_comment_2 c554a8888801 172.17.0.22 nginx_post_1 1cc1ef5ab896 172.17.0.22 post1.blog 1cc1ef5ab896 nginx_post_1 172.17.0.20 nginx_post_2 37f81921419c 172.17.0.23 fafe93f31a67 

Where at the designated host we simply have access to another container through the internal network of the docker.


And the environment section is just a designation of variables that we can get with you inside the application via getenv (). It is designed so that the docker-compose file is a single entry point to configure the entire application.


While the structure of our services looks like simple directories lying nearby,



But, in fact, when you start the docker of hosts, each of these directories is inside a separate isolated container. This is done somehow like this:


 'source_post_1': volumes: ['../Services/Post:/home/gregory/source/'] 

That is, even though they are now nearby, it will not be possible to incline the class of another service or something like that from one service at startup. Nearby they are exclusively from convenience, in real life they should be each in their repository, not touching each other at all.


Gate service



This service will be the entry point to our blog, it is he who will render the templates, display the result and pull the services he needs. By the way, there are different approaches, for example, you can refuse a single entry point and implement everything on the frontend. That is, the browser itself will go to the necessary services and collect the result directly in the browser. What can I say, it all depends on your particular case, and there and there are pros and cons.


So, we have php and nothing else. Although, let's take at least composer, where without it. Create two more directories, one with our microframe, which we now write, the second for public scripts, js, and other resources.


Looks like that:



In the composer, just specify where to do autoload, so that we don’t bother with this, and connect the generated autoload to public / index.php


So, we already have something, let's decide what else we need?



Not bad, what else?



Let's write here such a repository of objects so as not to create them anywhere, but to be able to access (inject) the applications already created at any point with all the necessary dependencies (dependency). We will not be entertained with Reflection and other interesting things, our time is strictly limited.


  Storage::set('Request', new Request()); Storage::set('Router', new Router()); Storage::set('Redirect', new Redirect()); Storage::set('App', new App()); 

In Di, using this storage we simply add all the objects that we need.
In public, we start Di, we get a router, we register all URLs that will be useful to us, we get the application and start it.


  $router->get('/logout/', 'AuthController@logout'); $router->get('/404', 'SystemController@notFound'); $router->post('/post/add_request/', 'PostController@add') 

In the application we get the request, the map in the router is the existing action of the existing controller for this request, at the same time we also write to the request all the post or het variables that came to us.


Execute the controller method, get the response, render the response and show the result of our work, that's all.


  $current_request = $this->router->getCurrent(); $controller = new $current_request->controller; $response = $controller->{$current_request->method}(); $response->render(); 

The framework is there, now we need to work with services, create a directory with services, create a class for each service, describe access points to each of the services. We inherit them from the main class of services, where we implement query options.


https://github.com/gregory-vc/blog/blob/master/Services/Gate/My/Engine/Service.php


  static public function get($method, $params = []) { $service = new static; return $service->executeGet($method, $params); } static public function post($method, $params = []) { $service = new static; return $service->executePost($method, $params); } 

There at the request we select a random connector from the service provided, something like that


  $rand_connector = rand(0, $count_connector) % $count_connector; 

Make a request from the controller and render it like this:


  $posts = Post::get('all'); return $this->response->html('posts', $posts); 

We need to render, but how? We have no template engines. Write your own? Well no. Just use php.


  ob_start(); require_once($layout_template); $contents = ob_get_contents(); ob_end_clean(); $this->content = $contents; 

Extremely powerful template engine the size of 4 lines.


Post and comment service


What's next? Now we can make inquiries and render the result, now we need to write services giving the answer. Everything is just copying our new engine to other services, changing urls and writing work with models and databases, instead of remote services.


Implement working with models, standard findAll, findBy, add, save:
https://github.com/gregory-vc/blog/blob/master/Services/Auth/My/Engine/Model.php


So? To be honest, this is almost all that we need, not counting authorization.
We can make gate requests for any service, from any other service to any other.


Authorization service
The scheme is simple: we have users and their access on the authorization server, we make an authorization request from the gateway, generate a token, return it to the gateway and another user, put the user and the token into the session and that's it. Unforgettable to send a token with a request to add a post, because what? That's right, the service of the posts will go to the authorization service and ask, is it true that this token is good? Depending on the result, different generators are generated.


  public function login($user, $password) { $hash = hash('sha256', $password); $user = User::findBy([ 'login' => $user, 'password' => $hash ]); if (!empty($user) && is_array($user)) { $user = current($user); $user['token'] = bin2hex(random_bytes(30)); User::save($user); return [ 'login' => $user['login'], 'token' => $user['token'] ]; } else { throw new \Exception('Not found user'); } } 

Result


In general, you can download and deploy it in one command, remind the repository: https://github.com/gregory-vc/blog


Where appropriate in meaning, I brought out for clarity which particular node was generated by one or another block.


I was also impressed by the time of page generation. This is 5-9 ms for a page with a post and a few comments (!). Yes, all this is biased, yes, all this is parrots, yes, microservices have nothing to do with it, yes, depending on what to compare. But. The same laravel generates its page, generally without requests and data, just a greeting, in 90 ms, on my own machine. It is 10-20 times longer.


I understand what is happening there much more than anything else, do not compare, but nevertheless, I will try to express the thought: for the concrete task of a separate isolated microservice it is not necessary for all this. For service comments, I threw out the class of work with services on the network. For the gateway service, I threw out the base work class. For each service I collected only what he needed. And the right service should be quite a bit :)


And the main thing is the incredible potential for scaling this blog for unbelievable loads. No one will interfere, for example, then take and rewrite the comment service on Go.


Problems


Network overhead
Not knowing how another service works, we can completely get into a situation where it is not that it works poorly and spoils everything, it also uses our service (!) So that we can give our results.


Let me remind you how to try it all


 Clone git clone https://github.com/gregory-vc/blog.git . Install Docker: wget -qO- https://get.docker.com/ | sh sudo usermod -aG docker user sudo apt-get install python-pip sudo pip install docker-compose Compile chmod 744 compile ./compile chmod 744 upload_db ./upload_db Run http://gate.blog:30001/ admin admin 

')

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


All Articles