So, our blog will consist of 5 microservices written in golang:
- Gateway API (api-gw) - responsible for routing, authentication, logging and tracing of requests
- Users (user) - registration / authentication of users, logging, tracing of requests
- Articles (post) - create / read / modify / delete articles (CRUD), logging, tracing and authorization of requests
- Comments (comment) - create / read / modify / delete comments (CRUD), logging, tracing and authorization of requests
- Categories (category) - creating / reading / changing / deleting categories (CRUD), logging, tracing and authorization of requests
The client application (web / frontend) will be implemented on vue.js and will interact with microservices via the REST API, and the microservices themselves will interact with each other via gRPC.
As storage we will use MongoDB.
We will show with a separate cherry on the cake how to keep the API documentation (in swagger format) up-to-date in an actively developing project with minimal labor.
Blog Component Diagram
Each microservice will be implemented in a separate Docker container, and the project will be launched using docker-compose.
Immediately make a reservation in the example, to simplify the development process, I will use two assumptions that should not be used in production.
- The database is deployed in a docker container. This approach reduces storage reliability (with the exception of the scheme discussed at HighLoad 2018).
- The whole project is hosted in one git repository. This approach contradicts one of the basic principles of microservice architecture - isolation, and increases the likelihood of inter-component connectivity.
You can see the demo of the project here , and the source code here .
Project structure
How will the development process be built
As I said earlier, the interaction between microservices will be built on the basis of gRPC. In a nutshell, gRPC is a high-performance framework developed by Google for calling remote procedures (RPC) - it works on top of HTTP / 2. GRPC is based on the so-called protofile (see example below), the main task of which is to declare two things in a compact form:
- give a complete list of service interfaces (analogue of API interfaces);
- describe what is fed to the input of each interface and what we get at the output.
Below, as an example, the protofile of the Category service is given.
syntax = "proto3"; package protobuf; import "google/api/annotations.proto"; // Category service CategoryService { // rpc Create (CreateCategoryRequest) returns (CreateCategoryResponse) { option (google.api.http) = { post: "/api/v1/category" }; } // rpc Update (UpdateCategoryRequest) returns (UpdateCategoryResponse) { option (google.api.http) = { post: "/api/v1/category/{Slug}" }; } // rpc Delete (DeleteCategoryRequest) returns (DeleteCategoryResponse) { option (google.api.http) = { delete: "/api/v1/category/{Slug}" }; } // SLUG rpc Get (GetCategoryRequest) returns (GetCategoryResponse) { option (google.api.http) = { get: "/api/v1/category/{Slug}" }; } // rpc Find (FindCategoryRequest) returns (FindCategoryResponse) { option (google.api.http) = { get: "/api/v1/category" }; } } //------------------------------------------ // CREATE //------------------------------------------ message CreateCategoryRequest { string ParentId = 1; string Name = 2; string Path = 3; } message CreateCategoryResponse { Category Category = 1; } //------------------------------------------ // UPDATE //------------------------------------------ message UpdateCategoryRequest { string Slug = 1; string ParentId = 2; string Name = 4; string Path = 5; int32 Status = 6; } message UpdateCategoryResponse { int32 Status =1; } //------------------------------------------ // DELETE //------------------------------------------ message DeleteCategoryRequest { string Slug = 1; } message DeleteCategoryResponse { int32 Status =1; } //------------------------------------------ // GET //------------------------------------------ message GetCategoryRequest { string Slug = 1; } message GetCategoryResponse { Category Category = 1; } //------------------------------------------ // FIND //------------------------------------------ message FindCategoryRequest { string Slug = 1; } message FindCategoryResponse { repeated Category Categories = 1; } //------------------------------------------ // CATEGORY //------------------------------------------ message Category { string Slug = 1; string ParentId = 2; string Path = 3; string Name = 4; int32 Status = 5; }
Now that we have figured out in general terms why a protofile is needed, let's see how the development process of our microservices will look:
- We describe the structure of the service in the protofile;
- We start the code generator (./bin/protogen.sh), it will generate the main part of the server code for us + will create client code, for example, for the API Gateway + will create up-to-date documentation in the swagger format;
- All we need to do with our own hands is to write the code for the implementation of the interfaces in a special file /protobuf/functions.go.
Further, if we want to make changes to one of our microservices, we proceed according to the above algorithm: we edit the protofile, run protogen, we edit the implementation in functions.go, and the changes will “leave” automatically to the documentation and to the clients.
Continued in the article "Writing a Blog on Microservices Part 2 of Gateway API . "