.NET ヘルプ

CQRSパターン C#(開発者向けの仕組み)

リーガン・パン
リーガン・パン
2024年4月3日
共有:

CQRSの紹介

CQRS はコマンドとクエリの責任分離を意味します。 データの読み取りと書き込みを分離することに焦点を当てたパターンです。 この区別は幾つかの理由で非常に重要です。 まず、各操作をより柔軟に最適化できるようになり、アプリケーションのパフォーマンスとスケーラビリティが向上します。 コマンド(書き込み)とクエリ(読み取り)を分離すると、それぞれを独立して最適化できます。

たとえば、複雑なアプリケーションは高速な読み取り操作を必要とする一方で、書き込み操作が遅いことを許容できる場合があります。 CQRSを適用することにより、開発者は読み取りと書き込みのために異なるデータモデルを使用し、それぞれの操作の特定のニーズに合わせてデータアクセス層を分離できます。 この記事では、CQRSパターンの概念と.NET開発者向けのIronPDFライブラリについて探ります。

基本概念とコンポーネント

CQRSの核は、コマンド操作とクエリ操作を分離し、それぞれがデータインタラクションの異なる側面を処理することにあります。 これらのコンポーネントを理解することは、パターンを効果的に実装するために重要です。

  • コマンド: これらはデータを更新する責任があります。 コマンドは複雑なビジネスロジックを体現し、情報を返さずにデータストアのデータ状態を変更することができます。 コマンドは、書き込みデータタスクを処理する独自の役割を担い、出力を生成することなくアプリケーションの状態に直接影響を与えます。 例えば、新しいユーザーを追加することや既存の製品詳細を更新することは、コマンドによって実行されるアクションです。
  • クエリ:クエリは、クエリハンドラーによって管理され、システムの状態を変更せずにデータやデータ転送オブジェクトを取得します。 それらは、データに関してあなたが尋ねる質問です。 例えば、ユーザーのプロファイルを取得することや、在庫にあるすべての商品をリストすることはクエリです。 クエリはデータを返しますが、データやその状態を変更しないことを保証します。

    CQRSを .NETアプリケーションに実装するための人気ツールの一つは、メディエーターパターンライブラリであるMediatRです。 アプリケーションのコンポーネント間の結合を減らし、それらが間接的に通信するのを助けます。 MediatRは、コマンドやクエリとそのハンドラーとの間を仲介することにより、コマンドやクエリの処理を支援します。

ASP.NET Coreによる実践的な実装

ASP.NET CoreでCQRSパターンを実装するには、コマンドとクエリを分離するためにプロジェクトを設定し、それらの間を調停するためにMediatRのようなライブラリを使用します。 以下は、ASP.NET Core アプリケーションで CQRS をセットアップする方法の簡単な概要です。

ステップ1: ASP.NET アプリケーションをセットアップする

  1. Visual Studio を起動して新しいプロジェクトを作成します。

  2. 「ASP.NET Core Web Application」プロジェクトタイプを検索して選択します。 次へ進む。

    CQRS パターン C#(開発者向けの仕組み):図 1 - 新しい ASP.NET プロジェクトの作成

  3. プロジェクトに名前を付けて、その場所を設定してください。 作成をクリックしてください。

  4. ASP.NET Coreの「Webアプリケーション(モデルビューコントローラー)」テンプレートを選択してください。 あなたの要件に合った .NET Core バージョンをターゲットにしていることを確認してください。 作成をクリックしてください。

ステップ 2

次に、CQRS用にプロジェクトを整理します。 次の内容を日本語に翻訳してください:

フォルダを追加して、コマンド、クエリ、および共通のインターフェースを分離することができます。 ソリューション エクスプローラーでプロジェクトを右クリックし、「追加」から「新しいフォルダー」に進みます。 「Commands」、「Queries」、「Interfaces」の三つのフォルダを作成してください。

「Interfaces」フォルダーに、コマンドおよびクエリ用のインターフェースを追加します。 コマンドに対しては、コマンドを受け取りアクションを実行するメソッドHandleを持つインターフェースICommandHandlerを持つかもしれません。 クエリの場合、クエリを受け取りデータを返すメソッドHandleを持つインターフェースIQueryHandlerを持つことができます。

