.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
原因をざっくり整理すると次の通りです。
| 項目 | 内容 |
|---|---|
| 対象 API | System.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 でも同様のことをどう実現するか考える必要があります。
| MSMQ | RabbitMQ | Azure Service Bus |
|---|---|---|
| Transactional Queue | Publisher Confirms + 手動 ACK、再送設計でカバー | トランザクション / セッション機能で対応 |
| Dead-letter Queue | デッドレタリング用の専用キュー + ルーティング | 標準で DLQ をサポート(MaxDeliveryCount 超過など) |
| 遅延再試行 | 遅延キュー / TTL + Dead-letter を組み合わせる | 遅延キュー、スケジュールメッセージ機能など |
新基盤の機能をうまく使うことで、MSMQ 時代よりもシンプルな実装にできる場合も多いです。
MSMQ API と各基盤の対応関係(ざっくりマッピング)
完全な 1 対 1 ではありませんが、よく使われる MSMQ API を基準に RabbitMQ / Service Bus との対応をまとめておきます。
| MSMQ(System.Messaging) | RabbitMQ | Azure Service Bus | メモ |
|---|---|---|---|
MessageQueue.Send | IModel.BasicPublish | ServiceBusSender.SendMessageAsync | 送信 API |
MessageQueue.Receive | EventingBasicConsumer + BasicAck | ServiceBusProcessor.ProcessMessageAsync | 受信 / 処理 API |
| Transactional Queue | Publisher Confirms + 手動 ACK / NACK | トランザクション / セッション | 厳密な「Exactly once」は基盤ごとに考慮が必要 |
| Ack / Negative Ack | BasicAck / BasicNack | CompleteMessageAsync / AbandonMessageAsync | 再試行ポリシーと組み合わせて設計 |
| キューの作成 / 管理 | 管理ツール / Management Plugin / IaC | Azure ポータル / 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 などに差し替えていくのが、もっとも現実的でリスクの少ないアプローチです。

コメント