Skip to main content

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.

ClinicFlow is organized as a three-project Clean Architecture solution. Each layer has a clearly defined responsibility and a strict dependency rule: inner layers know nothing about outer layers. Domain has no project references at all. Application references only Domain. Infrastructure references both Domain and Application — it is the outermost layer responsible for I/O, persistence, and external services.
ClinicFlow.Domain          ← no project references (pure)

ClinicFlow.Application     ← references Domain only

ClinicFlow.Infrastructure  ← references Domain + Application
This structure means the entire business model and application orchestration logic can be compiled, understood, and tested with zero knowledge of how data is stored or how the system is deployed.

Layer 1 — Domain

Project: ClinicFlow.Domain (net10.0, no <PackageReference> entries) The Domain layer is the heart of ClinicFlow. It contains every concept that belongs to the clinic’s ubiquitous language and enforces every invariant without reaching outside itself.

Entities

All entities inherit from BaseEntity, which provides a Guid identity, auditing support, and a domain-event collection:
public abstract class BaseEntity
{
    public Guid Id { get; private set; }
    private readonly IList<IDomainEvent> _domainEvents = [];

    public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();

    protected BaseEntity()
    {
        Id = Guid.NewGuid();
    }

    public void AddDomainEvent(IDomainEvent domainEvent) => _domainEvents.Add(domainEvent);
    public void ClearDomainEvents() => _domainEvents.Clear();
}
Entities that can be logically removed without hard-deletion inherit SoftDeletableEntity, which adds an IsDeleted flag and MarkAsDeleted() / UndoDeletion() helpers. Doctor is the primary example — suspending a doctor soft-deletes the record and triggers a DoctorSuspendedEvent that cascades into appointment reassignment.
EntityDescription
AppointmentFull lifecycle: Scheduled → CheckedIn → InProgress → Completed / Cancelled / NoShow / LateCancellation / RequiresReassignment
DoctorPhysician linked to a User, a MedicalSpecialty, and a ConsultationRoom; supports suspension and reactivation
PatientPrimary and family-member profiles with scheduling clearance and penalty tracking
UserAuthentication identity shared across roles; holds UserRole, hashed password, phone verification state
MedicalRecordClinical encounter record attached to a completed appointment
ScheduleDoctor’s weekly availability slots used to validate appointment time ranges
MedicalSpecialtyNamed specialty referenced by doctors and appointment type restrictions
AppointmentTypeDefinitionConfigurable appointment type with category, duration, age policy, and specialty restrictions
ClinicalFormTemplateJSON-schema-backed template for dynamic clinical detail forms
PatientPenaltyPenalty record (late cancellation, no-show) with block duration and expiry

Value objects

Value objects encapsulate domain concepts that have no identity of their own — they are equal when their data is equal:
Value ObjectWraps
PersonNameFirst and last name
EmailAddressValidated e-mail string
PhoneNumberValidated phone string
MedicalLicenseNumberPhysician license identifier
ConsultationRoomRoom identifier for a doctor
TimeRangeStart/end TimeOnly pair for appointment slots
BloodTypePatient blood type enum wrapper
PenaltyHistoryAggregated count and recency of patient penalties
SchedulingClearanceDerived from penalty history — grants or denies booking rights
AgeEligibilityPolicyMin/max age bounds for appointment types
CancellationLimitAllowed cancellation window for an appointment type
EmergencyContactName and phone for a patient’s emergency contact
EncounterDurationDuration of a clinical encounter

Domain events

Events implement the IDomainEvent marker interface and are raised inside entity methods via AddDomainEvent(). They are pending in memory until the Infrastructure layer dispatches them after persistence. Appointment events: AppointmentScheduledEvent, AppointmentRescheduledEvent, AppointmentCancelledEvent, AppointmentLateCancelledEvent, AppointmentCheckedInEvent, AppointmentStartedEvent, AppointmentCompletedEvent, AppointmentMarkedAsNoShowEvent, AppointmentReassignedEvent, AppointmentSystemCancelledEvent Other events: DoctorSuspendedEvent, PatientReactivatedEvent, ScheduleDeactivatedEvent

Domain services

