ProIT: медіа для профі в IT
13 хв

Фреймворк gRPC: як із ним працювати та чим він кращий за REST API

author avatar NIX Team

Netflix, Docker, Spotify, Dropbox та багато інших IT-гігантів вирішили стандартизувати мікросервіси із фреймворком gRPC. Цей інструмент Google презентували у 2015 році, й за цей час він став гарною альтернативою популярному до того REST API.

У цій статті Роман Махник та Ігор Кочат, .NET Developers у NIX, розкриють основні особливості gRPC і пояснять, як працювати з цією технологією на прикладі gRPC-сервісу, написаного на мові C#.

Чому ІТ-проєкти відмовляються від монолітної архітектури

Подібно до інших фреймворків для API gRPC має забезпечувати швидку, стабільну та безпечну взаємодію між різними сервісами. У цьому контексті йдеться про систему з невеликих вебзастосунків, кожен із яких виконує свою унікальну функцію великого застосунку.

Візьмемо як приклад інтернет-магазин ігор, що складається з трьох сервісів: один надає ігри, другий показує досягнення гравця, а третій формує знижки. Кожна дія у такому магазині потребує звернення до певного сервісу. Тож цілком логічним виглядає ідея об’єднання всіх в одну систему з єдиною базою даних. Це і є мікросервісний підхід при проєктуванні застосунків.

Щодо відмови від традиційних монолітів є кілька причин:

  • Масштабованість. Монолітні програми важко розширювати. Дані знаходяться на одному сервері, його модернізація недешева. Розміщення ж модуля або застосунку в цілому на іншому пристрої можливе лише при розділенні моноліту. А при використанні мікросервісної архітектури багато невеликих сервісів може бути розміщено на одній машині. При цьому ті, які мають велике навантаження, можна винести на окремі пристрої, зокрема й на хмарних платформах типу AWS Lambda та Azure Function. Там мікросервіси можна масштабувати автоматично, за необхідності, а оплата за ресурси буде залежати від фактичного часу роботи. Це вигідно і зручно. А для моноліта навіть у хмарі потрібна окрема, мінімум одна віртуальна машина із фіксованою абонплатою.
  • Оновлення та додавання функціоналу. Монолітні програми ускладнюють процес впровадження нових фіч. Адже при модифікації навіть малозначного елемента треба зупиняти весь застосунок. А в мікросервісній архітектурі тільки змінна частина перезапускається, тоді як решта системи функціонує без переривань.
  • Процес тестування. Всі компоненти монолітного застосунку зосереджені разом, через що потрібно запускати всю програму для проведення тестів. З мікросервісами можна окремо тестувати кожен компонент, не торкаючись інших частин системи.

При переході до мікросервісів одним із ключових моментів є розподіл відповідальності. Адже різні маленькі сервіси не мають дублювати функції. Тому критично важливо налагодити ефективне спілкування між ними.

Його можна будувати за двома методами:

  • Синхронна комунікація. В цьому випадку один сервіс чекає на відповідь від іншого.
  • Асинхронна комунікація. Один сервіс відправляє повідомлення в чергу, другий забирає його, коли готовий, і повертає відповідь.

У кожного методу є переваги та недоліки, які залежать від конкретної ситуації. Тож у статті ми зосередимося лише на синхронній комунікації. Для її реалізації найчастіше використовують REST та gRPC.

REST та gRPC: чим відрізняються

Для розуміння ключових відмінностей треба розібратися, як влаштовані обидва фреймворки.

У REST із використанням стандартної CRUD-логіки зазвичай застосовуються такі основні типи HTTP-запитів:

  • GET для читання.
  • POST для додавання.
  • PUT та PATCH для модифікації.
  • DELETE для видалення.

За схемою REST клієнт та сервер пов’язані через мережу. Коли клієнт надсилає HTTP-запит, сервер обробляє його і повертає HTTP-відповідь. І поки сервер обробляє запит, клієнт створює окремий потік та очікує.

Але HTTP-запити можна замінити на виклики віддалених процедур, тобто RPC. Схема взаємодії залишиться схожою, проте надсилання й отримання запитів і відповідей будуть трохи іншими.

RPC є методом виклику віддалених функцій або процедур, який дозволяє одній програмі викликати метод, функцію чи процедуру в іншій програмі у мережі. З цим методом клієнт та сервер спілкуються через спеціальні проксі-об’єкти, відомі як заглушки (stub).

