.NET 8でSystem.Messaging(MSMQ)が使えない原因と代替メッセージキュー(RabbitMQ・Azure Service Bus)移行ガイド

.NET Framework 4.7.2 までは普通に使えていた System.Messaging(MSMQ)が、.NET 8 に移行した途端にビルドエラー…。この記事では「なぜ使えないのか」「.NET 8 で何を選ぶべきか」「RabbitMQ / Azure Service Bus などへの置き換えをどう進めるか」を、実務レベルの視点で整理します。

目次

.NET 8 ではなぜ System.Messaging(MSMQ)が使えないのか

.NET 8(および .NET Core / .NET 5+ 系列)には、もともと System.Messaging 名前空間自体が含まれていません。これは「.NET Framework API の移植は .NET Core 3.0 で打ち切り」とされた際に、MSMQ が対象外とされたためです。

実際、Microsoft Q&A でも「.NET 4.7.2 から .NET 8 へ移行したら System.Messaging が見つからない」「MessageQueue が使えない」という質問に対し、MSMQ は .NET 8 には追加されておらず、代替のメッセージ基盤を選ぶべきと案内されています。

典型的なビルドエラーは次の 2 つです。

The type or namespace name 'Messaging' does not exist in the namespace 'System'
The type or namespace name 'MessageQueue' could not be found

原因をざっくり整理すると次の通りです。

項目内容
対象 APISystem.Messaging(MSMQ クライアント API)
利用可能なランタイム.NET Framework のみ(4.x 系)
.NET 5/6/7/8 には含まれない
理由MSMQ は Windows 専用機能であり、クロスプラットフォームを前提にした「モダン .NET」には取り込まれなかった
公式見解モダン .NET では MSMQ を追加する予定はなく、必要なら別のメッセージング製品の利用を推奨

つまり、「ライブラリを追加すれば解決する」種類の問題ではなく、アーキテクチャとして MSMQ から離れる前提で考える必要があるということです。

結論の先出し:.NET 8 では MSMQ を前提にしない設計へ

まず最初に結論を整理しておきます。

  • .NET 8 には公式の System.Messaging / MSMQ クライアントは存在しない
  • NuGet には 非公式な移植パッケージ(Experimental.System.Messaging や MSMQ.Messaging, Msmq.NetCore など)があるが、本番利用は自己責任であり、今後の互換性やサポートは期待できない
  • 実務的には、RabbitMQ / Azure Service Bus / AWS SQS / Kafka など別のメッセージング基盤へ置き換えるのが王道
  • 移行コストを抑えるために、一度「メッセージング層」をインターフェースで抽象化し、その裏で MSMQ → 新基盤を差し替えるのが現実的

選択肢をざっと俯瞰すると以下のようになります。

選択肢位置づけ使いどころ注意点
Experimental.System.Messaging など非公式 MSMQ クライアント短期的な延命・検証用サポートなし、本番運用には向かない
RabbitMQ軽量メッセージブローカーオンプレ / クラウド問わず
ワークキュー / Pub-Sub
サーバー運用が必要
Azure Service BusマネージドメッセージブローカーAzure 中心構成、トピック/サブスクリプション、トランザクションが欲しいAzure 依存・従量課金
AWS SQS / SNSマネージドキュー / 通知AWS 環境でシンプルキューや配信メッセージ順序や機能は Service Bus よりシンプル
Apache Kafkaイベントストリーミング基盤高スループット・履歴保持・イベントソーシング運用がやや重い、単純な MSMQ 置き換えにはオーバースペックなことも

非公式 MSMQ ポートを使うのはアリか?

NuGet を検索すると、次のようなパッケージが見つかります。

  • Experimental.System.Messaging – .NET Framework の System.Messaging を .NET 8+ 向けに移植した非公式パッケージ(Windows 限定)
  • MSMQ.Messaging – 参照ソースを元にした .NET Core 用ポート
  • Msmq.NetCore – MSMQ.Messaging のフォークで .NET Standard 2.1 対応

これらは「古い MSMQ コードをほぼそのまま動かしたい」というニーズに応えるために作られており、基本的には次のような使い方を想定しています。

// using System.Messaging; を置き換える
using Experimental.System.Messaging;

var queue = new MessageQueue(@".\Private$\sample");
queue.Send("hello");

