AYUDA .NET

Patrón CQRS C# (Cómo funciona para desarrolladores)

Regan Pun
Regan Pun
3 de abril, 2024
Compartir:

Introducción al CQRS

CQRS significa Segregación de Responsabilidades de Comando y Consulta. Es un patrón que se centra en separar la lectura de datos de su escritura. Esta distinción es crucial por varias razones. En primer lugar, permite una optimización más flexible de cada operación, mejorando el rendimiento y la escalabilidad de la aplicación. Cuando separas los comandos (escrituras) y las consultas (lecturas), puedes optimizarlos de manera independiente.

Por ejemplo, una aplicación compleja puede requerir operaciones de lectura rápidas pero tolerar operaciones de escritura más lentas. Al aplicar CQRS, los desarrolladores pueden utilizar diferentes modelos de datos para lecturas y escrituras, segregando la capa de acceso a los datos para adaptarla a las necesidades específicas de cada operación. En este artículo, exploraremos los conceptos del patrón CQRS y la biblioteca IronPDF para desarrolladores .NET.

Conceptos básicos y componentes

El corazón de CQRS reside en separar las operaciones de comando y consulta, cada una de las cuales maneja diferentes aspectos de la interacción de datos. Comprender estos componentes es crucial para aplicar el patrón con eficacia.

  • Comandos: Son responsables de actualizar los datos. Los comandos incorporan una lógica de negocio compleja y pueden cambiar el estado de los datos en el almacén de datos actuando sin devolver ninguna información. Los comandos asumen el papel exclusivo de gestionar las tareas de escritura de datos, influyendo directamente en el estado de la aplicación sin producir ninguna salida. Por ejemplo, añadir un nuevo usuario o actualizar los detalles de un producto existente son acciones que se realizan mediante comandos.
  • Consultas: Las consultas, gestionadas por un manejador de consultas, recuperan datos o objetos de transferencia de datos sin cambiar el estado del sistema. Son las preguntas que se hacen sobre los datos. Por ejemplo, obtener el perfil de un usuario o listar todos los productos disponibles en un inventario son consultas. Las consultas devuelven datos, pero garantizan que no modifican los datos ni su estado.

    Una de las herramientas más populares para implementar CQRS en aplicaciones .NET es MediatR, una biblioteca de patrones mediadores. Ayuda a reducir el acoplamiento entre los componentes de una aplicación, haciendo que se comuniquen indirectamente. MediatR facilita la gestión de comandos y consultas mediando entre el comando/consulta y su gestor.

Implementación práctica con ASP.NET Core

Implementar el patrón CQRS en ASP.NET Core implica configurar su proyecto para separar comandos y consultas, utilizando una biblioteca como MediatR para mediar entre ellos. A continuación se muestra una descripción general simplificada de cómo puede configurar CQRS en su aplicación ASP.NET Core.

Paso 1: Configure su aplicación ASP.NET

  1. Inicie Visual Studio y elija crear un nuevo proyecto.

  2. Busque y seleccione un tipo de proyecto "ASP.NET Core Web Application". Haga clic en Siguiente.

    Patrón CQRS C# (Cómo Funciona para Desarrolladores): Figura 1 - Creando un nuevo proyecto ASP.NET

  3. Dale un nombre a tu proyecto y establece su ubicación. Haga clic en Crear.

  4. Elija la plantilla "Aplicación web (Modelo-Vista-Controlador)" para ASP.NET Core. Asegúrese de que está utilizando la versión de .NET Core que se adapta a sus necesidades. Haga clic en Crear.

Paso 2

A continuación, deberá organizar su proyecto para CQRS. Puedes hacerlo añadiendo carpetas para separar los comandos, las consultas y las interfaces comunes que utilizarán. En el Explorador de soluciones, haga clic con el botón derecho del ratón en su proyecto, vaya a "Añadir" y, a continuación, a "Nueva carpeta". Cree tres carpetas: "Comandos", "Consultas" e "Interfaces".

En la carpeta "Interfaces", añada interfaces para sus comandos y consultas. Para un comando, podrías tener una interfaz ICommandHandler con un método Handle que toma un comando y realiza la acción. Para una consulta, podrías tener una interfaz IQueryHandler con un método Handle que toma una consulta y devuelve datos.

Patrón CQRS en C# (Cómo funciona para desarrolladores): Figura 2 - Ejemplo de cómo se podrían organizar los archivos

Paso 3

Ahora, vamos a añadir un comando y una consulta para demostrarlo. Supongamos que su aplicación gestiona tareas y desea agregar una tarea (comando) y recuperar tareas (consulta).

En la carpeta "Interfaces", añade dos interfaces:

//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

En la carpeta "Commands", añade una clase AddItemCommand con propiedades para los detalles de la tarea. También, agrega una clase AddItemCommandHandler que implemente ICommandHandler y contenga la lógica para añadir una tarea a la base de datos.

En la carpeta "Queries", añade una clase GetTasksQuery que representa una solicitud de tareas. Agregue otra clase GetTasksQueryHandler que implemente IQueryHandler y contenga la lógica para recuperar tareas de la base de datos.

Por un ejemplo sencillo, su AddItemCommand podría verse así:

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

Y el 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

Su GetItemsQuery podría estar vacío si no necesita ningún parámetro para obtener tareas, y GetItemsQueryHandler podría parecerse a:

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

