Skip to content

Query Module

A. Shafie edited this page Oct 29, 2023 · 3 revisions

Queries

Queries are designed to retrieve data from the system without altering its state. Here's how to utilize the query contracts, handlers, and mediators.

Query Contracts

Your queries can be articulated using:

  • IQuery<TQueryResult>: Represents a query expected to return a result. Every query must have a result.

    public class RetrieveUserQuery : IQuery<UserDetails>
    {
        public int UserId { get; set; }
    }
  • IStreamQuery<TQueryResult>: Represents a query that provides a stream of data as the outcome, encapsulated in the form IAsyncEnumerable<TQueryResult>.

    public class RetrieveAllUsersQuery : IStreamQuery<UserDetails>
    {
    }

Query Handler Contracts

Handlers process the queries. Implement the correct handler based on your specific needs:

  • IQueryHandler<TQuery, TQueryResult>: An asynchronous handler that caters to a specific query and returns a result. The handler can process more derived types of the specified TQuery due to its covariant nature.

    public class RetrieveUserHandler : IQueryHandler<RetrieveUserQuery, UserDetails>
    {
        public Task<UserDetails> HandleAsync(RetrieveUserQuery query, CancellationToken cancellationToken = default)
        {
            // Handle and return user details...
        }
    }
  • IStreamQueryHandler<TQuery, TQueryResult>: An asynchronous handler tailored for streaming queries. Returns a stream of data, typically represented as IAsyncEnumerable<TQueryResult>. This handler also boasts covariant handling capabilities.

    public class RetrieveAllUsersHandler : IStreamQueryHandler<RetrieveAllUsersQuery, UserDetails>
    {
        public IAsyncEnumerable<UserDetails> StreamAsync(RetrieveAllUsersQuery query, CancellationToken cancellationToken = default)
        {
            // Stream user details...
        }
    }

Query Mediator

The IQueryMediator serves as a bridge between your queries and their corresponding handlers, ensuring that the right handler is invoked for a given query.

  • QueryAsync<TQueryResult>: This method mediates a regular query to its corresponding handler and retrieves the result.

  • StreamAsync<TQueryResult>: Mediates a streaming query, allowing you to fetch a stream of data from the system.

/// <summary>
///     Mediates a query to its corresponding handler
/// </summary>
public interface IQueryMediator : IRegistrableQueryConstruct
{
    Task<TQueryResult> QueryAsync<TQueryResult>(IQuery<TQueryResult> query, CancellationToken cancellationToken = default);

    IAsyncEnumerable<TQueryResult> StreamAsync<TQueryResult>(IStreamQuery<TQueryResult> query, CancellationToken cancellationToken = default);
}

Query Pre Handlers

Pre-handlers allow operations to be performed before a query gets processed. Covariant handling ensures these pre-handlers can cater to broader query types:

  • IQueryPreHandler<TQuery>: Triggered before the processing of the designated TQuery.

    public class RetrieveUserPreHandler : IQueryPreHandler<RetrieveUserQuery>
    {
        public Task PreHandleAsync(RetrieveUserQuery query, CancellationToken cancellationToken = default)
        {
            // Validate or setup prerequisites...
        }
    }
  • IQueryPreHandler: A universal pre-handler that initiates before every query.

    public class GeneralPreHandler : IQueryPreHandler
    {
        public Task PreHandleAsync(IQuery query, CancellationToken cancellationToken = default)
        {
            // General validation or setup for all queries...
        }
    }

Query Post Handlers

Post-handlers activate after a query's execution, useful for operations like resource cleanup or logging. Covariant handling allows them to cater to more derived query types:

  • IQueryPostHandler<TQuery>: Activated after the processing of the given TQuery.

    public class RetrieveUserPostHandler : IQueryPostHandler<RetrieveUserQuery>
    {
        public Task PostHandleAsync(RetrieveUserQuery query, CancellationToken cancellationToken = default)
        {
            // Logging, cleanup, or further processing...
        }
    }
  • IQueryPostHandler<TQuery, TQueryResult>: Designed for a specific TQuery producing a TQueryResult outcome.

    public class RetrieveUserDetailedPostHandler : IQueryPostHandler<RetrieveUserQuery, UserDetails>
    {
        public Task PostHandleAsync(RetrieveUserQuery query, UserDetails result, CancellationToken cancellationToken = default)
        {
            // Processing based on query and result...
        }
    }

Query Error Handlers

These handlers allow interception and handling of errors that might arise during the query processing lifecycle.

  • IQueryErrorHandler<TQuery>: Triggered for a specific query encountering an error.

    public class RetrieveUserErrorHandler : IQueryErrorHandler<RetrieveUserQuery>
    {
        public Task HandleErrorAsync(RetrieveUserQuery query, Exception exception, CancellationToken cancellationToken = default)
        {
            // Handle error for RetrieveUserQuery...
        }
    }
  • IQueryErrorHandler<TQuery, TQueryResult>: Activated when a specific TQuery targeting a TQueryResult runs into an error.

    public class RetrieveUserDetailedErrorHandler : IQueryErrorHandler<RetrieveUserQuery, UserDetails>
    {
        public Task HandleErrorAsync(RetrieveUserQuery query, UserDetails result, Exception exception, CancellationToken cancellationToken = default)
        {
            // Handle error based on query and result...
        }
    }
  • IQueryErrorHandler: Acts as a catch-all error handler for all queries.

    public class GeneralErrorHandler : IQueryErrorHandler
    {
        public Task HandleErrorAsync(IQuery query, object result, Exception exception, CancellationToken cancellationToken = default)
        {
            // General error handling for all queries...
        }
    }