便利ではありますが、README などでも「開発者の個人的な用途のために作られており、テストも限定的」と明記されており、将来バージョンでの互換性も保証されていません。

おすすめの使い方としては、次のように割り切るのが無難です。

  • 短期的に「とりあえず .NET 8 ビルドを通したい」環境でのみ利用
  • 本番環境では、できるだけ早く RabbitMQ や Azure Service Bus などにリプレース
  • 保守契約や SLA が必要なシステムでは、非公式パッケージに依存しない

本命は別メッセージング基盤への置き換え

長期的な視点に立つと、MSMQ から離れて別のメッセージング製品を採用するのが最も現実的です。代表的な選択肢をもう少し詳しく見ていきます。

RabbitMQ

RabbitMQ は OSS のメッセージングサーバーで、AMQP というプロトコルを使ったキューイング・Pub/Sub が得意です。公式の .NET クライアントライブラリ RabbitMQ.Client も提供されており、.NET 8 からも問題なく利用できます。

  • オンプレ・クラウドどちらでも導入しやすい
  • ワークキュー、Pub/Sub、ルーティングなど柔軟なメッセージパターンをサポート
  • コンテナ(Docker / Kubernetes)での運用も一般的

Azure Service Bus

Azure Service Bus は、Azure 上で提供されるフルマネージドなメッセージングサービスです。

  • キューに加え、トピック/サブスクリプション(Pub/Sub)が標準で利用可能
  • セッション、トランザクション、デッドレターキューなどエンタープライズ向け機能が充実
  • .NET 用 SDK として Azure.Messaging.ServiceBus が提供されており、.NET 8 対応

AWS SQS / SNS

AWS 環境中心であれば、SQS(Simple Queue Service)と SNS(Simple Notification Service)が候補になります。

  • SQS:シンプルなメッセージキュー(標準と FIFO キュー)
  • SNS:Push 型の通知・配信(メール / HTTP / Lambda など)
  • .NET からは AWSSDK.SQS / AWSSDK.SimpleNotificationService で利用

Apache Kafka

Kafka は「キュー」というよりは 分散ログ/イベントストリーミング基盤 であり、次のような要件に向いています。

  • 大量のイベント(ログ、トラッキング情報など)をバースト的に受け付けたい
  • イベントの履歴を長期間保持し、後からリプレイしたい
  • 複数のコンシューマーが独立して同じイベントストリームを読む

単純な MSMQ の置き換えだけが目的ならオーバースペックになりがちですが、将来のイベントドリブンアーキテクチャを見据えるなら有力候補です。

どれを選ぶべき?判断のためのチェックリスト

「うちのシステムでは結局どれを選ぶのが良いのか」を考えるために、よくある観点ごとに比較してみます。

観点RabbitMQ が向くパターンAzure Service Bus が向くパターンAWS SQS / Kafka が向くパターン
インフラオンプレ or 任意クラウド、自前でサーバー運用したいAzure 中心、PaaS/マネージドを最大限活用したいAWS 中心、あるいはビッグデータ基盤と連携したい
配信パターンワークキュー、Pub/Sub、ルーティングキュー + Pub/Sub + 高度なフィルタリングシンプルキュー(SQS) or 高度なイベントストリーム(Kafka)
順序・履歴キュー単位である程度の順序セッションや FIFO 的要件にも対応FIFO キュー(SQS) / 完全なイベント履歴とリプレイ(Kafka)
運用負荷ミドルウェア運用の知見があるチーム向けAzure ポータルで管理、SaaS 的に運用コストを抑えたいAWS または Kafka クラスタ運用の知見がある
レガシーからの移行容易性MSMQ の「ローカルキューに貯める」イメージに近い.NET / Azure との親和性が高く企業向け機能も豊富クラウドネイティブ / ビッグデータ寄りの要件にハマると強い

ざっくりまとめると、

  • Azure ベースなら → Azure Service Bus
  • AWS ベースなら → SQS/SNS
  • オンプレやマルチクラウドなら → RabbitMQ
  • イベントストリーミング基盤が欲しいなら → Kafka

という選び方が分かりやすいです。

.NET 8 + RabbitMQ の最小実装(MSMQ からの置き換えイメージ)

ここからは、実際に MSMQ の MessageQueue.Send/Receive を RabbitMQ に置き換えるイメージで、.NET 8 向けのコード例を見ていきます。

メッセージ送信(Publish)

