Microsoft недавно выпустила .NET Core 3.0 и подготовила почву для выхода из WCF. .NET Core больше не поддерживает режим полной платформы, что фактически прекращает поддержку WCF в том виде, в каком мы его знаем. Наши варианты миграции различаются. Сегодня давайте обсудим один из таких подходов, который, по моему мнению, является подходом с низким уровнем воздействия. Давайте рассмотрим миграцию WCF на gRPC с использованием .NET Core с сохранением обратной совместимости.

Давайте разветвим код из Использование WCF с .NET Core для сегодняшнего обсуждения. Весь код для сегодняшнего поста находится в ветке gRPC. Некоторыми концепциями сегодня поделился со мной мой брат Джейсон Сили. Несколько месяцев я пытался убедить его написать этот пост. Спасибо ему за подход к переходу с WCF на gRPC, о котором я расскажу.

Что такое gRPC?

Вместо того, чтобы делать предположение, что вы знаете, что такое gRPC, возможно, уместно небольшое введение? gRPC — это система удаленного вызова процедур (RPC) с открытым исходным кодом. gRPC был разработан Google и работает на транспортном протоколе HTTP/2. На момент написания этого поста gRPC официально поддерживается 12 языками (веб частично включен в этот список).

В конечном счете, цель gRPC — предоставить инфраструктуру RPC, которая может работать где угодно. Это обеспечивает настоящую межплатформенную коммуникацию в действительно богатом формате. Я приглашаю вас перейти на официальный сайт, чтобы узнать больше.

Почему я хочу перенести WCF на gRPC?

Помимо (полу?) очевидного факта, что WCF больше не является первоклассным гражданином в среде .NET, существует множество причин, по которым вам может потребоваться миграция. Как насчет того, чтобы мы позволили официальной странице начать разговор:

Основные сценарии использования:

  • Распределенные системы с низкой задержкой, высокой масштабируемостью.
  • Разработка мобильных клиентов, которые обмениваются данными с облачным сервером.
  • Разработка нового протокола, который должен быть точным, эффективным и независимым от языка.
  • Многоуровневый дизайн для расширения, например. аутентификация, балансировка нагрузки, ведение журнала и мониторинг и т. д.

Живя в нашем подключенном современном мире, кажется, что вы многое выиграете от действительно кросс-платформенной системы. Да, можно утверждать, что многое из того же можно сделать с помощью REST, но мы сравниваем яблоки и апельсины.

Другая причина, по которой вы можете захотеть мигрировать, — это воспользоваться более новыми версиями фреймворка. Например, Entity Framework Core v3 поддерживается только на netstandard2.1 или netcoreapp3.0. Это полностью исключает его использование с WCF.

Представление

Производительность — еще одна причина, по которой вы можете захотеть переключиться. Марк Рендл написал два поста, в которых сравнивал их. Во втором посте он пытается сделать действительно сравнение яблок с яблоками, которое ставит их близко, но все же выигрывает gRPC. Я хочу подчеркнуть, что вы *также* получаете кросс-платформенную совместимость, используя gRPC, где WCF не предлагает этого с привязкой NetTCP (используется во втором тесте). Один читатель все еще предполагает, что WCF превзойдет gRPC, если вы удалите аутентификацию из WCF. Я оставлю это на ваше усмотрение.

Давайте начнем!

Когда я начал изучать .NET Core 3.0 и то, как я могу настроить приложение gRPC, я, естественно, начал с собственного введения MSDN. Учебник и образец, найденные там, довольно просты, но я хочу выделить пару красных флажков.

  1. Примеры на MSDN почти всегда крайне упрощены по форме. Это означает, что они не обязательно будут «лучшими» для готового кода.
  2. В примере по вышеупомянутой ссылке предполагается, что вы полностью и полностью продвигаетесь вперед с .NET Core 3.0.

Тем не менее, если вы хотите использовать изящный подход к миграции на gRPC, вы не можете зафиксировать 100% на netcoreapp3.0 (или, соответственно, netstandard2.1). К счастью, нам не нужно создавать зависимость от этих последних конструкций фреймворка для выполнения нашей задачи.