CQRS パターン C# (開発者向けの仕組み): 図2 - ファイルの整理例

ステップ3

では、コマンドとクエリを追加してデモを行いましょう。 あなたのアプリケーションがタスクを管理しており、タスク(コマンド)を追加し、タスクを取得(クエリ)したいとします。

「Interfaces」フォルダーに、2つのインターフェースを追加してください。

//Define interfaces for your handlers:
public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}
public interface IQueryHandler<TQuery, TResult>
{
    TResult Handle(TQuery query);
}
//Define interfaces for your handlers:
public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}
public interface IQueryHandler<TQuery, TResult>
{
    TResult Handle(TQuery query);
}
'Define interfaces for your handlers:
Public Interface ICommandHandler(Of TCommand)
	Sub Handle(ByVal command As TCommand)
End Interface
Public Interface IQueryHandler(Of TQuery, TResult)
	Function Handle(ByVal query As TQuery) As TResult
End Interface
$vbLabelText   $csharpLabel

「Commands」フォルダーに、タスクの詳細のプロパティを持つAddItemCommandクラスを追加します。 また、AddItemCommandHandler クラスを追加します。これは ICommandHandler を実装し、データベースにタスクを追加するためのロジックを含んでいます。

「Queries」フォルダーに、タスクのリクエストを表すクラスGetTasksQueryを追加します。 別のクラスGetTasksQueryHandlerを追加し、これはIQueryHandlerを実装し、データベースからタスクを取得するためのロジックを含みます。

簡単な例として、AddItemCommandは次のようになります:

public class AddItemCommand
{
    public string Name { get; set; }
    public int Quantity { get; set; }
    // Constructor
    public AddItemCommand(string name, int quantity)
    {
        Name = name;
        Quantity = quantity;
    }
}
public class AddItemCommand
{
    public string Name { get; set; }
    public int Quantity { get; set; }
    // Constructor
    public AddItemCommand(string name, int quantity)
    {
        Name = name;
        Quantity = quantity;
    }
}
Public Class AddItemCommand
	Public Property Name() As String
	Public Property Quantity() As Integer
	' Constructor
	Public Sub New(ByVal name As String, ByVal quantity As Integer)
		Me.Name = name
		Me.Quantity = quantity
	End Sub
End Class
$vbLabelText   $csharpLabel

そして、AddItemCommandHandler:

public class AddItemCommandHandler : ICommandHandler<AddItemCommand>
{
    public void Handle(AddItemCommand command)
    {
        // Here, you'd add the item to your database, for example, to have employee data stored
        Console.WriteLine($"Adding item: {command.Name} with quantity {command.Quantity}");
        // Add database logic here
    }
}
public class AddItemCommandHandler : ICommandHandler<AddItemCommand>
{
    public void Handle(AddItemCommand command)
    {
        // Here, you'd add the item to your database, for example, to have employee data stored
        Console.WriteLine($"Adding item: {command.Name} with quantity {command.Quantity}");
        // Add database logic here
    }
}
Public Class AddItemCommandHandler
	Implements ICommandHandler(Of AddItemCommand)

	Public Sub Handle(ByVal command As AddItemCommand)
		' Here, you'd add the item to your database, for example, to have employee data stored
		Console.WriteLine($"Adding item: {command.Name} with quantity {command.Quantity}")
		' Add database logic here
	End Sub
End Class
$vbLabelText   $csharpLabel

タスクを取得するためにパラメータを必要としない場合、GetItemsQuery は空でも問題ありません。GetItemsQueryHandler は次のようになります:

public class GetItemsQuery
{
    // This class might not need any properties, depending on your query
}
using CQRS_testing.Interfaces;
namespace CQRS_testing.Queries
{
    public class GetItemsQueryHandler : IQueryHandler<GetItemsQuery, IEnumerable<string>>
    {
        public IEnumerable<string> Handle(GetItemsQuery query)
        {
            // Here, you'd fetch items from your database
            return new List<string> { "Item1", "Item2" };
        }
    }
}
public class GetItemsQuery
{
    // This class might not need any properties, depending on your query
}
using CQRS_testing.Interfaces;
namespace CQRS_testing.Queries
{
    public class GetItemsQueryHandler : IQueryHandler<GetItemsQuery, IEnumerable<string>>
    {
        public IEnumerable<string> Handle(GetItemsQuery query)
        {
            // Here, you'd fetch items from your database
            return new List<string> { "Item1", "Item2" };
        }
    }
}
Imports CQRS_testing.Interfaces

