Связь между C# и C#: REST, gRPC и все, что между

Существует много способов связи между клиентом C# и сервером C#. Некоторые из них надежные, другие нет. Некоторые очень быстрые, другие нет. Важно знать различные варианты, чтобы вы могли решить, что лучше для вас. В этой статье будут рассмотрены самые популярные на сегодняшний день технологии и почему они так широко используются. Мы поговорим о REST, gRPC и всем, что между ними.

Оптимальный сценарий

Давайте рассмотрим, как мы хотели бы, чтобы наше клиент-серверное общение выглядело в реальном мире. Я представляю что-то вроде этого:

// on client side public void Foo() {     var server = new MyServer(new Uri("https://www.myserver.com/");)     int sum = server.Calculator.SumNumbers(12,13);  }

// on server side class CalculatorController : Controller{     public int SumNumbers(int a, int b)     {         return a + b;     } }

Я хотел бы иметь полную поддержку Intellisense. Когда я нажимаю server и . хочу, чтобы Visual Studio показывал все контроллеры. И когда я нажимаю CalculatorController и ., я хочу видеть все методы действия. Я также хочу высочайшую производительность, очень малую нагрузку на сеть и двунаправленную передачу данных. И мне нужна надежная система, которая отлично справляется с управлением версиями, чтобы можно было без проблем развертывать новые версии клиентов и новые версии серверов.

Это слишком много?

Обратите внимание, что я говорю здесь об API без сохранения состояния. Это эквивалентно проекту C #, где есть только два типа классов:

  • Статические классы только со статическими методами.
  • Классы POCO, в которых нет ничего, кроме полей и свойств, тип которых является примитивным или другим классом POCO.

Наличие состояния в API вводит сложность, и это является корнем всего зла. Итак, ради этой статьи, давайте сделаем вещи красивыми и не сохраняющими состояния.

Традиционный REST подход

REST API появился в начале 2000-х и покорил интернет. Сейчас это, безусловно, самый популярный способ создания веб-сервисов.

REST определяет фиксированный набор операций GET, POST, PUT и DELETE для запросов от клиента к серверу. На каждый запрос получаем ответ, содержащий полезную нагрузку(обычно JSON). Запросы включают параметры в сам запрос или как полезную нагрузку (обычно JSON), когда это POST или PUT запрос.

Существует стандарт RESTful API, который определяет следующие правила (который вам на самом деле не нужен):

  • GET используется для получения ресурса
  • PUT используется для изменения состояния ресурса
  • POST используется для создания ресурса
  • DELETE используется для удаления ресурса

Если вы не знакомы с REST до сих пор, приведенное выше объяснение, вероятно, не поможет, так что вот пример. В .NET есть встроенная поддержка REST. Фактически ASP.NET Web API по умолчанию является веб-службой REST. Вот как выглядит типичный клиент и сервер ASP.NET:

На сервере:

