Skip to main content

Implementing Use Cases

Use cases represent application-specific business rules. This architecture uses CQRS (Command Query Responsibility Segregation) to separate write operations (commands) from read operations (queries).

Overview

Every use case consists of:
  • A Request (Command or Query)
  • A Handler that processes the request
  • Optional Application Services for cross-cutting concerns

Commands (Write Operations)

Commands modify system state and typically return an identifier or void.
1

Create the Command

Create a command class that implements IRequestCommand<TResponse> or IRequestCommand (for void).Create Application/UseCases/YourEntity/Commands/CreateYourEntity/CreateYourEntityCommand.cs:
using Core.Application;
using System.ComponentModel.DataAnnotations;

namespace Application.UseCases.YourEntity.Commands.CreateYourEntity
{
    /// <summary>
    /// Command to create a new YourEntity
    /// Implements IRequestCommand<TResponse> where TResponse is the return type
    /// </summary>
    public class CreateYourEntityCommand : IRequestCommand<string>
    {
        [Required]
        public string PropertyOne { get; set; }
        
        [Required]
        public int PropertyTwo { get; set; }

        public CreateYourEntityCommand()
        {
        }
    }
}
Example from codebase: Application/UseCases/DummyEntity/Commands/CreateDummyEntity/CreateDummyEntityCommand.cs:13-22
2

Create the Command Handler

Create a handler that implements IRequestCommandHandler<TRequest, TResponse>.Create Application/UseCases/YourEntity/Commands/CreateYourEntity/CreateYourEntityHandler.cs:
using Application.ApplicationServices;
using Application.Constants;
using Application.DomainEvents;
using Application.Exceptions;
using Application.Repositories;
using Core.Application;

namespace Application.UseCases.YourEntity.Commands.CreateYourEntity
{
    internal sealed class CreateYourEntityHandler : IRequestCommandHandler<CreateYourEntityCommand, string>
    {
        private readonly ICommandQueryBus _domainBus;
        private readonly IYourEntityRepository _repository;
        private readonly IYourEntityApplicationService _applicationService;

        public CreateYourEntityHandler(
            ICommandQueryBus domainBus,
            IYourEntityRepository repository,
            IYourEntityApplicationService applicationService)
        {
            _domainBus = domainBus ?? throw new ArgumentNullException(nameof(domainBus));
            _repository = repository ?? throw new ArgumentNullException(nameof(repository));
            _applicationService = applicationService ?? throw new ArgumentNullException(nameof(applicationService));
        }

        public async Task<string> Handle(CreateYourEntityCommand request, CancellationToken cancellationToken)
        {
            // 1. Create domain entity from command
            var entity = new Domain.Entities.YourEntity(
                request.PropertyOne,
                request.PropertyTwo
            );

            // 2. Validate entity
            if (!entity.IsValid)
                throw new InvalidEntityDataException(entity.GetErrors());

            // 3. Check business rules (optional)
            if (_applicationService.YourEntityExists(entity.Id))
                throw new EntityDoesExistException();

            try
            {
                // 4. Persist entity
                object createdId = await _repository.AddAsync(entity);

                // 5. Publish domain event
                await _domainBus.Publish(entity.To<YourEntityCreated>(), cancellationToken);

                // 6. Return identifier
                return createdId.ToString();
            }
            catch (Exception ex)
            {
                throw new BussinessException(ApplicationConstants.PROCESS_EXECUTION_EXCEPTION, ex.InnerException);
            }
        }
    }
}
Example from codebase: Application/UseCases/DummyEntity/Commands/CreateDummyEntity/CreateDummyEntityHandler.cs:17-43
3

Create Update and Delete Commands

Follow the same pattern for other operations:Update Command:
public class UpdateYourEntityCommand : IRequestCommand
{
    [Required]
    public string Id { get; set; }
    
    public string PropertyOne { get; set; }
    public int PropertyTwo { get; set; }
}
Update Handler:
public async Task Handle(UpdateYourEntityCommand request, CancellationToken cancellationToken)
{
    // 1. Retrieve existing entity
    var entity = await _repository.FindOneAsync(request.Id)
        ?? throw new EntityDoesNotExistException();

    // 2. Apply changes
    entity.SetPropertyOne(request.PropertyOne);
    entity.SetPropertyTwo(request.PropertyTwo);

    // 3. Validate
    if (!entity.IsValid)
        throw new InvalidEntityDataException(entity.GetErrors());

    // 4. Update
    await _repository.UpdateAsync(entity);

    // 5. Publish event
    await _domainBus.Publish(entity.To<YourEntityUpdated>(), cancellationToken);
}
Delete Command:
public class DeleteYourEntityCommand : IRequestCommand
{
    [Required]
    public string Id { get; set; }
}

Queries (Read Operations)

Queries retrieve data without modifying state and return DTOs (Data Transfer Objects).
1

Create the DTO

Create a Data Transfer Object for the query response:
using Core.Application;

namespace Application.DataTransferObjects
{
    public class YourEntityDto : IDto
    {
        public string Id { get; set; }
        public string PropertyOne { get; set; }
        public int PropertyTwo { get; set; }
        public DateTime CreatedAt { get; set; }
    }
}
2

Create the Query

Create a query class that implements IRequestQuery<TResponse>.Create Application/UseCases/YourEntity/Queries/GetYourEntityBy/GetYourEntityByQuery.cs:
using Application.DataTransferObjects;
using Core.Application;
using System.ComponentModel.DataAnnotations;

namespace Application.UseCases.YourEntity.Queries.GetYourEntityBy
{
    public class GetYourEntityByQuery : IRequestQuery<YourEntityDto>
    {
        [Required]
        public string Id { get; set; }