Тобто створюється враження, що клієнт викликає процедури (методи) так, ніби вони реалізовані прямо в нього. Ці заглушки перенаправляють дані за відповідною адресою. Це спрощує програмування, адже не треба нічого робити в самому коді, описувати HTTP-заголовки, методи тощо.

Заглушки створюються автоматично, і розробнику залишається викликати потрібні функції або процедури.

Одне з найкращих застосувань цієї концепції представлено у вигляді gRPC. gRPC – це система, що реалізує традиційний RPC із кількома оптимізаціями.

На фоні інших RPC-реалізацій у цього фреймворку є низка переваг:

  • Просте визначення сервісів. Це можливо завдяки .proto-файлам.
  • HTTP/2. Це надає підтримку двосторонньої потокової передачі даних. Попри те, що HTTP/2 вже був доступний від ASP.NET Core 3 (документація, приклад), gRPC створений спеціально для того, щоб ефективно використовувати цей протокол.
  • Трасування. Ідеальний інструмент для діагностики та покращення сервісів, який надає можливість відстежувати виклики процедур.
  • Механізм Health check. Він потрібен для швидкої перевірки стану сервісу та його готовності обробляти запити. Це полегшує балансування навантаження.
  • Балансування навантаження. Воно забезпечує рівномірне розподілення завдань між серверами, що спрощує масштабування. Цей процес може здійснюватися як на рівні клієнта, так і через проксі з використанням сервіс-меша.
  • Інтеграції системи автентифікації. У ранніх версіях протоколу така можливість була відсутня, через що RPC загалом використовували лише для внутрішніх комунікацій.

Порівняно з технологією REST gRPC також демонструє низку переваг:

  • Застосування Protobuf. На відміну від текстового JSON або XML, що використовується в REST і не піддається стисненню, Protobuf є бінарним форматом. Сервер визначає структуру даних, використовуючи мову опису інтерфейсу протокольного буфера (IDL) у файлі специфікації прототипу. Потім gRPC серіалізує структуру в бінарний формат, а потім десеріалізує її на будь-яку вказану мову програмування. Цей механізм робить це швидше, ніж під час використання JSON, який не стискається під час передачі. Хоча gRPC пропонує вбудовану підтримку JSON.
  • Обробка HTTP-запитів. Для цього не потрібен постійний аналіз можливих статус-кодів і змісту даних, як це відбувається у REST. Виклик процедур максимально спрощений.
  • Зручний опис контрактів. REST вимагає для опису інтерфейсів та документації таких додаткових інструментів, як OpenAPI або Swagger. А контракти в gRPC визначаються безпосередньо через .proto-файли.
  • Двонапрямний потоковий зв’язок. Клієнт і сервер можуть надсилати й отримувати кілька запитів та відповідей одночасно по одному з’єднанню. REST не пропонує цієї функції. Використовуючи REST API, клієнт надсилає серверу один запит REST API, а потім сервер відправляє одну відповідь. Клієнт повинен дочекатися її, перш ніж продовжити роботу. Цей механізм є моделлю запиту-відповіді і є унарним з’єднанням даних (один-до-одного). За допомогою gRPC клієнт може надіслати один або кілька запитів API на сервер, що може призвести до однієї або кількох відповідей від сервера. З’єднання даних можуть бути унарними (один-до-одного), потоковими серверами (один-до-багатьох), потоковими даними клієнта (багато-до-одного) або двонанапрямними потоками (багато-до-багатьох). Цей механізм можливий, оскільки gRPC заснований на HTTP/2.
  • HTTP/2. REST часто вдається до старшого HTTP/1.1.

Про останній пункт треба сказати окремо.

Переваги HTTP/2 включають:

  • Бінарний формат даних, що забезпечує зменшення обсягу переданої інформації та покращує продуктивність.
  • Оптимізація трафіку завдяки більш ефективному стисканню HTTP-даних, насамперед заголовків.
  • Підтримка передачі даних у потоках.
  • Мультиплексування, завдяки якому в HTTP/2 достатньо одного з’єднання для передачі кількох файлів. А в HTTP 1.1 для, наприклад, трьох файлів потрібно й три з’єднання.
  • Пріоритезація потоків при передачі.

Налаштування з’єднання gRPC

Для створення gRPC-каналу потрібно виконати такі кроки:

  1. Відкрити сокети.
  2. Ініціювати з’єднання TCP між клієнтом та сервером.
  3. Узгодити TLS.
  4. Встановити HTTP/2-з’єднання.
  5. Запустити виклик gRPC-процедури.

