本文へスキップ
WCF から gRPC・ASP.NET Core への移行ガイドのアイキャッチ画像
Architecture

WCF から gRPC・ASP.NET Core への移行ガイド

公開: 更新: 約7分で読めます

WCF(Windows Communication Foundation)は .NET Framework 専用のスタックであり、.NET Core 以降のランタイムには標準搭載されていません。.NET 10 への移行を進めるうえで、WCF で実装されたサービス群をどう扱うかは避けて通れない論点です。本ガイドでは、WCF 資産を移行する 3 つの選択肢(CoreWCF によるリフト&シフト、gRPC への移行、ASP.NET Core Web API への移行)を整理し、コントラクトの対応づけ、ストリーミング・認証・エラー処理の扱い、そして実際の進め方までを .NET 10 時点の情報にもとづいて解説します。

移行の 3 つの選択肢

WCF は .NET Core 以降に存在しないため、移行先は次の 3 方式から選ぶことになります。それぞれ設計思想が異なるため、サービスの利用形態に応じて選定します。

  • CoreWCF(リフト&シフト) — コミュニティ主導で開発された WCF サーバー側の移植実装。ServiceContract や DataContract をほぼそのまま流用でき、SOAP エンドポイントも維持できる。既存の SOAP クライアントとの互換性を保ったまま .NET 10 ランタイムへ載せ替えたい場合に最短の選択肢となる。
  • gRPC への移行 — コントラクトファーストで .proto を定義し、HTTP/2 上でバイナリ通信を行う。低レイテンシかつ多言語対応で、社内のサービス間通信に最適。WCF の RPC 的な使い方と親和性が高い。
  • ASP.NET Core Web API(REST)への移行 — HTTP/JSON ベースの REST API へ再設計する。ブラウザや外部パートナーなど、幅広いクライアントへ公開する用途に向く。
WCF の契約を gRPC の .proto 定義へ対応づけるマッピング
WCF の ServiceContract / DataContract を gRPC の service / message へ対応づける。

方式の向き不向き

選定の基準は「誰が、どこから呼ぶか」です。次の目安で振り分けると判断がぶれにくくなります。

  • 社内サービス間の高頻度通信 — gRPC。バイナリシリアライズと HTTP/2 多重化により、スループットとレイテンシで有利。厳密なコントラクトも保てる。
  • SOAP 互換の維持が必須 — CoreWCF。既存クライアントを改修できない、あるいは WS-* 系の要件が残る場合に選ぶ。移行コストを最小化できる。
  • 外部公開・ブラウザ連携 — ASP.NET Core Web API。REST/JSON は普及度が高く、OpenAPI によるドキュメント生成やフロントエンドとの統合が容易。

実際の移行では、社内向けは gRPC、外部向けは Web API、当面互換維持が必要な一部だけ CoreWCF、というように方式を混在させるのが現実的です。

コントラクトの対応づけ(WCF → gRPC)

gRPC へ移行する場合、WCF の属性ベースのコントラクトを .proto のスキーマへ翻訳します。対応関係は次のとおりです。

  • [ServiceContract] インターフェイス → service
  • [OperationContract] メソッド → rpc
  • [DataContract] / [DataMember] 型 → message / フィールド

まず、移行前の WCF コントラクトの例を示します。

[ServiceContract]
public interface IOrderService
{
    [OperationContract]
    OrderResult GetOrder(int orderId);
}

[DataContract]
public class OrderResult
{
    [DataMember(Order = 1)]
    public int OrderId { get; set; }

    [DataMember(Order = 2)]
    public string Status { get; set; }

    [DataMember(Order = 3)]
    public decimal Amount { get; set; }
}

これを gRPC の .proto で表現すると次のようになります。フィールド番号は WCF の Order に相当し、後方互換のために一度割り当てた番号は変更しないのが原則です。

syntax = "proto3";

option csharp_namespace = "Orders.Grpc";

package orders;

service OrderService {
  rpc GetOrder (GetOrderRequest) returns (OrderResult);
}

message GetOrderRequest {
  int32 order_id = 1;
}

message OrderResult {
  int32 order_id = 1;
  string status = 2;
  string amount = 3; // decimal は proto3 に無いため文字列で表現する
}

decimal は proto3 のプリミティブに存在しないため、金額のように精度が重要な値は string として送り、受信側でパースするのが一般的です。double で送ると丸め誤差が生じるため避けます。

サーバー実装(ASP.NET Core + gRPC)

ビルド時に Grpc.Tools が .proto から基底クラス(OrderService.OrderServiceBase)を生成します。これを継承してサービスを実装します。

public class OrderServiceImpl : OrderService.OrderServiceBase
{
    private readonly IOrderRepository _repository;

    public OrderServiceImpl(IOrderRepository repository)
        => _repository = repository;

    public override async Task<OrderResult> GetOrder(
        GetOrderRequest request, ServerCallContext context)
    {
        var order = await _repository.FindAsync(request.OrderId);
        if (order is null)
        {
            throw new RpcException(
                new Status(StatusCode.NotFound, $"Order {request.OrderId} not found"));
        }

        return new OrderResult
        {
            OrderId = order.Id,
            Status = order.Status,
            Amount = order.Amount.ToString(CultureInfo.InvariantCulture)
        };
    }
}