Однако для начала вам нужно убедиться, что у вас либо установлена ​​Visual Studio 2019.3 (версия 16.3), либо вы знакомы с VSCode и установлен SDK netcore3.0. Использование шаблонов, предоставляемых SDK, поможет вам быстрее, даже если вы в конечном итоге измените что-то для поддержки более ранних версий фреймворка.

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

Создание хоста gRPC (простой способ)

Я начал с того, что следовал учебнику на MSDN. Я быстро решил, что меня это не устраивает. В их примере вы копируете/вставляете прото-файл с вашего сервера на ваш клиент. Они быстро рассинхронизируются, если вы будете контролировать их обоих. Конечно, есть способы синхронизировать их, но я предположил, что должен быть способ поделиться ими.

Обсуждая эту идею с братом, он предложил мне сделать отдельную сборку и поместить туда прото-файлы, но независимо от сервиса. Теперь и клиент, и сервер могут использовать одни и те же сгенерированные DTO и т. д. Я рекомендую вам все же просмотреть их руководство, чтобы увидеть, как все это выстраивается, и тогда вы сможете лучше оценить подход, который я избрал. У нас есть несколько шагов, чтобы добраться туда.

1) Создайте общую «прото» библиотеку

Начнем с создания библиотеки netstandard2.0. Я назвал свой DotNetCoreWCF.Proto, чтобы придерживаться соглашения об именах из предыдущего сообщения в блоге. Чтобы это работало на нашем хосте WCF, это не может быть netstandard2.1. Затем добавьте ссылки на Google.Protobuf, Grpc и Grpc.Tools. Ваш csproj должен выглядеть примерно так:

<ItemGroup>
    <PackageReference Include="Google.Protobuf" Version="3.10.0" />
    <PackageReference Include="Grpc" Version="2.24.0" />
    <PackageReference Include="Grpc.Tools" Version="2.24.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
</ItemGroup>

Затем добавьте папку с именем Protos. Если ваш репозиторий используется совместно с приложениями/библиотеками на других языках, вы можете добавить папку, а затем связать свои прототипы из другой папки в вашем репозитории. (Добавить существующий элемент -> Добавить как ссылку) В нашем случае давайте просто создадим файлы proto здесь напрямую. Я скопировал greet.proto из примера MSDN, а затем создал свой собственный с именем employee.proto. В этой библиотеке нет шаблона для proto, поэтому просто добавьте его в виде текстового файла. Теперь настройте свой csproj, как показано ниже:

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

Отличие, которое вы заметите здесь от автоматически сгенерированных, заключается в том, что он исключает атрибут GrpcServices. После того, как вы сопоставили его здесь как Protobuf, в диалоговом окне свойств файлов proto появится атрибут gRPC Stub Classes. При установке значения Сервер и клиент этот атрибут будет пропущен. Установка любого другого значения также установит его в csproj. Мы хотим, чтобы он генерировал оба. Смотрите employees.proto здесь.

Одна важная вещь, на которую следует обратить внимание в файле proto, — это строка option csharp_namespace = "DotNetCoreWCF.GrpcSample.Services";. Это пространство имен, в котором наши прототипы будут генерировать клиент, сервер и DTO. В идеале вы будете соответствующим образом распределять имена для всех своих различных сервисов и прототипов, но я просто делаю это предельно простым.

Один вопрос, который у меня возник, на который Джейсон помог ответить, заключался в том, как сгенерировать обнуляемое целое число ( int?). Он показал мне включить import "google/protobuf/wrappers.proto";, а затем использовать оболочку/контейнер для Int32Value вместо простого int32. Другим интересным вопросом было то, как представлять массив. См. ключевое слово repeated. Мне пришлось добавить в закладки эту страницу на grpc.io для быстрой справки.

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

2) Адаптеры (преобразование домена в grpc и обратно)

Одна очень большая часть примера кода, которую я собираюсь сильно затушевать, — это адаптеры. Вы заметите библиотеку DotNetCoreWCF.Logic (см. здесь), которая используется серверами и клиентом как netstandard2.0. Внутри я использую AutoMapper, но намеренно предоставляю каждое преобразование независимо, например IEmployeeAdapter. Это, конечно, для того, чтобы позже я мог легко передумать, *как* выполняется преобразование.