Можна використовувати й механізм Persistent Connection. У такому випадку достатньо встановити одне з’єднання й уникнути відразу чотирьох перших етапів.

Але з таким механізмом виникають складнощі у налаштуванні балансування навантаження. Наприклад, якщо вам потрібно кілька екземплярів сервісу, їх треба якось розподіляти.

Для цього існує два рішення:

  • Балансування на рівні клієнта. Для цього клієнтська бібліотека працює з кількома інстансами та надсилає запити у певному відсотковому співвідношенні.
  • Балансування за допомогою проксі. Якщо для хостингу серверів використовується оркестратор на кшталт Kubernetes, то він може відповідно до ситуації прибирати або додавати інстанси. З цим допоможе балансування через проксі Service Mesh: Linkerd, Istio, Consul тощо. Клієнт встановлює стабільне з’єднання з Mesh, а той відстежує появу чи зникнення екземплярів сервісу та вирішує, як розподіляти з’єднання. Тож коннект буде лише з актуальними сервісами, хоча клієнту це не важливо, оскільки він працює з одним з’єднанням.

Хтось може порівняти gRPC із фреймворком WCF. Проте перший є спеціалізованим інструментом для фактично однієї задачі, а другий – це універсальне рішення з підтримкою як RPC, так і REST чи SOAP.

Однак у WCF є вада: залежність від платформи .NET. А gRPC працюватиме з будь-яким середовищем, плюс писати його можна будь-якою мовою з тих, що підтримуються.

Щоправда, gRPC не може повноцінно інтегруватися у браузери через відсутність реалізації HTTP/2-комунікації. Це пов’язано з відсутністю контролю каналу, який можливий у .NET-застосунку. Для цього в самому Google пропонують використовувати gRPC-проксі. У такому випадку браузер відправлятиме стандартні запити HTTP 1.1 на проксі, а вже він конвертуватиме повідомлення на gRPC-коли.

Створення gRPC-застосунку

Для демонстрації принципів створення застосунків для gRPC ми розберемо простий сервіс, який зберігає визначення слів. Ми зможемо додавати слова та визначення у словник, а потім так само отримувати визначення слів.

Першим кроком треба відкрити Visual Studio, де вже є готові шаблони для розробки таких сервісів. Для цього необхідно вибрати опцію «Створити новий проєкт» та ввести «gRPC», після чого система запропонує відповідний темплейт.

У проєкті міститься .proto-файл, який визначає сервіс та інтерфейс. Сам сервіс має процедуру SayHello. Вона приймає об’єкт Hello, який описаний далі, і відправляє назад реплай, який описаний у цьому ж файлі.

Ексклюзивною особливістю для .NET є опція csharp_namespace, яка вказує місце створення класів для цього застосунку.

syntax = "proto3";
option csharp_namespace = "GrpcExample";
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}

Кожна властивість у повідомленнях, що надсилаються та одержуються, має свій порядковий номер. Це дозволяє визначати послідовність передачі даних (з огляду на бінарний формат) та сумісність із попередніми версіями.

Якщо на серверній стороні оновлюється .proto-файл, змінюючи властивості, то клієнт із не оновленим файлом пропустить нові властивості й читатиме старі як порожні.

Тепер нам потрібно замінити цей код згідно з нашою логікою:

syntax = "proto3";
option csharp_namespace = "GrpcExample";
package definition;
service Definition {
rpc AddDefinition (AddDefinitionRequest) returns (AddDefinitionResponse);
rpc GetDefinition (GetDefinitionRequest) returns (GetDefinitionResponse);
}
message AddDefinitionRequest {
  string word = 1;
  string definition = 2;
}
message AddDefinitionResponse {
  string message = 1;
  int32 dictionarySize = 2;
}
message GetDefinitionRequest {
  string word = 1;
}

Також змінимо наш сервіс:

