Skip to content

messaging queries

Brian Greco edited this page Apr 5, 2023 · 2 revisions

IMAGE Queries

The query implementation is based on the CQRS (Command Query Responsibility Segregation) pattern. This pattern suggests that it is beneficial to have actions that modify the state of an application separate from querying of the application state. The rational is the process for updating an application's state is usually more involved and requires steps not necessary for querying. This allows querying of the application's state to be more efficient and not necessarily dependent on the loading of complete domain-models. This allows the query implementation to utilize more efficient methods of accessing the data.

The following shows an example of declaring and dispatching a query. When a query is dispatched, it is handled by a query consumer. The query consumer can be an application service or directly handled by a repository. Where the query is handled can also change overtime and does not require refactoring the calling code. The calling code only knows about the IMessagingService service it uses to dispatch queries.

Define Query

As with commands and domain-events, queries are often located within an application's project representing the domain. Create the following entity to be the return type of the query in the following location: Examples.Messaging.Domain/Entities

namespace Examples.Messaging.Domain.Entities;

public class Car
{
    public string Make { get; set; } = string.Empty;
    public string Model { get; set; } = string.Empty;
    public string Color { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public int Year { get; set;}
}

Then create a query named CarSalesQuery by deriving from the based Query class in the following location: Examples.Messaging.Domain/Queries

using Examples.Messaging.Domain.Entities;
using NetFusion.Messaging.Types;

namespace Examples.Messaging.Domain.Queries;

public class CarSalesQuery : Query<Car[]>
{
    public string Make { get; }
    public int Year { get; }

    public CarSalesQuery(
        string make,
        int year)
    {
        Make = make;
        Year = year;
    }
}

This example will download the data to be queried from the Internet.

Define Query Consumer

The following repository defines the consumer for the query and is located here: Examples.Messaging.Infra/Repositories

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices.JavaScript;
using System.Text.Json;
using System.Threading.Tasks;
using Examples.Messaging.Domain.Entities;
using Examples.Messaging.Domain.Queries;
using Microsoft.Extensions.Logging;

namespace Examples.Messaging.Infra.Repositories;

public class AutoSalesRepository
{
    private readonly ILogger _logger;

    public AutoSalesRepository(
        ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger("Auto Sales Repository");
    }

    public async Task<Car[]> OnQuery(CarSalesQuery query)
    {
        _logger.LogDebug("Attempting to download data...");

        var httpClient = new HttpClient();

        HttpResponseMessage response = await httpClient.GetAsync(
            @"https://raw.githubusercontent.com/grecosoft/NetFusion-Examples/master/Examples/Data/inventory.json");

        response.EnsureSuccessStatusCode();
        string responseBody = await response.Content.ReadAsStringAsync();

        var data = JsonSerializer.Deserialize<InventoryResponse>(responseBody, 
            new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });

        if (data == null) return Array.Empty<Car>();
            
        return data.SalesInfo 
            .Where(s => s.Make == query.Make && s.Year == query.Year)
            .Where(s => 
                !string.IsNullOrWhiteSpace(s.Make) &&
                !string.IsNullOrWhiteSpace(s.Make) && 
                !string.IsNullOrWhiteSpace(s.Color))
            .Select(s => new Car(s.Make!, s.Model!, s.Color!, s.Price, s.Year))
            .ToArray();
    }
    
    private class InventoryResponse
    {
        public AutoSalesInfo[] SalesInfo { get; set; } = Array.Empty<AutoSalesInfo>();
    }
    
    private class AutoSalesInfo
    {
        public string? Make { get; set; }
        public string? Model { get; set; }
        public string? Color { get; set; }
        public decimal Price { get; set; }
        public int Year { get; set;}
    }
}

The following data is returned from the above web request:

IMAGE

Define Message Route

Add the following to the InMemoryRouter class to route the query to the repository:

using Examples.Messaging.Domain.Entities;
using Examples.Messaging.Domain.Queries;
using Examples.Messaging.Infra.Repositories;
using NetFusion.Messaging.InProcess;

namespace Examples.Messaging.Infra.Routers;

public class InMemoryRouter : MessageRouter
{
    protected override void OnConfigureRoutes()
    {
        OnQuery<CarSalesQuery, Car[]>(route => route.ToConsumer<AutoSalesRepository>(c => c.OnQuery));
    }
}

Define WebApi Controller

Define the following controller to create and dispatch the query:

using Examples.Messaging.Domain.Entities;
using Examples.Messaging.Domain.Queries;
using Microsoft.AspNetCore.Mvc;
using NetFusion.Messaging;

namespace Examples.Messaging.WebApi.Controllers;

[ApiController, Route("api/messaging")]
public class MessageController : ControllerBase
{
    private readonly IMessagingService _messaging;

    public MessageController(IMessagingService messaging)
    {
        _messaging = messaging;
    }

    [HttpGet("auto/sales/{make}/{year}")]
    public Task<Car[]> GetAutoSales(string make, int year)
    {
        var query = new CarSalesQuery(make, year);
        return _messaging.DispatchAsync(query);
    }
}

Run the WebApi server application and test the execution of the query:

cd ./src/Demo.WebApi
dotnet run

Test the dispatching of the query by invoking the WebApi method as follows: image

The following shows the debug log written when a query is dispatched:

image

image

After the query is dispatched, a log entry is also written displaying its corresponding result:

image

Clone this wiki locally