エンドポイントの登録は Program.cs で行います。DI コンテナは WCF の ServiceHost ではなく Microsoft.Extensions.DependencyInjection をそのまま利用します。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();

var app = builder.Build();
app.MapGrpcService<OrderServiceImpl>();
app.Run();

ストリーミング(WCF duplex → gRPC streaming)

WCF のコールバック契約による双方向通信(duplex)は、gRPC のストリーミングへ対応づけます。gRPC は 3 種類のストリーミングを標準で備えており、コールバック用に別インターフェイスを用意する必要がありません。

  • サーバーストリーミング — 1 リクエストに対しサーバーが複数のレスポンスを返す。進捗通知やフィードの配信に相当。
  • クライアントストリーミング — クライアントが複数のメッセージを送り、サーバーが 1 回応答する。バッチアップロードなど。
  • 双方向ストリーミング — WCF の duplex に最も近く、両者が独立してメッセージを送受信する。
service OrderService {
  rpc WatchOrders (WatchRequest) returns (stream OrderResult);
}

サーバー側は IServerStreamWriter にメッセージを書き込むことで逐次送信します。

public override async Task WatchOrders(
    WatchRequest request,
    IServerStreamWriter<OrderResult> responseStream,
    ServerCallContext context)
{
    await foreach (var order in _repository.SubscribeAsync(context.CancellationToken))
    {
        await responseStream.WriteAsync(new OrderResult
        {
            OrderId = order.Id,
            Status = order.Status,
            Amount = order.Amount.ToString(CultureInfo.InvariantCulture)
        });
    }
}

認証とエラー処理の対応

WCF のバインディング設定(<security> や <binding>)で表現していた認証は、ASP.NET Core の標準ミドルウェアに置き換えます。トークンベース認証であれば AddAuthentication().AddJwtBearer() を用い、メソッド単位の認可は [Authorize] 属性で表現します。従来 web.config に散在していた設定は appsettings.json と環境変数へ集約します。

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer();
builder.Services.AddAuthorization();

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGrpcService<OrderServiceImpl>();

エラー処理は、WCF の FaultException を gRPC の RpcException へ置き換えます。FaultCode に相当するのが StatusCode で、NotFound・InvalidArgument・PermissionDenied といった標準ステータスへマッピングします。詳細な業務エラー情報は、リッチエラーモデル(google.rpc.Status の details)や Metadata(トレーラー)へ載せて伝搬します。REST(Web API)へ移行する場合は、同じ考え方で HTTP ステータスコードと ProblemDetails に対応づけます。

移行の進め方

方式が混在しても破綻しないよう、契約の棚卸しから段階的に進めます。次の順序が実務では安定します。

  1. 契約の棚卸し — すべての ServiceContract と OperationContract を列挙し、呼び出し元・通信頻度・SOAP 依存の有無を洗い出す。
  2. 方式選定 — サービス単位で gRPC / CoreWCF / Web API を割り当てる。判断に迷うものは当面 CoreWCF に寄せ、後から作り替える。
  3. 再実装 — .proto や Web API コントローラを定義し、業務ロジックは既存資産から可能な限り分離して再利用する。
  4. 並走 — 新旧サービスを同時稼働させ、リバースプロキシ(YARP など)やクライアント側の切り替えフラグで一部トラフィックのみ新側へ流し、応答を突き合わせる。
  5. 切替 — 差異がないことを確認できた機能から順に新側へ寄せ、最終的に旧 WCF エンドポイントを撤去する。

この段階移行の考え方は、ランタイム全体の刷新でも共通します。より広い文脈は .NET Framework 4.x から .NET 10 への移行プレイブック を、サービス分割の設計指針は マイクロサービス設計パターン実践ガイド を参照してください。

gRPC と Native AOT

gRPC サービスは .NET 10 の Native AOT に対応しており、起動時間とメモリ使用量を大きく削減できます。コンテナのコールドスタートに敏感な社内マイクロサービスでは、gRPC + Native AOT の組み合わせが有効です。プロジェクトファイルで PublishAot を有効化し、リフレクションに依存しないコード生成経路を使うことで、AOT 前提のビルドが成立します。

<PropertyGroup>
  <PublishAot>true</PublishAot>
</PropertyGroup>

一方 CoreWCF は SOAP 互換のためのシリアライズ層が重く、AOT の恩恵を受けにくい点は選定時に考慮すべきトレードオフです。長期的に高性能・軽量を狙うサービスは、CoreWCF での一時退避を経て最終的に gRPC へ寄せる二段構えが有力です。

まとめ

WCF 移行は「どの方式に載せ替えるか」を一律に決めるのではなく、サービスごとの利用形態から gRPC・CoreWCF・Web API を割り当てる作業です。社内サービス間は gRPC、互換維持が必要な部分は CoreWCF、外部公開は Web API という基本方針を軸に、契約の棚卸しから並走・切替まで段階的に進めれば、リスクを抑えた移行が可能です。gRPC は Native AOT 対応により性能面でも WCF を上回る余地があります。全体像は .NET モダナイゼーション完全ガイド にまとめています。

エンハンスド株式会社では、WCF を含むレガシーなサービス基盤の移行を支援しています。

この記事をシェア

コピーしました

関連記事