using Grpc.Core;
using GrpcExample.Interfaces;
using GrpcExample.Repositories.Interfaces;
namespace GrpcExample.Services
{
    public class DefinitionService : Definition.DefinitionBase, IDefinitionService
    {
        private readonly ILogger<DefinitionService> _logger;
        private readonly IDefinitionRepository _definitionRepository;
        public DefinitionService(ILogger<DefinitionService> logger, IDefinitionRepository definitionRepository)
        {
            _logger = logger;
            _definitionRepository = definitionRepository;
        }
        public async override Task<AddDefinitionResponse> AddDefinition(AddDefinitionRequest request, ServerCallContext context)
        {
            var key = request.Word.Trim();
            await _definitionRepository.Create(key, request.Definition);
        	return new AddDefinitionResponse
        	{
            	Message = "Definition added successfully.",
            	DictionarySize = await _definitionRepository.Count()
        	};
        }
        public async override Task<GetDefinitionResponse> GetDefinition(GetDefinitionRequest request, ServerCallContext context)
        {
            var key = request.Word.Trim();
            var data = await _definitionRepository.Get(key);
            if (data == null)
            {
                return new GetDefinitionResponse
                {
                    Definition = string.Empty,
                    Found = false 
                };
            }
            return new GetDefinitionResponse
	{
                Definition = data, 
                Found = true 
            };
        }
    }
}

Для прикладу додамо простий in-memory репозиторій:

using GrpcExample.Repositories.Interfaces;
using System.Collections.Concurrent;
namespace GrpcExample.Repository
{
    public class DefinitionRepository : IDefinitionRepository
    {
        private readonly ConcurrentDictionary<string, string> definitions = new();

        public async Task Create(string name, string definition)
        {
            definitions.AddOrUpdate(name, x => definition, (x, y) => definition);
        }
        public async Task<string?> Get(string name)
        {
            if (definitions.TryGetValue(name, out var definition))
            {
                return definition;
            }
            return null;
        }
        public async Task<int> Count()
        {
            return definitions.Count;
        }
    }
}

Ось такий вигляд має мати наш клас Program:

using GrpcExample.Interfaces;
using GrpcExample.Repositories.Interfaces;
using GrpcExample.Repository;
using GrpcExample.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddGrpc(); 	// тут ми реєструємо gRPC сервіс
builder.Services.AddSingleton<IDefinitionRepository, DefinitionRepository>();
builder.Services.AddSingleton<IDefinitionService, DefinitionService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGrpcService<DefinitionService>();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
app.Run();

А тепер створимо клієнт для нашого gRPC-сервісу. Клієнтом буде виступати вебзастосунок з оригінальним іменем WebApi, який буде приймати HTTP-запити та звертається до gRPC-сервісу для виконання процедур.

Єдина відмінність у .proto-файлі – це namespace для генерації клієнтського коду. Але можна було б використовувати і спільний файл. Головне: домовитись у команді, де саме його зберігати і як зручніше додавати до проєкту.

syntax = "proto3";
option csharp_namespace = "WebApi";
package definition;
service Definition {
  rpc AddDefinition (AddDefinitionRequest) returns (AddDefinitionResponse);
  rpc GetDefinition (GetDefinitionRequest) returns (GetDefinitionResponse);
}
message AddDefinitionRequest {
  string word = 1;
  string definition = 2;
}
message AddDefinitionResponse {
  string message = 1;
  int32 dictionarySize = 2;
}
message GetDefinitionRequest {
  string word = 1;
}
message GetDefinitionResponse {
  string definition = 1;
  bool found = 2;
}

Файли Protobuf у проєкті реєструються окремо, не як стандартні файли. Для цього вказується використання певного Protobuf і роль сервісу в контакті (у такому випадку роль буде клієнтською).

Залишається тільки вказати в серверному проєкті використання такого ж .proto-файл вже із роллю сервера. Саме за допомогою такого синтаксису і є можливість винести .proto-файл в окрему від проєкту директорію.

Для простоти прикладу продовжимо з копіями цього файлу для кожного застосунку:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework> 
  </PropertyGroup>
  <ItemGroup>
    <Protobuf Include="Protos\Definition.proto" GrpcServices="Client" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
    <PackageReference Include="Grpc.Net.ClientFactory" Version="2.59.0" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
  </ItemGroup>
</Project>

Для більш ефективного повторного використання з’єднання також слід ініціалізувати фабрику gRPC-клієнтів. Для цього треба застосувати AddGrpcClient та визначити конкретний сервіс, для якого потрібен клієнт. Після цього слід вказати адресу, на якій знаходиться сервіс.


public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    });
    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApi", Version = "v1" });
    });
}

Застосування створеного gRPC-клієнта доволі просте. Його можна будь-коли інтегрувати у потрібний об’єкт за допомогою Dependency Injection.