Если вы решите использовать AutoMapper, как это сделал я, одна ошибка, на которую я хочу обратить внимание, — это взять IEnumerable, Array, List или что-то подобное и преобразовать его в тип repeats в protobuf. Посмотрите EmployeeProfile.cs. Обратите особое внимание на opt.UseDestinationValue(). Погуглил, нашел, добавил. Оно работает.

3) Обработчики (разрешить повторное использование между WCF и gRPC)

Как и в случае с моими адаптерами, я хотел убедиться, что уровень gRPC полностью изолирован/отделен от бизнес-логики. Я пошел дальше и создал несколько обработчиков в новой сервисной библиотеке под названием DotNetCoreWCF.Service.Core (см. здесь). Каждый метод, предоставляемый службами (Get, Delete, Update), теперь представлен одним классом и интерфейсами. Я также переместил IEmployeeStore сюда.

4) Создайте сервер gRPC

Вот тут-то и начинаются сложности. Прежде чем мы перейдем к серверу двойного назначения (WCF и gRPC), давайте создадим автономный узел gRPC. Идите вперед и создайте новую «службу gRPC» из шаблона в Visual Studio. Далее удалите папку Protos. Теперь пришло время сослаться на нашу общую библиотеку и подключить ее. Одна важная проблема, *вам нужно понять*, заключается в том, что служба gRPC будет зарегистрирована как одноэлементная. Вы слышали это. Если у вас есть какая-либо область действия, вам нужно убедиться, что вы обрабатываете ее надлежащим образом (т. е. дочернюю область в вызовах методов).

Хорошо, имея это в виду, давайте подключим нашу службу сотрудников. Давайте создадим новый класс и назовем его EmployeeServiceHost. Если вы посмотрите на GreeterService в качестве примера, вы увидите, что нам нужно наследовать его от нашего сгенерированного прото-сервера. В данном случае это будет EmployeeService.EmployeeServiceBase. Поскольку это будет синглтон, я думаю, нам придется сделать немного грязного кодирования, а? Введем IServiceProvider в конструктор. Нам нужно будет переопределить каждый из методов из нашей базы. Смотрите готовый файл здесь.

Перейдите в Startup.cs, а затем сопоставьте конечную точку endpoints.MapGrpcService();. Наш хозяин готов к работе. Полный код смотрите здесь.

5) Создайте хост двойного назначения

Наконец-то мы готовы разобраться, почему ты здесь, амирит? Давайте возьмем оригинальное консольное приложение DotNetCoreWCF.Host и переименуем его в DotNetCoreWCF.Service. Я переделал тонну этого проекта в процессе. Некоторые ключевые изменения:

  • Добавлена ​​поддержка IHostBuilder (общий узел) и настроены службы gRPC и WCF как реализации IHostedService.
  • Продолжал использовать Unity из-за его «простой» оболочки для WCF, подключенной к основному конвейеру на IHostBuilder.
  • Зарегистрировали обработчики и адаптеры для использования с gRPC и WCF, чтобы они использовали общий путь кода.
  • Исправлена ​​регистрация ILogger в Unity, чтобы он корректно поддерживал ILogger‹T›, добавлено небольшое базовое ведение журнала.

Суть этого приложения и то, как оно работает, обрабатывается через каждый IHostedService. Посмотрите на GrpcServiceHost и EmployeeServiceManager (WCF), чтобы увидеть, что я сделал.

Создайте клиент gRPC

Создайте новое консольное приложение (.NET Core), используя netcoreapp3.0. Добавьте ссылки на Google.Protobuf, Grpc.Net.Client, Grpc.Net.ClientFactory и Grpc.Tools. Далее, вы действительно захотите использовать свои собственные модели предметной области, не так ли? Я имею в виду... передача сгенерированных DTO может стать грязной, верно? Давайте добавим прокси-клиент? Создайте папку с именем Proxies, а затем создайте в ней класс с именем EmployeeClient.

В моем посте WCF мы создали IEmployeeService, который повторно использовался на разных уровнях. Мы собираемся использовать его здесь. Grpc довольно легко поддерживает асинхронность, давайте продолжим и расширим интерфейс до IEmployeeClientAsync, как показано ниже.