Public Class GetItemsQuery
	' This class might not need any properties, depending on your query
End Class
Namespace CQRS_testing.Queries
	Public Class GetItemsQueryHandler
		Implements IQueryHandler(Of GetItemsQuery, IEnumerable(Of String))

		Public Function Handle(ByVal query As GetItemsQuery) As IEnumerable(Of String)
			' Here, you'd fetch items from your database
			Return New List(Of String) From {"Item1", "Item2"}
		End Function
	End Class
End Namespace
$vbLabelText   $csharpLabel

ASP.NETコントローラーでは、これらのハンドラーを使用してコマンドやクエリを処理します。 タスクを追加する場合、コントローラーアクションはAddTaskCommandを作成し、そのプロパティをフォームデータから設定した後、AddTaskCommandHandlerのインスタンスに渡して処理させます。 タスクを取得するためには、GetTasksQueryHandlerを呼び出してデータを取得し、ビューに渡します。

コントローラーに接続する

コマンドとクエリが設定されたら、それらをコントローラーで使用することができます。 次のようにして、ItemsControllerクラス内でそれを行うことができます:

public class ItemsController : Controller
{
    private readonly ICommandHandler<AddItemCommand> _addItemHandler;
    private readonly IQueryHandler<GetItemsQuery, IEnumerable<string>> _getItemsHandler;
    // Constructor injection is correctly utilized here
    public ItemsController(ICommandHandler<AddItemCommand> addItemHandler, IQueryHandler<GetItemsQuery, IEnumerable<string>> getItemsHandler)
    {
        _addItemHandler = addItemHandler;
        _getItemsHandler = getItemsHandler;
    }
    public IActionResult Index()
    {
        // Use the injected _getItemsHandler instead of creating a new instance
        var query = new GetItemsQuery();
        var items = _getItemsHandler.Handle(query);
        return View(items);
    }
    [HttpPost]
    public IActionResult Add(string name, int quantity)
    {
        // Use the injected _addItemHandler instead of creating a new instance
        var command = new AddItemCommand(name, quantity);
        _addItemHandler.Handle(command);
        return RedirectToAction("Index");
    }
}
public class ItemsController : Controller
{
    private readonly ICommandHandler<AddItemCommand> _addItemHandler;
    private readonly IQueryHandler<GetItemsQuery, IEnumerable<string>> _getItemsHandler;
    // Constructor injection is correctly utilized here
    public ItemsController(ICommandHandler<AddItemCommand> addItemHandler, IQueryHandler<GetItemsQuery, IEnumerable<string>> getItemsHandler)
    {
        _addItemHandler = addItemHandler;
        _getItemsHandler = getItemsHandler;
    }
    public IActionResult Index()
    {
        // Use the injected _getItemsHandler instead of creating a new instance
        var query = new GetItemsQuery();
        var items = _getItemsHandler.Handle(query);
        return View(items);
    }
    [HttpPost]
    public IActionResult Add(string name, int quantity)
    {
        // Use the injected _addItemHandler instead of creating a new instance
        var command = new AddItemCommand(name, quantity);
        _addItemHandler.Handle(command);
        return RedirectToAction("Index");
    }
}
Public Class ItemsController
	Inherits Controller

	Private ReadOnly _addItemHandler As ICommandHandler(Of AddItemCommand)
	Private ReadOnly _getItemsHandler As IQueryHandler(Of GetItemsQuery, IEnumerable(Of String))
	' Constructor injection is correctly utilized here
	Public Sub New(ByVal addItemHandler As ICommandHandler(Of AddItemCommand), ByVal getItemsHandler As IQueryHandler(Of GetItemsQuery, IEnumerable(Of String)))
		_addItemHandler = addItemHandler
		_getItemsHandler = getItemsHandler
	End Sub
	Public Function Index() As IActionResult
		' Use the injected _getItemsHandler instead of creating a new instance
		Dim query = New GetItemsQuery()
		Dim items = _getItemsHandler.Handle(query)
		Return View(items)
	End Function
	<HttpPost>
	Public Function Add(ByVal name As String, ByVal quantity As Integer) As IActionResult
		' Use the injected _addItemHandler instead of creating a new instance
		Dim command = New AddItemCommand(name, quantity)
		_addItemHandler.Handle(command)
		Return RedirectToAction("Index")
	End Function
