В этой статье хочу поделится нашими 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) в актуальном состоянии в активно развивающемся проекте.

Компонентная схема блога


image

Каждый микросервис будет реализован в отдельном 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;
}

Теперь, когда мы в общих чертах разобрались зачем нужен протофайл, посмотрим как будет выглядеть процесс разработки наших микросервисов:

  1. Описываем структуру сервис в протофайле;
  2. Запускаем генератор кода (./bin/protogen.sh), он сгенерит нам основную часть серверного кода + создаст клиентский код, например, для API Gateway + создаст актуальную документацию в формате swagger;
  3. Все что нам останется сделать своими руками, это написать код реализацию интерфейсов в специальном файле /protobuf/functions.go.

Далее, если мы захотим внести изменения в один из наших микросервисов, действуем по вышеописанному алгоритму: правим протофайл, запускаем protogen, правим реализацию в functions.go, а в документацию и к клиентам изменения “уедут” автоматически.

Продолжение в статье «Пишем блог на микросервисах часть 2 API Gateway».

Комментарии (8)


  1. DmitriyTitov
    29.10.2019 00:56

    Каковы на ваш взгляд основные причины для того чтобы разделить сайт с журналами на отдельные микрослужбы? Какие проблемы вы решаете таким образом?
    Ещё интересуют причины выбора Mongo вместо традиционных реляционных БД. В чём преимущество Mongo с вашей точки зрения?


    1. Ptimofeev Автор
      30.10.2019 11:55

      Из основных причин, пожалуй это высокая нагрузка, но из моего опыта у систем такого класса это достаточно редкое явление ) Также использование микросервисной архитектуры позволяет сделать разные части проекта независимыми, т.е. например, отказ в работе комментариев не отразится на работоспособности других сервисов. В целом, цель статьи показать вариант реализации взаимодействия между микросервисами на простом и понятном примере. MongoDB выбрана с целью децентрализации данных. Использование единой релеационной БД вносит ненужную связанность между микросервисами, а использование отдельных релеационных БД под каждый микросервис, на мой взгляд, избыточно.


  1. karl93rus
    29.10.2019 08:19

    А ещё я никак не могу понять смысл gRPC. Зачем ещё какая-то прослойка, если можно просто поднять микросервис и по урлу его дёргать? Объясните пожалуйста.


    1. xkondorx
      29.10.2019 08:32

      Я тоже не могу найти объективных причин усложнения архитектуры, объемы передаваемой информации не оправдывают его применение. Ну и раз уж речь зашла о gRPC то не плохо было бы сюда прикрутить что нибудь вроде егеря для трассировки. Чтобы решить те задачи которые в данном случае решает gRPC, достаточно реализовать REST Client для каждого микросервиса в виде пакета.


      1. Sly_tom_cat
        29.10.2019 16:49

        Ну если относиться к проекту как к демонстратору технологий, то решения — вполне уместные, разве что вот Mongo немного смущает…


        1. xkondorx
          29.10.2019 16:53

          Вроде как все сущности укладываются в рамки «документа», транзакционно зависимых CRUD операций на ум не приходит. Почему бы и не mongo, по сути на его месте может быть любая БД.


  1. time2rfc
    29.10.2019 12:23

    При написании связанных сервисов на GO столкнулся с тем что много кода дублируют друг дружку, особенно части отвечающие за транспорт и бизнесс-область, я правильно понимаю что по этой причине у вас все в одном моно-репозитории?
    Сам думал о моно-репе или модных-новых-классных модулях


    1. Ptimofeev Автор
      30.10.2019 12:03

      Мое личное мнение, моно-репа это наследие мышления в системе координат монолитых решений. Избыточность кода, это цена за независимость и децентрализованность. Представьте что каждый отдельный микросервис «пилит» отдельная команда, которая ничего не знает о коде других команд. В этом смысле избыточность выглядет уже иначе )
      В примере моно-репа использовалась для упрощения процесса запуска проекта и понимания структуры проекта в целом.