まずはシンプルな「キューにメッセージを投げる」コードです。

using RabbitMQ.Client;
using System;
using System.Text;

var factory = new ConnectionFactory
{
    Uri = new Uri("amqp://user:pass@host:5672/")
};

using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

// キューがなければ作る
channel.QueueDeclare(
    queue: "work-queue",
    durable: true,
    exclusive: false,
    autoDelete: false,
    arguments: null);

// 送信するメッセージ
var body = Encoding.UTF8.GetBytes("hello");

// 永続化フラグ
var props = channel.CreateBasicProperties();
props.Persistent = true;  // ディスクに書き出して耐障害性を高める

channel.BasicPublish(
    exchange: "",
    routingKey: "work-queue",
    basicProperties: props,
    body: body);

Console.WriteLine(" [x] Sent 'hello'");

MSMQ の queue.Send("hello") に相当する部分が BasicPublish になります。

メッセージ受信(Consume)

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;

var factory = new ConnectionFactory
{
    Uri = new Uri("amqp://user:pass@host:5672/")
};

using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();

// キューの存在を保証
channel.QueueDeclare(
    queue: "work-queue",
    durable: true,
    exclusive: false,
    autoDelete: false,
    arguments: null);

// 1 つずつフェアに配る
channel.BasicQos(
    prefetchSize: 0,
    prefetchCount: 1,
    global: false);

var consumer = new EventingBasicConsumer(channel);

consumer.Received += (sender, ea) =>
{
    var message = Encoding.UTF8.GetString(ea.Body.ToArray());
    Console.WriteLine($" [x] Received: {message}");

    try
    {
        // TODO: メッセージ処理ロジック

        // 正常処理できたので ACK
        channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
    }
    catch (Exception ex)
    {
        Console.Error.WriteLine(ex);

        // 必要に応じて NACK + 再キュー or デッドレターへルーティング
        channel.BasicNack(deliveryTag: ea.DeliveryTag, multiple: false, requeue: true);
    }
};

channel.BasicConsume(
    queue: "work-queue",
    autoAck: false,  // 手動 ACK にする
    consumer: consumer);

Console.WriteLine(" [*] Waiting for messages. Press Enter to exit.");
Console.ReadLine();

MSMQ の Receive() に対して、RabbitMQ では イベントベース の受信(EventingBasicConsumer)が一般的です。

メッセージング層をインターフェースで抽象化する

移行を楽にするには、アプリ側では「MSMQ なのか RabbitMQ なのか」を意識しないようにしておくのが重要です。例えば次のようなインターフェースを定義しておきます。

public interface IMessageBus
{
    Task PublishAsync(string queue, string body, CancellationToken cancellationToken = default);
    Task SubscribeAsync(string queue, Func<string, Task> handler, CancellationToken cancellationToken = default);
}

RabbitMQ 用の実装は次のようなイメージです。

public sealed class RabbitMqMessageBus : IMessageBus, IAsyncDisposable
{
    private readonly IConnection _connection;
    private readonly IModel _channel;

    public RabbitMqMessageBus(string connectionUri)
    {
        var factory = new ConnectionFactory
        {
            Uri = new Uri(connectionUri)
        };
        _connection = factory.CreateConnection();
        _channel = _connection.CreateModel();
    }

    public Task PublishAsync(string queue, string body, CancellationToken cancellationToken = default)
    {
        _channel.QueueDeclare(queue, durable: true, exclusive: false, autoDelete: false, arguments: null);

        var bytes = Encoding.UTF8.GetBytes(body);
        var props = _channel.CreateBasicProperties();
        props.Persistent = true;

        _channel.BasicPublish("", queue, props, bytes);
        return Task.CompletedTask;
    }

    public Task SubscribeAsync(string queue, Func<string, Task> handler, CancellationToken cancellationToken = default)
    {
        _channel.QueueDeclare(queue, durable: true, exclusive: false, autoDelete: false, arguments: null);
        _channel.BasicQos(0, 1, false);

        var consumer = new EventingBasicConsumer(_channel);

        consumer.Received += async (_, ea) =>
        {
            var text = Encoding.UTF8.GetString(ea.Body.ToArray());
            await handler(text);
            _channel.BasicAck(ea.DeliveryTag, multiple: false);
        };

        _channel.BasicConsume(queue, autoAck: false, consumer);
        return Task.CompletedTask;
    }