У прикладі створено 2 ендпоінти під обидва gRPC-методи: AddDefinition, GetDefinition. Це функції контролера, які приймають HTTP-запити та перенаправляють їх на обробку до сервісу gRPC.

using Microsoft.AspNetCore.Mvc;
using WebApi;
namespace WebApiClient.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class DefinitionController : ControllerBase
    {
        private readonly Definition.DefinitionClient _client;
        public DefinitionController(Definition.DefinitionClient client)
        {
            _client = client;
        }
       [HttpPost]
        public async Task<IActionResult> AddDefinition([FromBody] AddDefinitionRequest request)
        {
            var result = await _client.AddDefinitionAsync(request);
            return Ok(new { Message = result.Message, DictionarySize = result.DictionarySize });
        }
        [HttpGet]
        public async Task<IActionResult> GetDefinition([FromQuery] GetDefinitionRequest request)
        {
            var result = await _client.GetDefinitionAsync(request);
            return Ok(new { Definition = result.Definition, Found = result.Found });
        }
    }
}

Наступним кроком треба активувати gRPC-сервіс, перевірити його працездатність і запустити клієнт.

Надсилання запиту можна трохи спростити за допомогою документації Swagger. Вказуємо необхідні параметри й дивимось на відповідь. Як бачимо, визначення для нашого слова додане, і наш словник тепер має 1 запис.

У логах можемо переконатися, що запит був оброблений нашим gRPC-сервісом.

Статус-коди gRPC

Корисно також пам’ятати, що gRPC має свою систему кодів статусу. gRPC-помилки позначаються кодами, які визначені у класі Grpc.Core.StatusCode.

Вони включають такі значення:

OK (0): Успішне виконання запиту.

CANCELLED (1): Запит скасовано клієнтом.

UNKNOWN (2): Невідома помилка.

INVALID_ARGUMENT (3): Неправильні аргументи запиту.

DEADLINE_EXCEEDED (4): Час виконання запиту минув.

NOT_FOUND (5): Ресурс не знайдено.

ALREADY_EXISTS (6): Ресурс уже існує.

PERMISSION_DENIED (7): Недостатньо прав для виконання операції.

RESOURCE_EXHAUSTED (8): Вичерпано ресурси.

FAILED_PRECONDITION (9): Порушено передумову.

ABORTED (10): Операцію було перервано.

OUT_OF_RANGE (11): Значення знаходиться поза допустимим діапазоном.

UNIMPLEMENTED (12): Функціонал не реалізований.

INTERNAL (13): Внутрішня помилка сервера.

UNAVAILABLE (14): Сервер тимчасово недоступний.

DATA_LOSS (15): Втрата даних.

UNAUTHENTICATED (16): Відсутні облікові дані або недійсні.

Для їх використання достатньо «кинути» RpcException у коді gRPC-сервісу.

Наприклад, це може виглядати таким чином:

throw new RpcException(new Status(StatusCode.AlreadyExists, "The definition is already in the dictionary."));

Також є можливість створити «мапер» різних виключень (exceptions) по типу звичного для нас ExceptionHandlerMiddleware або ExceptionFilter за допомогою ExceptionInterceptor. Приклад такого коду можна знайти за цим посиланням.

Unit-тестування gRPC-сервісів

Unit-тестування для gRPC-клієнту просте, тому що ми можемо просто підмінити імплементацію сервісу по інтерфейсу. При тестуванні самого ж gRPC-сервісу єдиним нюансом буде необхідність передачі ServerCallContext до методу, що буде тестуватися.

Якщо цей об’єкт ніяк не використовується реалізацією методу сервісу (як у нашому випадку), ми можемо просто підмінити його (як, наприклад, mockContext у коді нижче). В іншому випадку можна використати TestServerCallContext для створення такого об’єкта (приклад mockContext2 є у коді нижче).

TestServerCallContext зазвичай використовується в тестах, коли вам необхідно змоделювати конкретні умови або сценарії, які можуть виникнути у реальному виклику віддаленого методу gRPC.