Domain services coordinate multi-entity operations that don’t belong on any single entity:
ServiceResponsibility
AppointmentSchedulingServiceValidates patient clearance, doctor availability, and slot conflicts before calling Appointment.Schedule()
AppointmentCancellationServiceDetermines standard vs. late cancellation based on the cancellation window and issues penalties
AppointmentReschedulingServiceEnforces the single-reschedule rule and checks new slot availability
AppointmentReassignmentServiceAssigns a new doctor and time slot to a RequiresReassignment appointment
MedicalEncounterServiceOrchestrates appointment start, completion, and medical record creation
PatientPenaltyServiceApplies, evaluates, and resolves patient penalties
DoctorRegistrationServiceCreates a Doctor record and links it to an existing User
PrimaryProfileRegistrationServiceRegisters a new User + Patient pair
FamilyMemberRegistrationServiceAdds a dependent patient profile under an existing account
WeeklyScheduleSetupServiceCreates or replaces a doctor’s weekly schedule, enforcing slot overlap rules
UserAuthenticationServiceLogin, logout, and lockout logic
PatientAccessServiceChecks that the requesting user is authorized to act on behalf of a patient

Repository and service interfaces

All persistence and external-service contracts are declared in ClinicFlow.Domain/Interfaces/, keeping the Domain layer independent of any concrete implementation:
  • Repositories: IAppointmentRepository, IAppointmentTypeDefinitionRepository, IClinicalFormTemplateRepository, IDoctorRepository, IMedicalRecordRepository, IMedicalSpecialtyRepository, IPatientPenaltyRepository, IPatientRepository, IScheduleRepository, IUserRepository
  • Unit of work: IUnitOfWork
  • Services: IPasswordHasherService, IPhoneVerificationService, IRegionalSchedulingService

Layer 2 — Application

Project: ClinicFlow.Application (net10.0)
<ItemGroup>
  <ProjectReference Include="..\ClinicFlow.Domain\ClinicFlow.Domain.csproj" />
</ItemGroup>
<ItemGroup>
  <PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
  <PackageReference Include="MediatR" Version="14.1.0" />
  <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.8" />
</ItemGroup>
The Application layer orchestrates use cases. It contains no domain logic and no infrastructure code — it calls domain services and repositories through the interfaces defined in the Domain layer.

CQRS structure

Every use case is expressed as either a Command (mutates state, returns no or minimal data) or a Query (reads state, never mutates). Commands and queries are organized by feature area, each in its own folder with three files: the request record, the handler, and the validator.
Appointments/Commands/
├── CancelAppointmentByDoctor/
├── CancelAppointmentByPatient/
├── CancelAppointmentByStaff/
├── CheckInAppointmentByStaff/
├── CleanExpiredDisplacedAppointments/
├── MarkAppointmentAsNoShowByDoctor/
├── MarkAppointmentAsNoShowByStaff/
├── ReassignAppointment/
├── RescheduleByDoctor/
├── RescheduleByPatient/
├── RescheduleByStaff/
├── ScheduleByDoctor/
├── ScheduleByPatient/
└── ScheduleByStaff/
Feature areas covered: Appointments, AppointmentTypes, ClinicalFormTemplates, Doctors, MedicalRecords, MedicalSpecialties, Patients, Penalties, Schedules, Users.

ValidationBehavior pipeline

All commands and queries pass through the ValidationBehavior<TRequest, TResponse> MediatR pipeline behavior before reaching their handler. It runs every registered FluentValidation IValidator<TRequest> in parallel and throws an application-level ValidationException if any rules fail — preventing any invalid request from touching the domain or the database.
// ClinicFlow.Application/Behaviors/ValidationBehavior.cs
public class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    /// <inheritdoc />
    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken cancellationToken
    )
    {
        if (validators.Any())
        {
            var context = new ValidationContext<TRequest>(request);

            var validationResults = await Task.WhenAll(
                validators.Select(v => v.ValidateAsync(context, cancellationToken))
            );

            var failures = validationResults
                .Where(r => r.Errors.Count > 0)
                .SelectMany(r => r.Errors)
                .ToList();

            if (failures.Count > 0)
                throw new ValidationException(failures);
        }
        return await next(cancellationToken);
    }
}

Dependency injection registration

AddApplicationServices() is the single extension method that wires up the Application layer:
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
    services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());

    services.AddMediatR(cfg =>
    {
        cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
        cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
    });

    return services;
}
AddValidatorsFromAssembly auto-discovers every IValidator<T> in the Application assembly. AddBehavior ensures the ValidationBehavior wraps every IRequest<T> dispatch globally.

Layer 3 — Infrastructure