En sus controladores ASP.NET, utilizará estos manejadores para procesar comandos y consultas. Para agregar una tarea, la acción del controlador crearía un AddTaskCommand, establecería sus propiedades a partir de los datos del formulario y luego lo pasaría a una instancia de AddTaskCommandHandler para manejarlo. Para recuperar tareas, llamaría a un GetTasksQueryHandler para obtener los datos y pasarlos a la vista.

Cableado en un controlador

Una vez configurados los comandos y las consultas, ya puedes utilizarlos en tus controladores. Aquí está cómo podría hacerlo en una clase 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

Para conectar todo, especialmente si estás utilizando Inyección de Dependencias (DI) en ASP.NET Core, necesitarás registrar tus manejadores de comandos y consultas con el contenedor de DI en el archivo Startup.cs. De esta manera, ASP.NET puede proporcionar instancias de tus manejadores cuando se necesiten.

He aquí un ejemplo muy básico de registro de un manejador:

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

En la aplicación práctica de CQRS, la distinción entre el modelo de datos para operaciones de escritura y el de operaciones de lectura es fundamental, ya que garantiza que la arquitectura admita enfoques variados y optimizados para tratar los datos.

IronPDF: C# Biblioteca PDF

Patrón CQRS C# (Cómo Funciona Para Desarrolladores): Figura 3 - Página web de IronPDF

Explore IronPDF para la gestión de PDF es una herramienta para desarrolladores que trabajan con el lenguaje de programación C#, permitiéndoles crear, leer y editar documentos PDF directamente dentro de sus aplicaciones. Esta biblioteca es fácil de usar, lo que facilita la integración de funcionalidades PDF como generar reportes PDF, facturas, o crear PDFs a partir de código HTML. IronPDF admite varias funciones, como la edición de texto e imágenes en PDF, la configuración de la seguridad de los documentos y la conversión de páginas web a formato PDF. Su versatilidad y facilidad de uso lo convierten en un valioso recurso para los desarrolladores que deseen implementar operaciones PDF en sus proyectos.

IronPDF sobresale con su capacidad de conversión de HTML a PDF, manteniendo todos los diseños y estilos intactos. Crea archivos PDF a partir de contenidos web, adecuados para informes, facturas y documentación. Los archivos HTML, las URL y las cadenas HTML se pueden convertir a PDF sin problemas.

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

Ejemplo de código

Ahora, exploremos cómo se puede utilizar IronPDF dentro de una aplicación C# siguiendo el patrón de segregación de responsabilidad de comando-consulta (CQRS). A continuación se muestra un ejemplo simplificado que demuestra cómo podría utilizar IronPDF dentro de una configuración CQRS para generar un informe PDF. Este ejemplo es conceptual y se centra en la generación de un documento PDF como comando.

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

En este ejemplo, GeneratePdfReportCommand representa un comando en el patrón CQRS. Incluye un método GenerateReportAsync que recibe reportContent como una cadena HTML y un outputPath donde se guardará el informe en PDF. La clase HtmlToPdf de IronPDF se utiliza para convertir el contenido HTML al formato PDF, que luego se guarda en la ruta especificada. Esta configuración ilustra cómo puede integrar la funcionalidad de generación de PDF en la arquitectura de su aplicación, especialmente en escenarios que requieren una clara separación de preocupaciones, tal y como promueve CQRS.

Patrón CQRS C# (Cómo Funciona para Desarrolladores): Figura 4 - PDF Generado

Conclusión

Patrón CQRS en C# (Cómo funciona para desarrolladores): Figura 5 - Información de licencia de IronPDF

Para finalizar, el patrón de Segregación de Responsabilidades de Comando y Consulta (CQRS) ofrece un enfoque estructurado para separar las responsabilidades de lectura y escritura de datos en sus aplicaciones. Esta separación no sólo aclara la arquitectura, sino que también mejora la flexibilidad, escalabilidad y rendimiento de sus sistemas. Siguiendo los pasos descritos anteriormente, puede implementar CQRS en sus aplicaciones ASP.NET Core, utilizando herramientas como MediatR para agilizar la comunicación entre comandos, consultas y sus manejadores.

La integración de IronPDF en su aplicación basada en CQRS amplía aún más sus capacidades, permitiéndole crear, manipular y almacenar documentos PDF sin esfuerzo. Tanto si genera informes, facturas o cualquier tipo de documento, las completas funciones de IronPDF y su sencilla sintaxis lo convierten en una potente herramienta de desarrollo. IronPDF ofrece una prueba gratuita, dándote la oportunidad de explorar sus capacidades antes de comprometerte. Para uso continuo, las licencias comienzan desde $749, ofreciendo varias opciones para adaptarse a las necesidades de su proyecto.

Regan Pun
Ingeniero de software
Regan se licenció en Ingeniería Electrónica por la Universidad de Reading. Antes de incorporarse a Iron Software, sus anteriores puestos de trabajo le obligaban a centrarse en tareas concretas; lo que más le gusta de Iron Software es la variedad de tareas que puede realizar, ya sea añadiendo valor a las ventas, el soporte técnico, el desarrollo de productos o el marketing. Le gusta entender cómo utilizan los desarrolladores la biblioteca de Iron Software y utilizar ese conocimiento para mejorar continuamente la documentación y desarrollar los productos.
< ANTERIOR
En C# (Cómo funciona para desarrolladores)
SIGUIENTE >
Pruebas unitarias en C# (cómo funcionan para los desarrolladores)