        public GetYourEntityByQuery()
        {
        }
    }
}
Example from codebase: Application/UseCases/DummyEntity/Queries/GetDummyEntityBy/GetDummyEntityByQuery.cs:7-15
3

Create the Query Handler

Create a handler that implements IRequestQueryHandler<TRequest, TResponse>.Create Application/UseCases/YourEntity/Queries/GetYourEntityBy/GetYourEntityByHandler.cs:
using Application.DataTransferObjects;
using Application.Exceptions;
using Application.Repositories;
using Core.Application;

namespace Application.UseCases.YourEntity.Queries.GetYourEntityBy
{
    internal sealed class GetYourEntityByHandler : IRequestQueryHandler<GetYourEntityByQuery, YourEntityDto>
    {
        private readonly IYourEntityRepository _repository;

        public GetYourEntityByHandler(IYourEntityRepository repository)
        {
            _repository = repository ?? throw new ArgumentNullException(nameof(repository));
        }

        public async Task<YourEntityDto> Handle(GetYourEntityByQuery request, CancellationToken cancellationToken)
        {
            // 1. Retrieve entity
            var entity = await _repository.FindOneAsync(request.Id)
                ?? throw new EntityDoesNotExistException();

            // 2. Map to DTO and return
            return entity.To<YourEntityDto>();
        }
    }
}
Example from codebase: Application/UseCases/DummyEntity/Queries/GetDummyEntityBy/GetDummyEntityByHandler.cs:8-17
4

Create Paginated Query

For listing operations, use pagination:
public class GetAllYourEntitiesQuery : QueryRequest<YourEntityDto>
{
    public uint PageIndex { get; set; } = 1;
    public uint PageSize { get; set; } = 10;
}

internal sealed class GetAllYourEntitiesHandler : IRequestQueryHandler<GetAllYourEntitiesQuery, QueryResult<YourEntityDto>>
{
    private readonly IYourEntityRepository _repository;

    public GetAllYourEntitiesHandler(IYourEntityRepository repository)
    {
        _repository = repository;
    }

    public async Task<QueryResult<YourEntityDto>> Handle(GetAllYourEntitiesQuery request, CancellationToken cancellationToken)
    {
        var entities = await _repository.GetAllAsync(
            request.PageIndex,
            request.PageSize
        );

        return new QueryResult<YourEntityDto>
        {
            Items = entities.Select(e => e.To<YourEntityDto>()).ToList(),
            PageIndex = request.PageIndex,
            PageSize = request.PageSize,
            TotalCount = await _repository.CountAsync()
        };
    }
}

Application Services

Application services handle cross-cutting concerns that don’t belong in handlers.
1

Define the Interface

namespace Application.ApplicationServices
{
    public interface IYourEntityApplicationService
    {
        bool YourEntityExists(string id);
        Task<bool> ValidateBusinessRule(object criteria);
    }
}
2

Implement the Service

using Application.Repositories;

namespace Application.ApplicationServices
{
    public class YourEntityApplicationService : IYourEntityApplicationService
    {
        private readonly IYourEntityRepository _repository;

        public YourEntityApplicationService(IYourEntityRepository repository)
        {
            _repository = repository ?? throw new ArgumentNullException(nameof(repository));
        }

        public bool YourEntityExists(string id)
        {
            var entity = _repository.FindOne(id);
            return entity != null;
        }

        public async Task<bool> ValidateBusinessRule(object criteria)
        {
            // Implement complex business logic here
            return true;
        }
    }
}
Example from codebase: Application/ApplicationServices/DummyEntityApplicationService.cs:10-22

Key Concepts

Commands:
  • Modify system state
  • Can return an ID or void
  • May publish domain events
  • Validate before persisting
Queries:
  • Read-only operations
  • Return DTOs, not domain entities
  • No side effects
  • Can use optimized read models
A handler should:
  1. Validate input
  2. Create/retrieve domain entities
  3. Execute business logic
  4. Persist changes (commands only)
  5. Publish events (commands only)
  6. Return results
A handler should NOT:
  • Contain complex business logic (use domain entities)
  • Directly access external services (use application services)
  • Return domain entities (use DTOs)
The architecture provides standard exceptions:
  • InvalidEntityDataException: Entity validation failed
  • EntityDoesNotExistException: Entity not found (404)
  • EntityDoesExistException: Duplicate entity (400)
  • BussinessException: General business rule violation (400)
These are handled by BaseExceptionFilter.

Complete CRUD Example

Here’s how all use cases work together:
// In your controller
public class YourEntityController : BaseController
{
    private readonly ICommandQueryBus _commandQueryBus;

    public YourEntityController(ICommandQueryBus commandQueryBus)
    {
        _commandQueryBus = commandQueryBus;
    }

    [HttpPost("api/v1/[Controller]")]
    public async Task<IActionResult> Create(CreateYourEntityCommand command)
    {
        var id = await _commandQueryBus.Send(command);
        return Created($"api/[Controller]/{id}", new { Id = id });
    }

    [HttpGet("api/v1/[Controller]/{id}")]
    public async Task<IActionResult> GetById(string id)
    {
        var entity = await _commandQueryBus.Send(new GetYourEntityByQuery { Id = id });
        return Ok(entity);
    }

    [HttpPut("api/v1/[Controller]")]
    public async Task<IActionResult> Update(UpdateYourEntityCommand command)
    {
        await _commandQueryBus.Send(command);
        return NoContent();
    }

    [HttpDelete("api/v1/[Controller]/{id}")]
    public async Task<IActionResult> Delete(string id)
    {
        await _commandQueryBus.Send(new DeleteYourEntityCommand { Id = id });
        return NoContent();
    }
}

Next Steps

Creating Controllers

Learn how to expose your use cases through API endpoints

Event Handling

Publish and subscribe to domain events

Build docs developers (and LLMs) love