Project: ClinicFlow.Infrastructure (net10.0)
<ItemGroup>
  <ProjectReference Include="..\ClinicFlow.Domain\ClinicFlow.Domain.csproj" />
  <ProjectReference Include="..\ClinicFlow.Application\ClinicFlow.Application.csproj" />
</ItemGroup>
<ItemGroup>
  <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8" />
  <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.8" />
  <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" />
  <PackageReference Include="Bogus" Version="35.6.5" />
</ItemGroup>
The Infrastructure layer provides the concrete implementations of every interface declared in the Domain layer. It owns all I/O: database access, migrations, seeding, and external service stubs.

Persistence

ApplicationDbContext is the EF Core DbContext. Each entity has a dedicated IEntityTypeConfiguration<T> in Persistence/Configurations/ that maps value objects as owned types, configures check constraints, sets max lengths, and defines relationships. The UnitOfWork implementation wraps ApplicationDbContext.SaveChangesAsync() and is the single point where all persistence changes are flushed to PostgreSQL. Repository implementations (AppointmentRepository, AppointmentTypeDefinitionRepository) implement the domain’s IRepository interfaces using EF Core LINQ queries against the ApplicationDbContext.

Data seeding

When SeedOnStartup: true is configured, DbSeeder is hooked into EF Core’s UseSeeding and UseAsyncSeeding callbacks inside AddInfrastructureServices(). The seeder uses the Bogus library to generate realistic fake data — doctors, patients, specialties, appointment type definitions, clinical form templates, and appointments — populating the database on the first run without any manual SQL.

External service stubs

The Infrastructure layer provides concrete implementations for the domain service interfaces:
  • IPhoneVerificationService — phone OTP verification flow
  • IPasswordHasherService — password hashing and verification

Dependency injection registration

AddInfrastructureServices() wires the full Infrastructure layer:
public static IServiceCollection AddInfrastructureServices(
    this IServiceCollection services,
    IConfiguration configuration
)
{
    var dbOptions = DatabaseOptions.FromConfiguration(configuration);

    services.AddDbContext<ApplicationDbContext>(options =>
    {
        options.UseNpgsql(dbOptions.ConnectionString);

        if (dbOptions.SeedOnStartup)
        {
            options.UseSeeding(
                (context, _) =>
                    Persistence.Seeding.DbSeeder.Seed(
                        (ApplicationDbContext)context,
                        TimeProvider.System
                    )
            );
            options.UseAsyncSeeding(
                (context, _, cancellationToken) =>
                    Persistence.Seeding.DbSeeder.SeedAsync(
                        (ApplicationDbContext)context,
                        TimeProvider.System,
                        cancellationToken
                    )
            );
        }
    });

    services.AddScoped<IUnitOfWork, UnitOfWork>();
    services.AddScoped<IAppointmentRepository, AppointmentRepository>();
    services.AddScoped<
        IAppointmentTypeDefinitionRepository,
        AppointmentTypeDefinitionRepository
    >();

    return services;
}
DatabaseOptions reads from the Database configuration section, which must supply ConnectionString and an optional SeedOnStartup boolean.

Dependency flow summary

  ┌─────────────────────────────────────────┐
  │         ClinicFlow.Infrastructure        │
  │  EF Core · Npgsql · Repositories ·      │
  │  UnitOfWork · Seeding (Bogus) ·          │
  │  IPasswordHasherService impl ·           │
  │  IPhoneVerificationService impl          │
  └──────────────┬──────────────────────────┘
                 │ references
  ┌──────────────▼──────────────────────────┐
  │         ClinicFlow.Application           │
  │  MediatR handlers · FluentValidation ·  │
  │  ValidationBehavior pipeline ·          │
  │  Commands & Queries per feature         │
  └──────────────┬──────────────────────────┘
                 │ references
  ┌──────────────▼──────────────────────────┐
  │           ClinicFlow.Domain              │
  │  Entities · Value Objects ·             │
  │  Domain Events · Domain Services ·      │
  │  Repository interfaces · IUnitOfWork    │
  │  (zero external dependencies)           │
  └─────────────────────────────────────────┘

Domain Model

Explore every entity, value object, and domain event in detail — including the full appointment lifecycle state machine.

CQRS and MediatR

See how commands, queries, handlers, and the ValidationBehavior pipeline are used end-to-end across all feature areas.

Build docs developers (and LLMs) love