В этой статье хочу поделится нашими c SergeyMaslov наработками решения типовых задач с использованием микросервисной архитектуры на примере задачи «создание блога» (в надежде, что читатель представляет как устроен блог и это не должно вызывать вопросов по функциональности:)
Итак, наш блог будет состоять из 5 микросервисов, написанных на golang:
- API Gateway (api-gw) – отвечает за маршрутищацию, аутентификацию, логирование и трасировку запросов
- Пользователи (user) – регистрация/аутентификация пользователей, логирование, трасировка запросов
- Статьи (post) – создание/чтение/изменение/удаление статей (CRUD), логирование, трасировка и авторизация запросов
- Комментарии (comment) – создание/чтение/изменение/удаление комментариев (CRUD), логирование, трасировка и авторизация запросов
- Категории (category) – создание/чтение/изменение/удаление категорий (CRUD), логирование, трасировка и авторизация запросов
Клиентское приложение (web/frontend) будет реализован на vue.js и будет взаимодействовать с микросервисами через REST API, а сами микросервисы будут взаимодействовать друг с другом по gRPC.
В качестве хранилища мы будем использовать MongoDB.
Отдельной вишенкой на торте покажем, как с минимальными трудозатратами поддерживать документацию API (в формате swagger) в актуальном состоянии в активно развивающемся проекте.
Компонентная схема блога
Каждый микросервис будет реализован в отдельном Docker контейнере, а запуск проекта будет осуществляться с помощью docker-compose.
Сразу оговорюсь в примере, для упрощения процесса разработки, буду использовать два допущения, которые не следует использовать в продакшене.
- База данных развернута в Docker контейнере. Такой подход снижает надежность хранилища (за исключением схемы, о которой говорилось на HighLoad 2018).
- Весь проект размещен в одном git-репозитории. Этот подход противоречит одному из основных принципов микросервисной архитектуры — изолированность, и увеличивает вероятность появления межкомпонентной связанности.
Демо проекта можно посмотреть здесь, а исходный код здесь.
Структура проекта
Как будет построен процесса разработки
Как я уже ранее говорил, взаимодействие между микросервисами будет построено на основе gRPC. В двух словах gRPC это высокопроизводительный фреймворк, разработанный компанией Google, для вызова удаленных процедур (RPC) — работает поверх HTTP/2. В основе gRPC лежит так называемый протофайл (см. пример ниже), основная задача которого в компактной форме задекларировать две вещи:
- дать полный перечень интерфейсов сервиса (аналог API интерфейсов);
- описать что подается на вход каждого интерфейса и что получаем на выходе.
Ниже, в качестве примера, приведен протофайла сервис Category.
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;
}
Теперь, когда мы в общих чертах разобрались зачем нужен протофайл, посмотрим как будет выглядеть процесс разработки наших микросервисов:
- Описываем структуру сервис в протофайле;
- Запускаем генератор кода (./bin/protogen.sh), он сгенерит нам основную часть серверного кода + создаст клиентский код, например, для API Gateway + создаст актуальную документацию в формате swagger;
- Все что нам останется сделать своими руками, это написать код реализацию интерфейсов в специальном файле /protobuf/functions.go.
Далее, если мы захоти внести изменения в один из наших микросервисов, действуем по вышеописанному алгоритму: правим протофайл, запускаем protogen, правим реализацию в functions.go, а в документацию и к клиентам изменения “уедут” автоматически.
Продолжение в статье «Пишем блог на микросервисах часть 2 API Gateway».
Автор: Ptimofeev