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. Учебник и образец, найденные там, довольно просты, но я хочу выделить пару красных флажков.
- Примеры на MSDN почти всегда крайне упрощены по форме. Это означает, что они не обязательно будут «лучшими» для готового кода.
- В примере по вышеупомянутой ссылке предполагается, что вы полностью и полностью продвигаетесь вперед с .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 г.