この記事は、C# その2 Advent Calendar 2019 の二日目の記事です
はじめに
ASP.NET Core 3.0 で gRPC がサポートされた。 一方で WCF は .NET Core に正式には移植されず、コミュニティによる開発にシフト。 業務では WCF をバリバリ使っているけど、gRPC への移行を検討しなければいけない時期になってきた。 2020 年には .NET 5 も来るし。
コードファーストで開発したい
Visual Studio で proto ファイルを編集したら保存時に C# のコードが生成されるようになったとはいえ、できれば proto ファイル書きたくない。
WCF では C# でデータコントラクトとサービスコントラクトのインタフェースを定義したら、 それをクライアントとサーバーの両方で利用できていた。 gRPC でも同じような体験が理想だ。
protobuf-net.Grpc
WCF から gRPC への移行方法を調べていたら、protobuf-net.Grpc というパッケージに出会った。
README には次のように書いてある。
protobuf-net.Grpc adds code-first support for services over gRPC using either the native Grpc.Core API, or the fully-managed Grpc.Net.Client / Grpc.AspNetCore.Server API.
WCF のときみたいに、コードファーストで gRPC サービスを開発できそうなシロモノに見える。
コードファースト ASP.NET Core gRPC をやってみる
.NET Core ライブラリ作成
クライアントとサーバーで共通のインタフェースを使うために、 クライアントとサーバーの両方で参照する .NET Core ライブラリプロジェクトを作成する。 名前は『HelloGrpc.Shared』。
NuGet パッケージ追加
プロジェクトには次のパッケージを追加しておく。
- System.ServiceModel.Primitive
- WCF で使う ServiceContractAttribute と OperationContractAttribute が定義されている。
データコントラクトとサービスコントラクトを定義
この辺とても WCF っぽい。 引数が 1 つだけなのは protobuf-net.Grpc の制約。 gRPC でも 1 つなのは同じだし。 あと、DataMemberAttribute で Order を指定するのも必須。 .proto ファイルでもフィールドにタグ値を指定するけど、その代わりになる。
using System.Runtime.Serialization; using System.ServiceModel; using System.Threading.Tasks; namespace HelloGrpc.Shared { [ServiceContract] public interface IGreetingService { [OperationContract] Task<HelloReply> HelloAsync(HelloRequest request); } [DataContract] public class HelloRequest { [DataMember(Order = 1)] public string Name { get; set; } } [DataContract] public class HelloReply { [DataMember(Order = 1)] public string Message { get; set; } } }
gRPC サービス作成
Visual Studio から gRPC サービスのテンプレートを選択してプロジェクトを作成。 名前は『HelloGrpc.Server』。
NuGet パッケージ追加
ASP.NET Core で gRPC サービスを実装するのに必要なパッケージはあらかた参照してあるけど、 下記のパッケージも参照に追加する。
- protobuf-net.Grpc.AspNetCore
- コードファーストで gRPC サービスを実装するための機能を提供。
- protobuf-net.Grpc に依存している。
gRPC サービス実装
services.AddCodeFirstGrpc()
でコードファーストを有効にし、
endpoints.MapGrpcService<T>()
で実際にコードで定義したサービスを登録するだけ。
using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using ProtoBuf.Grpc.Server; namespace HelloGrpc.Server { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); } public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddCodeFirstGrpc(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGrpcService<GreetingService>(); }); } } public class GreetingService : Shared.IGreetingService { public Task<Shared.HelloReply> HelloAsync(Shared.HelloRequest request) { return Task.FromResult(new Shared.HelloReply { Message = $"Hello {request.Name}" }); } } }
.NET Core コンソールアプリケーション作成
gRPC サービスを呼び出すクライアントは、.NET Core コンソールアプリケーションで作成する。 プロジェクト名は『HelloGrpc.Client』で。
NuGet パッケージ追加
HelloGrpc.Client プロジェクトに次のパッケージを追加。
- Grpc.Net.Client
- .NET 用 gRPC クライアント
- protobut-net.Grpc
- C# コードで定義した gRPC サービスのインタフェースをもとに動的にクライアントを生成してくれるパッケージ
gRPC クライアントを実装
protobut-net.Grpc が提供する GrpcClientFactory クラスが、インタフェースから動的に gRPC クライアントを生成する拡張メソッドを提供しているので、それを使う。
using System; using System.Threading.Tasks; using Grpc.Net.Client; using HelloGrpc.Shared; using ProtoBuf.Grpc.Client; namespace HelloGrpc.Client { class Program { static async Task Main(string[] args) { var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = channel.CreateGrpcService<IGreetingService>(); // インタフェースから動的に gRPC クライアントを生成 var reply = await client.HelloAsync(new HelloRequest { Name = "Kubo" }); Console.WriteLine(reply.Message); Console.ReadLine(); } } }
実行
コードファーストで作成した gRPC サービスをホストし、クライアントから呼び出すことに成功。
まとめ
WCF から gRPC に移行する手段として、proto ファイルを書くのではなく、protobuf-net.Grpc を使いコードファーストで開発するのは良い線いってそうだ。さらに検証が必要だけど、希望が見えてきた。
なお、コードファーストで開発する別の選択肢としては MagicOnion もある。
既に大量の WCF 資産があり、MagicOnion だとそれらに結構な修正が必要そうだったので、 今回は選ばなかった。 ベンチマークを取ったわけではないけど、シリアライザに MessagePack を使っているので、 protobuf-net.Grpc を使った場合よりも速くなりそうな期待がある。 新規にコードファーストで開発するなら、MagicOnion という選択肢は有力だと思う。