public interface IEmployeeClientAsync : IEmployeeService
{
 Task<EmployeeResponse> GetAsync(EmployeeRequest request);
 Task<DeleteEmployeeResponse> DeleteAsync(DeleteEmployeeRequest request);
 Task<Employee> UpdateAsync(Employee employee);
}

Мы создадим клиент, реализующий этот интерфейс, и назовем его EmployeeClient. Оставим все это нереализованным и вернемся к нему. Как насчет того, чтобы перейти к настройке внедрения зависимостей сейчас. (см. готовый файл здесь)

Конфигурация клиента

Если вы читали предыдущие посты, то, вероятно, заметили, что мне нравится хранить регистрацию/конфигурацию вне моей записи в приложении. Я предпочитаю иметь какой-то класс Конфигурация или набор классов. Давайте создадим здесь в клиенте папку Конфигурация, а затем добавим в нее статический класс EmployeeClientConfiguration. В контексте этой сегодняшней демонстрации я не хочу вдаваться в аспекты конфигурации безопасности gRPC. Это означает, что я собираюсь сделать что-то особенное, чтобы *удалить* безопасность TLS. Смотри файл здесь.

AppContext.SetSwitch важен только для этой демонстрации. Где-то в недрах поддержки gRPC/HttpClient Http2 он указывает, что мы собираемся использовать незашифрованный канал. Не делайте этого в реальной жизни, люди! Единственная причина, по которой эта строка кода вообще здесь, заключается в том, что когда я подключаю ее к хосту WCF, я не хочу тратить время на генерацию и связывание сертификата вручную.

Еще одна вещь, которую следует отметить, отличается от примера Microsoft тем, что я сделал эти клиенты инъекционными. Это делается с помощью пакета Grpc.Net.ClientFactory nuget, который не включен в их образец. Мы собираемся внедрить клиент gRPC в наш собственный EmployeeClient, который мы создали выше. Опять см. готовый файл.

Видеть плоды своего труда

Теперь у нас есть полнофункциональный узел службы, который может одновременно запускать и WCF, и gRPC. С небольшими изменениями мы можем заставить его работать и как службу Windows. Это здорово.

Я не включил скриншоты DotNetCoreWCF.Client и DotNetCoreWCF.FullClient, так как ничего в них не менял, но они все еще работают. Не стесняйтесь ссылаться на предыдущий пост, чтобы прочитать о них.

Делаем лучше

Есть несколько вещей, которые я упустил из этого приложения. Прежде всего, это решение полностью лишено модульного тестирования. Это довольно большое нет-нет. С другой стороны, я заставил все использовать интерфейсы сверху вниз, поэтому модульное тестирование этого приложения не составит труда.

Затем я исключил поддержку SSL на хосте gRPC. Это тоже нет-нет. Шаблон из Visual Studio основан на новом GenericHost из netcoreapp3.0/ netstandard2.1, который, в свою очередь, добавляет SSL-сертификат по умолчанию в Kestrel. Поскольку мы обошли это и добавили Grpc.Core.Server вручную, мы не смогли воспользоваться этим преимуществом. Вот ответ StackOverflow, в котором говорится о реализации этого.

Вывод

Сегодня мы рассмотрели существующее сервисное приложение WCF и добавили поддержку gRPC. Мы показали «изящный» путь миграции WCF на gRPC с использованием .NET Core с сохранением обратной совместимости.

gRPC кажется мне логичной заменой сервисов WCF, и лично я с нетерпением жду продолжения работы с ними.

Дополнительное чтение

Если вы убежденный поклонник WCF и хотите, чтобы он успешно перенесся в новый мир .NET, я предлагаю вам внести свой вклад в проект Core WCF. Согласно этому сообщению Скотта Хантера (Microsoft), похоже, что это, вероятно, единственный способ, которым вы собираетесь продолжать использовать его в долгосрочной перспективе на новых платформах.

Кредиты

Фото Gilles Rolland-Monnet на Unsplash

Первоначально опубликовано на https://www.seeleycoder.com 5 октября 2019 г.