End Class
$vbLabelText   $csharpLabel

すべてを連携させるために、特にASP.NET Coreで依存性注入 (DI) を使用している場合は、コマンドおよびクエリのハンドラーをStartup.csファイル内のDIコンテナに登録する必要があります。この方法で、ASP.NETは必要に応じてハンドラーのインスタンスを提供できます。

こちらはハンドラーを登録する非常に基本的な例です:

builder.Services.AddTransient<ICommandHandler<AddItemCommand>, AddItemCommandHandler>();
builder.Services.AddTransient<IQueryHandler<GetItemsQuery, IEnumerable<string>>, GetItemsQueryHandler>();
builder.Services.AddTransient<ICommandHandler<AddItemCommand>, AddItemCommandHandler>();
builder.Services.AddTransient<IQueryHandler<GetItemsQuery, IEnumerable<string>>, GetItemsQueryHandler>();
builder.Services.AddTransient(Of ICommandHandler(Of AddItemCommand), AddItemCommandHandler)()
builder.Services.AddTransient(Of IQueryHandler(Of GetItemsQuery, IEnumerable(Of String)), GetItemsQueryHandler)()
$vbLabelText   $csharpLabel

CQRSの実際のアプリケーションでは、書き込み操作用のデータモデルと読み取り操作用のデータモデルを区別することが基盤となっており、アーキテクチャがデータ処理の多様で最適化されたアプローチをサポートすることを保証します。

IronPDF: C# PDFライブラリ

CQRS パターン C# (開発者向けの仕組み): 図 3 - IronPDF ウェブページ

IronPDFでPDF管理を探索するは、C#プログラミング言語を使用する開発者向けのツールで、アプリケーション内で直接PDFドキュメントの作成、読み取り、編集を可能にします。 このライブラリはユーザーフレンドリーであり、PDFレポート、請求書の生成、またはHTMLコードからPDFを作成するなどのPDF機能を統合することが簡単になります。 IronPDFは、PDF内のテキストや画像の編集、ドキュメントのセキュリティ設定、ウェブページをPDF形式に変換するなど、さまざまな機能をサポートしています。 その多様性と使いやすさにより、プロジェクトにPDF操作を実装しようとする開発者にとって貴重なリソースとなります。

IronPDFは、すべてのレイアウトとスタイルを保持したまま、HTMLからPDFへの変換機能で優れています。 WebコンテンツからPDFを作成し、レポート、請求書、ドキュメントに適しています。 HTMLファイル、URL、およびHTML文字列はシームレスにPDFに変換できます。

using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
Imports IronPdf

Friend Class Program
	Shared Sub Main(ByVal args() As String)
		Dim renderer = New ChromePdfRenderer()

		' 1. Convert HTML String to PDF
		Dim htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>"
		Dim pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent)
		pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf")

		' 2. Convert HTML File to PDF
		Dim htmlFilePath = "path_to_your_html_file.html" ' Specify the path to your HTML file
		Dim pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath)
		pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf")

		' 3. Convert URL to PDF
		Dim url = "http://ironpdf.com" ' Specify the URL
		Dim pdfFromUrl = renderer.RenderUrlAsPdf(url)
		pdfFromUrl.SaveAs("URLToPDF.pdf")
	End Sub
End Class
$vbLabelText   $csharpLabel

コード例

それでは、C#アプリケーションでCommand Query Responsibility Segregation(CQRS)パターンに従って、IronPDFをどのように活用できるかを探ってみましょう。 以下は、CQRSセットアップ内でIronPDFを使用してPDFレポートを生成する方法を示す簡単な例です。 この例は概念的なもので、コマンドとしてPDFドキュメントの生成に焦点を当てています。