    public ValueTask DisposeAsync()
    {
        _channel.Dispose();
        _connection.Dispose();
        return ValueTask.CompletedTask;
    }
}

アプリケーションコードは IMessageBus だけを相手にするように書き換えておけば、後からでも RabbitMQ → Azure Service Bus に差し替えやすくなります。

.NET 8 + Azure Service Bus の最小実装

Azure 環境が前提なら、Service Bus を使う方が全体としてシンプルになることが多いです。公式 SDK Azure.Messaging.ServiceBus は .NET 8 でもそのまま利用できます。

キューへの送信

using Azure.Messaging.ServiceBus;

// 接続文字列とキュー名は Azure ポータルから取得
var client = new ServiceBusClient("<ConnectionString>");
var sender = client.CreateSender("orders");

await sender.SendMessageAsync(new ServiceBusMessage("hello Service Bus"));

await sender.DisposeAsync();
await client.DisposeAsync();

メッセージ受信(イベント駆動)

using Azure.Messaging.ServiceBus;

var client = new ServiceBusClient("<ConnectionString>");
var processor = client.CreateProcessor(
    queueName: "orders",
    new ServiceBusProcessorOptions
    {
        MaxConcurrentCalls = 1,
        AutoCompleteMessages = false
    });

processor.ProcessMessageAsync += async args =>
{
    var body = args.Message.Body.ToString();
    Console.WriteLine($"Received: {body}");

    // TODO: 処理ロジック

    await args.CompleteMessageAsync(args.Message);
};

processor.ProcessErrorAsync += args =>
{
    Console.Error.WriteLine(args.Exception);
    return Task.CompletedTask;
};

await processor.StartProcessingAsync();

Console.WriteLine("Processing... Press Enter to stop");
Console.ReadLine();

await processor.StopProcessingAsync();
await processor.DisposeAsync();
await client.DisposeAsync();

MSMQ の PeekCompleted / ReceiveCompleted イベントによる非同期処理に近い感覚で利用できますが、Service Bus 側にデッドレターキューや再試行ポリシーなど多くの機能が備わっているため、再送・エラー処理の実装をシンプルにできるのが利点です。

MSMQ からの段階的移行パターン

いきなり MSMQ を止めて別基盤に全面切り替え…は現実的ではないケースが多いので、実務では次のような「段階移行」がよく採られます。

ステップ 1:メッセージング層を抽象化する

  • MessageQueue を直接触っている箇所をすべて洗い出す
  • IMessageBus や IQueueClient など、アプリ目線で必要な操作だけを持つインターフェースを定義
  • 既存コードを「MSMQ 実装の IMessageBus」経由で使う形に書き換える

この段階ではまだランタイムは .NET Framework のままでも構いません。「MSMQ 直接依存」から「抽象化層への依存」に変えることがゴールです。

ステップ 2:ブリッジ(リレー)サービスで MSMQ ⇔ 新基盤を中継する

次に、MSMQ を話す .NET Framework サービスと、RabbitMQ / Service Bus を話す .NET 8 サービスを用意し、両者の間を橋渡しします。

  • パターン A:MSMQ に来たメッセージを読み取り、新基盤へ転送する「送信側ブリッジ」
  • パターン B:新基盤からメッセージを受け取り、MSMQ へ流す「受信側ブリッジ」

ブリッジは単純なコンソールアプリや Windows サービスで実装できます。

// .NET Framework 側: MSMQ → RabbitMQ ブリッジ(イメージコード)
using System.Messaging;
using RabbitMQ.Client;
using System.Text;

var mq = new MessageQueue(@".\Private$\legacy");
var factory = new ConnectionFactory { Uri = new Uri("amqp://user:pass@host/") };
using var conn = factory.CreateConnection();
using var ch = conn.CreateModel();

ch.QueueDeclare("new-queue", durable: true, exclusive: false, autoDelete: false, arguments: null);

while (true)
{
    var msg = mq.Receive();   // MSMQ から取得
    var body = msg.Body.ToString();

    var bytes = Encoding.UTF8.GetBytes(body);
    var props = ch.CreateBasicProperties();
    props.Persistent = true;

    ch.BasicPublish("", "new-queue", props, bytes);
}

こうすることで、フロント側だけ先に .NET 8 + 新基盤に移行し、バックエンドは暫定的に MSMQ のまま…というような段階移行も可能になります。