[Route("People")] public class PeopleController : Controller {     [HttpGet]     public Person GetPersonById(int id)     {         Person person = _db.GetPerson(id);         return person;//Automatically serialized to JSON     } } 

На клиенте:

var client = new HttpClient(); string resultJson = await client.GetStringAsync("https://www.myserver.com/People/GetPersonById?id=123"); Person person = JsonConvert.DeserializeObject<Person>(resultJson);

REST чертовски удобен, но не подходит к оптимальному сценарию. Итак, давайте посмотрим, сможем ли мы сделать это лучше.

ReFit

ReFit не является альтернативой REST. Вместо этого он построен поверх REST и позволяет нам вызывать конечные точки сервера (endpoints ), как если бы они были простым методом. Это достигается путем разделения интерфейса между клиентом и сервером. На стороне сервера ваш контроллер будет реализовывать интерфейс:

public interface IMyEmployeeApi {     [Get("/employee/{id}")]     Task<Employee> GetEmployee(string id); }

Затем на стороне клиента вам нужно будет включить тот же интерфейс и использовать следующий код:

 var api = RestService.For<IMyEmployeeApi>("https://www.myserver.com"); var employee = await api.GetEmployee("abc"); 

Это так просто. Нет необходимости запускать сложную автоматизацию или использовать сторонние инструменты, кроме пары пакетов NuGet.

Это становится намного ближе к оптимальному сценарию. Теперь у нас есть IntelliSense и надежный контракт между клиентом и сервером. Но есть и другой вариант, который даже лучше в некоторых отношениях.

Swagger

Как и ReFit, Swagger также построен поверх REST. OpenAPI, или Swagger , является спецификацией REST API. Он описывает REST веб-сервис простыми файлами JSON. Эти файлы являются схемой API веб-службы. Они включают:

  • Все пути (URL) в API.
  • Ожидаемые операции (GET, POST,…) для каждого пути. Каждый путь может обрабатывать разные операции. Например, один и тот же путь mystore.com/Product может принять операцию POST, которая добавляет продукт, и операцию GET, которая возвращает продукт.
  • Ожидаемые параметры для каждого пути и операции.
  • Ожидаемые ответы для каждого пути.
  • Типы каждого параметра и объекта ответа.

Этот JSON-файл по сути является контрактом между клиентами и сервером. Вот пример файла swagger, описывающего веб-сервис под названием Swagger Petstore (для ясности я удалил некоторые части):

JSON схема

{     "swagger":"2.0",    "info":{        "version":"1.0.0",       "title":"Swagger Petstore",       "description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification",    },    "host":"petstore.swagger.io",    "basePath":"/api",    "schemes":[        "http"    ],    "consumes":[        "application/json"    ],    "produces":[        "application/json"    ],    "paths":{        "/pets":{           "get":{              "description":"Returns all pets from the system that the user has access to",             "operationId":"findPets",             "produces":[                 "application/json",                "application/xml",             ],             "parameters":[                 {                    "name":"tags",                   "in":"query",                   "description":"tags to filter by",                   "required":false,                   "type":"array",                   "items":{                       "type":"string"                   },                   "collectionFormat":"csv"                },                {                    "name":"limit",                   "in":"query",                   "description":"maximum number of results to return",                   "required":false,                   "type":"integer",                   "format":"int32"                }             ],             "responses":{                 "200":{                    "description":"pet response",                   "schema":{                       "type":"array",                      "items":{                          "$ref":"#/definitions/Pet"                      }                   }                }, ...  

Давайте рассмотрим последствия этого. С помощью JSON-файла, подобного приведенному выше, вы потенциально можете создать клиент C# с полным IntelliSense. В конце концов, вы знаете все пути, операции, какие параметры они ожидают, какие типы параметров, каковы ответы.

Есть несколько инструментов, которые делают именно это. На стороне сервера вы можете использовать Swashbuckle.AspNetCore для добавления Swagger в ваш ASP.NET и создания указанных файлов JSON. Для стороны клиента, вы можете использовать swagger-codegen и AutoRest чтобы обработать эти файлы в формате JSON и сгенерировать клиента. Давайте посмотрим пример того, как это сделать:

Добавление Swagger на ваш сервер ASP.NET

Начните с добавления пакета NuGet Swashbuckle.AspNetCore. В ConfigureServices, зарегистрируйте генератор Swagger:

services.AddSwaggerGen(options =>  	options.SwaggerDoc("v1", new OpenApiInfo {Title = "My Web API", Version = "v1"}));

В файле Startup.cs в методе Configure добавьте:

app.UseSwagger(); 

Наконец, методы действия(actions) внутри контроллера должны быть помечены [HttpXXX] и [FromXXX] атрибутами:

[HttpPost] public async Task AddEmployee([FromBody]Employee employee) {     //... }   [HttpGet] public async Task<Employee> Employee([FromQuery]string id) {     //... }

Это так просто для серверной части. При запуске проекта будет сгенерирован файл swagger.json, который вы можете использовать для генерации клиента.

Генерация клиента из Swagger с помощью AutoRest

Для того, чтобы начать использовать AutoRest , установить его с npm: npm install -g autorest. После установки вам потребуется использовать интерфейс командной строки AutoRest для создания клиента C# из файла swagger.json. Вот пример:

autorest --input-file="./swagger.json" --output-folder="GeneratedClient" --namespace="MyClient" --override-client-name="MyClient" --csharp

Это создаст папку GeneratedClient с сгенерированными файлами C#. Обратите внимание, что пространство имен и имя клиента переопределены. Добавьте эту папку в ваш клиентский проект в Visual Studio как показано ниже.

Вам нужно будет установить Microsoft.Rest.ClientRuntime пакет NuGet, потому что сгенерированный код зависит от него. После установки вы можете использовать API, как обычный C# класс:

var client = new MyClient(); Employee employee = client.Employee(id: "abc"); 

Есть некоторые тонкости, о которых вы можете прочитать в документации AutoRest. И вам нужно будет автоматизировать этот процесс, поэтому я предлагаю прочитать руководство Патрика Свенссона для получения некоторых полезных советов, а также эту статью Питера Яусовца.

Моя проблема с Swagger заключается в том, что файл JSON создается во время выполнения, поэтому это немного затрудняет автоматизацию процесса CI/CD.

Традиционный REST vs Swagger vs ReFit

Вот несколько моментов, которые следует учитывать при выборе.

  • Если у вас очень простой частный API REST, возможно, не стоит беспокоиться о генерации клиентов и общих интерфейсах. Небольшая задача не оправдывает дополнительных усилий.
  • Swagger поддерживает множество языков, а ReFit поддерживает только .NET. Swagger также является основой для многих инструментов, тестов, средств автоматизации и инструментов пользовательского интерфейса. Вероятно, это будет лучший выбор, если вы создаете большой публичный API.
  • Swagger намного сложнее, чем ReFit. С ReFit, это просто вопрос добавления единого интерфейса как в ваш сервер, так и в клиентский проект. С другой стороны, с ReFit вам придется создавать новые интерфейсы для каждого контроллера, тогда как Swagger позаботится об этом автоматически.

Но прежде чем что-то решать, проверьте 4-й вариант, который не имеет ничего общего с REST.

gRPC

gRPC (gRPC — удаленный вызов процедур) — это система удаленного вызова процедур с открытым исходным кодом, разработанная Google. Это немного похоже на REST в том смысле, что он обеспечивает способ отправки запросов от клиента к серверу. Но это во многом отличается, вот сходства и различия:

  • Как и REST, gRPC не зависит от языка. Есть инструменты для всех популярных языков, включая C#.
  • gRPC является базируется на контракте и использует .proto файлы для определения контракта. Это несколько похоже на Swagger swagger.json и общий интерфейс ReFit. Клиент любого языка программирования может быть сгенерирован из этих файлов.
  • gRPC использует бинарную сериализацию Protocol Buffer (Protobuf). Это отличается от REST, который обычно сериализуется в JSON или XML. Бинарная сериализация меньше и, следовательно, быстрее.
  • gRPC создан для создания длительных соединений по протоколу HTTP/2. Этот протокол проще и более компактный. Принимая во внимание, что REST использует протокол HTTP 1.x (обычно HTTP 1.1).
  • HTTP 1.1 требует TCP-квитирования (рукопожатие) для каждого запроса, тогда как HTTP/2 поддерживает соединение открытым.
  • HTTP/2 соединение использует мультиплексированные потоки. Это означает, что одно TCP-соединение может поддерживать множество потоков. Эти потоки могут выполняться параллельно, не ожидая друг друга, как в HTTP 1.1.
  • gRPC позволяет двунаправленную потоковую передачу.

Есть два способа использования gRPC. Для .NET Core 3.0 есть полностью управляемая библиотека gRPC for .NET. Также вы можете использовать gRPC C#. Это не означает, что gRPC for .NET заменяет gRPC C#. Давайте посмотрим пример с более новым gRPC for .NET.

Серверная часть gRPC for .NET

Это не руководство, а скорее общее представление о том, чего ожидать. Вот как будет выглядеть пример контроллера в gRPC:

 public class GreeterService : Greeter.GreeterBase {     public override Task<HelloReply> SayHello(HelloRequest request,         ServerCallContext context)     {         _logger.LogInformation("Saying hello to {Name}", request.Name);         return Task.FromResult(new HelloReply          {             Message = "Hello " + request.Name         });     } }

Вам нужно добавить следующее в Configure в файле Startup.cs:

 app.UseEndpoints(endpoints => {     endpoints.MapGrpcService<GreeterService>(); }); 

API описан в .proto файле, который является частью проекта:

syntax = "proto3";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

Этот .proto файл добавлен в файле .csproj:

<ItemGroup>   <Protobuf Include="Protos\greet.proto"/> </ItemGroup>

Клиентская часть gRPC for .NET

Клиент генерируется из .proto файлов. Сам код очень прост:

 var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new Greeter.GreeterClient(channel);   var response = await client.SayHello(     new HelloRequest { Name = "World" });   Console.WriteLine(response.Message); 

gRPC vs REST

GRPC звучит как приятная сделка. Это быстрее и проще под капотом. Так должны ли мы все перейти с REST на gRPC? Ответ в том, что это зависит. Вот некоторые соображения:

По моим впечатлениям, работа с gRPC и ASP.NET все еще невелика. Вам будет лучше со зрелой поддержкой REST. Что касается контрактного общения, это хорошо, за исключением того, что у вас есть похожие альтернативы в REST, о которых мы уже говорили: Swagger и ReFit.

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

Переход от REST к gRPC в большом приложении ASP.NET будет трудным. Однако если у вас есть архитектура на основе микросервисов, то этот переход станет намного легче выполнять постепенно.

Другие способы общения

Есть несколько других способов общения, о которых я вообще не упоминал, но стоит знать, что они существуют:

  • GraphQL — это язык запросов для API, разработанный Facebook. Это позволяет клиенту запрашивать именно те данные, которые ему нужны с сервера. Таким образом, вы можете создать только одну конечную точку на сервере, которая будет чрезвычайно гибкой и будет возвращать только те данные, которые нужны клиенту. GraphQL становится очень популярным в последние годы.
  • TcpClient и TcpListenerSystem.Net.Sockets) обеспечивают низкоуровневое соединение по TCP. По сути, вы собираетесь установить соединение и передать байтовые массивы. Это не идеально для большого приложения, где вы можете использовать контроллеры и методы действия ASP.NET для наведения порядка в большом API.
  • UdpClient предоставляет способ связи по протоколу UDP. TCP устанавливает соединение и затем отправляет данные, тогда как UDP просто отправляет данные. TCP гарантирует, что в данных нет ошибок, а UDP — нет. Протокол UDP более эффективен для быстрой передачи данных, так как вы недостаточно заботитесь о том, чтобы они были надежными и безошибочными. Вот некоторые примеры: потоковое видео, прямые трансляции и передача голоса по IP (VoIP).
  • WCF — это старая технология, которая в основном использует основанную на SOAP связь между процессами. Это огромный фреймворк, в который я не буду вдаваться, скажу лишь, что он потерял свою популярность для полезных нагрузок REST и JSON.

Вот и все. Надеюсь статья была интересной! Всем хорошего кода!

ссылка на оригинал статьи https://habr.com/ru/post/488102/

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *