Как правильно передавать объекты между микросервисами?

С микросервисами столкнулся недавно

Предположим есть два микросервиса: Первый (работает с пользователями, назовем его Users) и второй (работает со статистикой, документами и т.п., назовем его Documents), у обоих есть свои контроллеры, модели т.д., но что если надо будет запросить документ по определенному пользователю (как пример), понятно, что надо будет дернуть контроллер в миркосервисе Documnets, передав ему ID пользователя, но модель которую вернет контроллер, будет доступна только в его микросервисе (Documents), а в Users такой модели нет, получается надо добавить какую-то общую модель как для Users так и для Documents? И второе, как передать запрос из Users в Documents, через обычный HTTP client или есть другое решение?


Ответы (3 шт):

Автор решения: Uranus

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

Однако на практике многие компании предпочитают использовать единый стек технологий для всех микросервисов. Это упрощает разработку, снижает стоимость сопровождения и позволяет одной команде работать с несколькими сервисами. В такой ситуации имеет смысл выделить общие DTO (Data Transfer Object) -- классы, которые используются для передачи данных между сервисами. Эти классы должны быть независимы от внутренней структуры и бизнес-логики каждого сервиса. Лучше всего размещать их в отдельной библиотеке, которая будет подключаться к проектам.

Есть несколько подходов для передачи данных между микросервисами:

  1. HTTP-клиент -- простой и понятный способ выполнения REST-запросов, но требует передачи и проверки токенов безопасности.
  2. Шина сообщений (RabbitMQ, Kafka) -- асинхронный подход к взаимодействию. Устойчив к сбоям, хорошо масштабируется, подходит для систем с событийной архитектурой.
  3. gRPC -- эффективен для синхронного взаимодействия с высокой частотой вызовов. Обеспечивает хорошую производительность и поддерживает строгую типизацию данных. Используется в сценариях где важна низкая задержка.

Выбор подхода зависит от конкретных требований системы. Важно поддерживать слабую связанность между сервисами, минимизировать общие зависимости и обеспечивать четкие контракты для передачи данных.

→ Ссылка
Автор решения: Faraday

К примеру вы пишите обучающую програмку, суть которой в реализации какого-то функционала api (для примера, возьмём какой-то крипто-апи). Что вы будете делать в таком случае?

У вас есть два подхода

  1. Если у вас микросервисы связанны, вы можете установить для них одинаковы зависимости для тех библиотек (кодовой базы), которые вам нужно. В таком случае Documents и Users будут использовать одну кодовую базу для общения

  2. Да, переписать все нужные вам модели и, по факту, "продублировать" уже существующие модели в другом проекте

Но наиболее логичные подходы. Если вернуть в кому же примеру реализации API, вы будете искать библиотеку какую-то, внутри которой есть уже готовые классы и модели для общения с этим же API. В ином случае, вы будете вытягивать все json и через какой-то quicktype.io переписывать в свои классы

UPD: Да, как так же упомянул @Uranus, есть технологии (для примера gRPC), через которые вы можете делать "контракты" между нужными вам частями вашей экосистемы.

→ Ссылка
Автор решения: Andrei Khotko

Существуют разные подходы, но предложу тот, который использовался у нас на проектах. Предположу, что все микросервисы написаны на C#. Тогда:

Добавляем два проекта, которые будут являться частью Documents.sln решения, где расположены проекты микросервиса Documents:

  1. Documents.Contracts - Здесь содержатся модели, которые возвращаются API-шкой сервиса Documents
  2. Documents.ApiClient - содержит классы, с помощью которых можно вызывать методы сервиса Documents. Эти классы являются по сути оберткой над HttpClient, которая знает все рауты к нужным методам и умеет правильно их вызывать.

Два этих проекта должны публиковаться как nuget-пакеты, и подключаться в проектах микросервиса Users.

Подводя итоги, мы добились следующие удобства:

  1. Единый способ работы с микросервисом. Любой другой микросервис может просто взять готовый нугет-пакет и использовать его.
  2. Когда разрабатывается новая фича, и в микросервисе появляется новый API метод, этот метод добавляется в микросервис и клиент этого микросервиса. После добавления публикуется новая версия нугет-пакета Client, которую достаточно будет обновить / добавить в нужное место. Также и с изменением контрактов, если нужно какой-то из них расширить.
  3. Можно легко написать интеграционные тесты и проверить, что клиент в связке с развернутым микросервисом работает исправно.

Ниже идет пример того, как это может выглядеть. Здесь позволил себе не добавлять обработку исключений

Проект Documents.Contracts, класс UserDocumentContract

using System.Text.Json.Serialization;

namespace Documents.Contacts
{
    public class UserDocumentContact
    {
        [JsonPropertyName("userId")]
        public int UserId { get; set; }

        [JsonPropertyName("documentId")]
        public int DocumentId { get; set; }

        [JsonPropertyName("documentType")]
        public string DocumentType { get; set; }

        [JsonPropertyName("content")]
        public string Content { get; set; }
    }
}

Проект Documents.ApiClient, класс DocumentsApiClient

using System.Text.Json;
using Documents.Contacts;

namespace Documents.ApiClient
{
    public class DocumentsApiClient
    {
        private static readonly HttpClient HttpClient = new();

        public DocumentsApiClient(string apiUrl)
        {
            HttpClient.BaseAddress = new Uri(apiUrl);
        }

        public async Task<IReadOnlyList<UserDocumentContact>> GetUserDocuments(int userId)
        {
            using HttpResponseMessage response = await HttpClient.GetAsync($"user-docs/{userId}");

            response.EnsureSuccessStatusCode();

            var jsonResponse = await response.Content.ReadAsStringAsync();
            var contract = JsonSerializer.Deserialize<List<UserDocumentContact>>(jsonResponse);
            return contract;
        }
    }
}
→ Ссылка