ステップ 3:メッセージフォーマットとスキーマを整理する

MSMQ 時代は BinaryMessageFormatter や XmlMessageFormatter に任せていた部分を、JSON や Protobuf などの明示的なスキーマとして整理するチャンスです。

  • JSON(テキストベース、デバッグしやすい)
  • Protobuf / MessagePack(バイナリ、軽量・高速)

スキーマをバージョン管理(例:type や version プロパティ)しておくと、将来の API 変更にも強くなります。

ステップ 4:デッドレター / リトライ / 遅延再試行を設計する

MSMQ のトランザクションキューや配信レポートを使っていた場合、RabbitMQ / Service Bus でも同様のことをどう実現するか考える必要があります。

MSMQRabbitMQAzure Service Bus
Transactional QueuePublisher Confirms + 手動 ACK、再送設計でカバートランザクション / セッション機能で対応
Dead-letter Queueデッドレタリング用の専用キュー + ルーティング標準で DLQ をサポート(MaxDeliveryCount 超過など)
遅延再試行遅延キュー / TTL + Dead-letter を組み合わせる遅延キュー、スケジュールメッセージ機能など

新基盤の機能をうまく使うことで、MSMQ 時代よりもシンプルな実装にできる場合も多いです。

MSMQ API と各基盤の対応関係(ざっくりマッピング)

完全な 1 対 1 ではありませんが、よく使われる MSMQ API を基準に RabbitMQ / Service Bus との対応をまとめておきます。

MSMQ(System.Messaging)RabbitMQAzure Service Busメモ
MessageQueue.SendIModel.BasicPublishServiceBusSender.SendMessageAsync送信 API
MessageQueue.ReceiveEventingBasicConsumer + BasicAckServiceBusProcessor.ProcessMessageAsync受信 / 処理 API
Transactional QueuePublisher Confirms + 手動 ACK / NACKトランザクション / セッション厳密な「Exactly once」は基盤ごとに考慮が必要
Ack / Negative AckBasicAck / BasicNackCompleteMessageAsync / AbandonMessageAsync再試行ポリシーと組み合わせて設計
キューの作成 / 管理管理ツール / Management Plugin / IaCAzure ポータル / ARM / Bicep / Terraform運用チームとの役割分担も合わせて設計

短期的な「とりあえずビルドを通す」ための対処

本番移行の前に「まずは .NET 8 へのコンパイルを通して動作検証したい」という場合には、次のような割り切りもあります。

  • 条件コンパイルで MSMQ 依存コードを囲む
    • #if NETFRAMEWORK(または #if WINDOWS)側に MSMQ 実装を置く
    • .NET 8 側には「何もしない」スタブ実装を用意しておき、あとからメッセージング基盤を実装する
  • テスト用途に限り、Experimental.System.Messaging のような非公式パッケージで一時しのぎ

ただし、これらはあくまで短期的な回避策です。MSMQ 自体は Windows 専用であり、モダン .NET の世界では主役ではありません。 長期的には、どこかのタイミングで必ず RabbitMQ / Service Bus などへの完全移行を計画しておくべきです。

まとめ:.NET 8 時代のメッセージング戦略

最後に、本記事のポイントを整理します。

  • .NET 8 には System.Messaging(MSMQ クライアント API)が存在せず、ライブラリ追加だけでは解決しない
  • NuGet の非公式移植(Experimental.System.Messaging / MSMQ.Messaging / Msmq.NetCore 等)は、あくまで自己責任の暫定策として割り切る
  • 本番運用を考えるなら、RabbitMQ / Azure Service Bus / AWS SQS / Kafka など、サポートされているメッセージング基盤に置き換えるのが王道
  • まずは メッセージング層を抽象化し、ブリッジ(リレー)サービスで MSMQ ⇔ 新基盤を中継する段階移行が現実的
  • 移行時には、メッセージフォーマット(JSON / Protobuf など)、リトライポリシー、デッドレター、監視・メトリクスまで含めて設計を見直すと、.NET 8 以降の長期運用に強いシステムになる

.NET Framework 時代の MSMQ 前提のコードを、いきなりきれいに書き直す必要はありません。まずは依存箇所をインターフェースの裏側に押し込めることから始めて、徐々に RabbitMQ や Azure Service Bus などに差し替えていくのが、もっとも現実的でリスクの少ないアプローチです。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次