using IronPdf;
using System.Threading.Tasks;
namespace PdfGenerationApp.Commands
{
    public class GeneratePdfReportCommand
    {
        // Command handler that generates a PDF report
        public async Task GenerateReportAsync(string reportContent, string outputPath)
        {
            // Initialize the IronPDF HTML to PDF renderer
            var renderer = new ChromePdfRenderer();
            // Use IronPDF to generate a PDF from HTML content
            var pdf = await Task.Run(() => renderer.RenderHtmlAsPdf(reportContent));
            // Save the generated PDF to a specified path
            pdf.SaveAs(outputPath);
        }
    }
}
using IronPdf;
using System.Threading.Tasks;
namespace PdfGenerationApp.Commands
{
    public class GeneratePdfReportCommand
    {
        // Command handler that generates a PDF report
        public async Task GenerateReportAsync(string reportContent, string outputPath)
        {
            // Initialize the IronPDF HTML to PDF renderer
            var renderer = new ChromePdfRenderer();
            // Use IronPDF to generate a PDF from HTML content
            var pdf = await Task.Run(() => renderer.RenderHtmlAsPdf(reportContent));
            // Save the generated PDF to a specified path
            pdf.SaveAs(outputPath);
        }
    }
}
Imports IronPdf
Imports System.Threading.Tasks
Namespace PdfGenerationApp.Commands
	Public Class GeneratePdfReportCommand
		' Command handler that generates a PDF report
		Public Async Function GenerateReportAsync(ByVal reportContent As String, ByVal outputPath As String) As Task
			' Initialize the IronPDF HTML to PDF renderer
			Dim renderer = New ChromePdfRenderer()
			' Use IronPDF to generate a PDF from HTML content
			Dim pdf = Await Task.Run(Function() renderer.RenderHtmlAsPdf(reportContent))
			' Save the generated PDF to a specified path
			pdf.SaveAs(outputPath)
		End Function
	End Class
End Namespace
$vbLabelText   $csharpLabel

この例では、GeneratePdfReportCommand はCQRSパターン内のコマンドを表します。 これは、HTML文字列としてreportContentを受け取り、PDFレポートが保存されるoutputPathを指定するメソッドGenerateReportAsyncを含んでいます。 IronPDFのHtmlToPdfクラスは、HTMLコンテンツをPDF形式に変換し、それを指定されたパスに保存するために使用されます。 このセットアップは、特にCQRSによって推奨される関心の分離が必要なシナリオにおいて、PDF生成機能をアプリケーションのアーキテクチャに統合する方法を示しています。

CQRS パターン C#(開発者向けの仕組み):図 4 - 出力された PDF

結論

CQRS パターン C#(開発者向けの仕組み):図5 - IronPDF ライセンス情報

まとめると、コマンドクエリ責任分離 (CQRS) パターンは、アプリケーション内でデータの読み取りと書き込みの責任を分離するための構造化されたアプローチを提供します。 この分離はアーキテクチャを明確にするだけでなく、システムの柔軟性、スケーラビリティ、およびパフォーマンスも向上させます。 上記の手順に従うことで、MediatRのようなツールを使用して、コマンド、クエリ、およびそのハンドラー間の通信を効率化しながら、ASP.NET CoreアプリケーションにCQRSを実装できます。

IronPDFをCQRSベースのアプリケーションに統合することにより、その機能がさらに拡張され、PDFドキュメントの作成、操作、および保存を簡単に行うことができます。 レポート、請求書、または任意の形式のドキュメントを生成する際、IronPDF の包括的な機能とシンプルな構文は、開発ツールキットにおいて強力なツールとなります。 IronPDFは無料試用を提供しており、コミットする前にその機能を探索する機会を提供します。 継続して使用するには、$749からライセンスが開始され、プロジェクトのニーズに合わせたさまざまなオプションが提供されます。

リーガン・パン
ソフトウェアエンジニア
レーガンはリーディング大学で電子工学の学士号を取得しました。Iron Softwareに入社する前の仕事では、一つのタスクに集中して取り組んでいました。Iron Softwareでは、営業、技術サポート、製品開発、マーケティングのいずれにおいても広範な業務に携わることが最も楽しいと感じています。彼は、Iron Softwareライブラリを開発者がどのように使用しているかを理解し、その知識を使ってドキュメントを継続的に改善し、製品を開発することを楽しんでいます。
< 以前
C#での動作方法(開発者向け)
次へ >
C# 単体テスト (開発者はどのように機能するか)