using Grpc.Core;
using Grpc.Core.Testing;
using GrpcExample;
using GrpcExample.Interfaces;
using GrpcExample.Repositories.Interfaces;
using GrpcExample.Services;
using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
namespace UnitTest
{
    public class DefinitionServiceTests
    {
        [Fact]
        public async Task AddDefinition_ReturnsSuccessMessage()
        {
            // Arrange
            var loggerMock = Substitute.For<ILogger<DefinitionService>>();
            var definitionRepositoryMock = Substitute.For<IDefinitionRepository>();
            var service = new DefinitionService(loggerMock, definitionRepositoryMock);
            var world = "TestWord";
            var definition = "TestDefinition";
            var request = new GetDefinitionRequest { Word = world };
           var mockContext = Substitute.For<ServerCallContext>();
	var mockContext2 = TestServerCallContext.Create( 
       		method:nameof(IDefinitionService.GetDefinition)
                         , host: "localhost"
                         , deadline:DateTime.Now.AddMinutes(30)
                         , requestHeaders: new Metadata()
                         , cancellationToken:CancellationToken.None
                         , peer: "10.0.0.25:5001"
                         , authContext: null
                         , contextPropagationToken: null
                         , writeHeadersFunc: (metadata) => Task.CompletedTask
                         , writeOptionsGetter: () => new WriteOptions()
                         , writeOptionsSetter: (writeOptions) => { }
                         );
            definitionRepositoryMock.Get(world)
                .Returns(definition);
            // Act
            var result = await service.GetDefinition(request, mockContext);
            // Assert
            definitionRepositoryMock.Received(1).Get(world);
            Assert.NotNull(result);
            Assert.Equal(definition, result.Definition);
            Assert.True(result.Found);
        }
    }
}

Перевірка автентичності токена авторизації

Наступна потенційна проблема – необхідність автентифікації й авторизації для юзерів.

Для автентифікації gRPC-клієнта можна використовувати різні підходи, навіть звичні для всіх JWT-токени. Для цього можна вказати значення потрібного хедера через клас Metadata, значення якого будуть передаватися у вигляді HTTP-заголовків (хедерів).

Через те, що у нас є можливість налаштувати HttpClientHandler і для клієнта, і для сервера, ми можемо реалізувати автентифікацію зокрема і через клієнтські сертифікати.

Перевірка автентичності сертифікатів клієнта

Клієнт може також надати сертифікат клієнта для автентифікації. Перевірка дійсності сертифіката відбувається на рівні TLS задовго до його потрапляння в ASP.NET Core.

Коли запит потрапляє в ASP.NET Core, пакет для роботи із сертифікатами клієнта дозволяє розглядати дані сертифікату через клас ClaimsPrincipal. Приклад цього механізму ви можете оцінити за посиланням.

Авторизація запитів gRPC


Сервіси gRPC підтримують атрибут Authorize.

[Authorize]
public class DefinitionService : Definition.DefinitionBase
{
}

Можна використовувати аргументи конструктора і властивості атрибута Authorize, щоб обмежити доступ лише користувачами, які відповідають певним політикам авторизації.

Наприклад, за наявності політики авторизації з ім’ям MyAuthorizationPolicy використовуйте наступний код. Завдяки цьому доступ до служби зможуть отримати тільки ті користувачі, які відповідають цій політиці.

До окремих методів служби також можна застосовувати атрибут Authorize із додатковими вимогами.

[Authorize]
public class DefinitionService : Definition.DefinitionBase
{
    [Authorize("Administrators")]
    public async override Task<AddDefinitionResponse> AddDefinition(AddDefinitionRequest request, ServerCallContext context)
    {
                // ...something only Administrators can do
    }
}

Працювати із gRPC досить просто. Саме тому багато проєктів використовують цей фреймворк. Однак спільнота його фанатів досі поступається за розміром ком’юніті REST API. І це не дивно, адже для відповіді кінцевим користувачам нам все ще набагато зручніше використовувати REST-підхід, аніж налаштовувати проксі, як це описано вище.

Враховуючи зростання популярності gRPC, ком’юніті буде активно розширюватися, і ця технологія має всі шанси надалі стати корисною у багатьох випадках.

Хоча розглянутий приклад був простим, у складних системах gRPC може значно підвищити продуктивність спілкування між сервісами.

До того ж ми можемо обирати підхід, при якому використовуємо одну технологію для комунікації між сервісами, а іншу – для комунікації з кінцевим користувачем. Навіть якщо при цьому треба буде дублювати деякі ендпоінти, така гібридна схема може бути ефективнішою реалізацією.

Підписуйтеся на ProIT у Telegram, щоб не пропустити жодної публікації!

Приєднатися до company logo
Продовжуючи, ти погоджуєшся з умовами Публічної оферти та Політикою конфіденційності.