ClinicFlow is designed to grow through a small number of repeatable extension patterns. Whether you are adding a new business operation, exposing existing data through a query, or modelling a new domain concept, the same conventions apply throughout. This page walks through each pattern with real code examples drawn from the existing codebase.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/0Crazy-0/ClinicFlow/llms.txt
Use this file to discover all available pages before exploring further.
Adding a New Command
A CQRS command represents an operation that changes state. The Application layer pattern consists of three co-located classes: the command record, the handler, and the validator.Create a
sealed record implementing IRequest (for void commands) or IRequest<TResponse> (for commands that return a value). Place it in a new sub-directory under the relevant feature folder inside ClinicFlow.Application:// ClinicFlow.Application/AppointmentTypes/Commands/AddAllowedSpecialtyToAppointmentType/
// AddAllowedSpecialtyToAppointmentTypeCommand.cs
using MediatR;
namespace ClinicFlow.Application.AppointmentTypes.Commands.AddAllowedSpecialtyToAppointmentType;
public sealed record AddAllowedSpecialtyToAppointmentTypeCommand(
Guid AppointmentTypeId,
Guid SpecialtyId
) : IRequest;
Create a
sealed class implementing IRequestHandler<TCommand> (or IRequestHandler<TCommand, TResponse>). Inject dependencies through the primary constructor — MediatR registers handlers automatically via AddMediatR(cfg => cfg.RegisterServicesFromAssembly(...)).// AddAllowedSpecialtyToAppointmentTypeCommandHandler.cs
using ClinicFlow.Domain.Common;
using ClinicFlow.Domain.Entities;
using ClinicFlow.Domain.Exceptions.Base;
using ClinicFlow.Domain.Interfaces;
using ClinicFlow.Domain.Interfaces.Repositories;
using MediatR;
namespace ClinicFlow.Application.AppointmentTypes.Commands.AddAllowedSpecialtyToAppointmentType;
public sealed class AddAllowedSpecialtyToAppointmentTypeCommandHandler(
IAppointmentTypeDefinitionRepository appointmentTypeRepository,
IUnitOfWork unitOfWork
) : IRequestHandler<AddAllowedSpecialtyToAppointmentTypeCommand>
{
public async Task Handle(
AddAllowedSpecialtyToAppointmentTypeCommand request,
CancellationToken cancellationToken
)
{
var appointmentType =
await appointmentTypeRepository.GetByIdAsync(
request.AppointmentTypeId,
cancellationToken
)
?? throw new EntityNotFoundException(
DomainErrors.General.NotFound,
nameof(AppointmentTypeDefinition),
request.AppointmentTypeId
);
appointmentType.AddAllowedSpecialty(request.SpecialtyId);
await unitOfWork.SaveChangesAsync(cancellationToken);
}
}
Create a class inheriting
AbstractValidator<TCommand>. The ValidationBehavior<,> MediatR pipeline behaviour automatically discovers and runs all validators registered in the Application assembly before the handler executes.// AddAllowedSpecialtyToAppointmentTypeCommandValidator.cs
using ClinicFlow.Domain.Common;
using FluentValidation;
namespace ClinicFlow.Application.AppointmentTypes.Commands.AddAllowedSpecialtyToAppointmentType;
public sealed class AddAllowedSpecialtyToAppointmentTypeCommandValidator
: AbstractValidator<AddAllowedSpecialtyToAppointmentTypeCommand>
{
public AddAllowedSpecialtyToAppointmentTypeCommandValidator()
{
RuleFor(x => x.AppointmentTypeId)
.NotEmpty()
.WithMessage(DomainErrors.Validation.InvalidValue);
RuleFor(x => x.SpecialtyId)
.NotEmpty()
.WithMessage(DomainErrors.Validation.InvalidValue);
}
}
No manual registration is needed.
AddApplicationServices() in ClinicFlow.Application/DependencyInjection.cs calls:services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
});
Adding a New Query
Queries follow the same three-file pattern but return a response object instead ofUnit. They must not modify state.
Guid is still worth adding — it provides a clear 400 response before hitting the database.
Implementing a New Repository
Repositories are defined as interfaces in the Domain layer and implemented in the Infrastructure layer. The existing repository contracts live inClinicFlow.Domain/Interfaces/Repositories/.
Step 1 — Define the interface in Domain
Step 2 — Implement in Infrastructure
Step 3 — Register in DI
Add the registration toClinicFlow.Infrastructure/DependencyInjection.cs:
Adding a Domain Event
Domain events allow entities to signal that something meaningful happened without coupling them to the downstream side effects.Step 1 — Define the event record
Domain event records live inClinicFlow.Domain/Events/<Feature>/ and implement IDomainEvent:
Step 2 — Raise the event from an entity
Inside the aggregate root method that performs the operation, callAddDomainEvent:
Step 3 — Handle the event in Application
Domain events are wrapped inDomainEventNotification<TEvent> by UnitOfWork before being published via MediatR’s IPublisher. Your handler must therefore implement INotificationHandler<DomainEventNotification<TEvent>>:
Implementing IRegionalSchedulingService
If your deployment region has specific appointment scheduling regulations (minimum lead times, specialty-level restrictions, age-based policies), implementIRegionalSchedulingService to enforce them:
Summary Checklist
| Extension | Files to create | Registration |
|---|---|---|
| New command | <Name>Command.cs, <Name>CommandHandler.cs, <Name>CommandValidator.cs | Automatic (assembly scan) |
| New query | <Name>Query.cs, <Name>QueryHandler.cs, optional <Name>QueryValidator.cs | Automatic |
| New repository | I<Name>Repository.cs in Domain, <Name>Repository.cs in Infrastructure | Manual in Infrastructure/DependencyInjection.cs |
| New domain event | <Name>Event.cs in Domain Events folder, <Name>EventHandler.cs in Application | Automatic (MediatR notification scan) |
| New service interface | Interface in Domain or Application Interfaces, implementation in Infrastructure | Manual in host/Infrastructure DI |