Learnixo
Back to blog
AI Systemsintermediate

Application Layer — Use Cases, Interfaces, and Orchestration

What the Application layer is responsible for in Clean Architecture: orchestrating use cases via command and query handlers, defining interfaces for external services, and keeping business logic out.

Asma Hafeez KhanMay 16, 20264 min read
Clean Architecture.NETApplication LayerUse CasesCQRS
Share:š•

What the Application Layer Does

The Application layer orchestrates the domain to fulfill use cases. It answers: "what should the system do when a user submits this command or sends this request?"

Application layer is responsible for:
  āœ“ Command handlers (create patient, approve drug order)
  āœ“ Query handlers (get patient by ID, list active prescriptions)
  āœ“ Input validation (FluentValidation validators)
  āœ“ Interfaces for external services (IEmailService, IPharmacyNotificationService)
  āœ“ Repository interfaces (IPatientRepository)
  āœ“ Response DTOs (PatientResponse, PrescriptionSummary)
  āœ“ Result types (Result, Error)

Application layer does NOT:
  āœ— Implement repositories (that is Infrastructure's job)
  āœ— Contain EF Core, SQL, or Redis
  āœ— Contain HTTP routing or serialization
  āœ— Contain complex domain rules (those belong in entities)

The Application.csproj File

XML
<!-- src/SystemForge.Application/SystemForge.Application.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="FluentValidation" Version="11.*" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\Domain\SystemForge.Domain.csproj" />
  </ItemGroup>
</Project>

Command and Query Records

C#
// Application/Patients/Commands/CreatePatient/CreatePatientCommand.cs
namespace SystemForge.Application.Patients.Commands.CreatePatient;

public sealed record CreatePatientCommand(
    string Name,
    DateOnly DateOfBirth,
    string MRN);

// Application/Patients/Queries/GetPatient/GetPatientQuery.cs
public sealed record GetPatientQuery(PatientId PatientId);

// Application/DrugOrders/Commands/ApproveOrder/ApproveOrderCommand.cs
public sealed record ApproveOrderCommand(
    DrugOrderId OrderId,
    string ApprovedBy);

A Command Handler

C#
// Application/Patients/Commands/CreatePatient/CreatePatientCommandHandler.cs
namespace SystemForge.Application.Patients.Commands.CreatePatient;

public sealed class CreatePatientCommandHandler
{
    private readonly IPatientRepository _patients;
    private readonly IUnitOfWork _unitOfWork;

    public CreatePatientCommandHandler(
        IPatientRepository patients,
        IUnitOfWork unitOfWork)
    {
        _patients    = patients;
        _unitOfWork  = unitOfWork;
    }

    public async Task<Result<PatientId>> Handle(
        CreatePatientCommand command,
        CancellationToken ct)
    {
        // Check business precondition
        if (await _patients.ExistsByMRNAsync(command.MRN, ct))
            return Result.Failure<PatientId>(PatientErrors.MRNAlreadyExists);

        // Delegate creation to the domain entity
        var patientResult = Patient.Create(command.Name, command.DateOfBirth, command.MRN);
        if (patientResult.IsFailure)
            return Result.Failure<PatientId>(patientResult.Error);

        var patient = patientResult.Value;
        await _patients.AddAsync(patient, ct);
        await _unitOfWork.SaveChangesAsync(ct);   // dispatches domain events inside

        return Result.Success(patient.Id);
    }
}

A Query Handler

C#
// Application/Patients/Queries/GetPatient/GetPatientQueryHandler.cs
public sealed class GetPatientQueryHandler
{
    private readonly IPatientRepository _patients;

    public GetPatientQueryHandler(IPatientRepository patients)
        => _patients = patients;

    public async Task<Result<PatientResponse>> Handle(
        GetPatientQuery query,
        CancellationToken ct)
    {
        var patient = await _patients.GetByIdAsync(query.PatientId, ct);

        if (patient is null)
            return Result.Failure<PatientResponse>(PatientErrors.NotFound);

        var response = new PatientResponse(
            patient.Id.Value,
            patient.Name,
            patient.DateOfBirth,
            patient.MRN,
            patient.IsActive,
            patient.Prescriptions.Select(p => new PrescriptionSummary(
                p.Id.Value,
                p.MedicationCode.Value,
                p.Dosage.ToString(),
                p.IsActive)).ToList());

        return Result.Success(response);
    }
}

// Application/Patients/Queries/GetPatient/PatientResponse.cs
public sealed record PatientResponse(
    Guid Id,
    string Name,
    DateOnly DateOfBirth,
    string MRN,
    bool IsActive,
    IReadOnlyList<PrescriptionSummary> Prescriptions);

public sealed record PrescriptionSummary(
    Guid Id,
    string MedicationCode,
    string Dosage,
    bool IsActive);

Repository Interfaces (Defined Here, Implemented in Infrastructure)

C#
// Application/Abstractions/IPatientRepository.cs
namespace SystemForge.Application.Abstractions;

public interface IPatientRepository
{
    Task<Patient?> GetByIdAsync(PatientId id, CancellationToken ct);
    Task<Patient?> GetByMRNAsync(string mrn, CancellationToken ct);
    Task<bool> ExistsByMRNAsync(string mrn, CancellationToken ct);
    Task AddAsync(Patient patient, CancellationToken ct);
    Task<IReadOnlyList<Patient>> GetActiveAsync(CancellationToken ct);
}

// Application/Abstractions/IDrugOrderRepository.cs
public interface IDrugOrderRepository
{
    Task<DrugOrder?> GetByIdAsync(DrugOrderId id, CancellationToken ct);
    Task AddAsync(DrugOrder order, CancellationToken ct);
    Task<IReadOnlyList<DrugOrder>> GetPendingOrdersAsync(CancellationToken ct);
}

// Application/Abstractions/IUnitOfWork.cs
public interface IUnitOfWork
{
    Task<int> SaveChangesAsync(CancellationToken ct);
}

Service Interfaces for External Concerns

C#
// Application/Abstractions/IEmailService.cs
public interface IEmailService
{
    Task SendAsync(string to, string subject, string body, CancellationToken ct);
}

// Application/Abstractions/IPharmacyNotificationService.cs
public interface IPharmacyNotificationService
{
    Task NotifyOrderReadyForDispensingAsync(DrugOrderId orderId, CancellationToken ct);
}

// Application/Abstractions/IAuditLog.cs
public interface IAuditLog
{
    Task LogAsync(string message, CancellationToken ct);
    Task LogUserActionAsync(string userId, string action, string entityType, string entityId, CancellationToken ct);
}

// Application/Abstractions/ICurrentUser.cs
public interface ICurrentUser
{
    string? UserId { get; }
    string? Email { get; }
    bool IsAuthenticated { get; }
}

Dependency Injection Registration

C#
// Application/DependencyInjection.cs
using Microsoft.Extensions.DependencyInjection;

namespace SystemForge.Application;

public static class DependencyInjection
{
    public static IServiceCollection AddApplication(this IServiceCollection services)
    {
        var assembly = typeof(AssemblyReference).Assembly;

        // Register all command handlers
        services.Scan(scan => scan
            .FromAssemblies(assembly)
            .AddClasses(classes => classes.Where(t =>
                t.Name.EndsWith("CommandHandler") || t.Name.EndsWith("QueryHandler")))
            .AsSelf()
            .WithScopedLifetime());

        // Register all FluentValidation validators
        services.AddValidatorsFromAssembly(assembly);

        return services;
    }
}

Folder Layout

Application/
ā”œā”€ā”€ AssemblyReference.cs
ā”œā”€ā”€ DependencyInjection.cs
ā”œā”€ā”€ Abstractions/
│   ā”œā”€ā”€ IPatientRepository.cs
│   ā”œā”€ā”€ IDrugOrderRepository.cs
│   ā”œā”€ā”€ IUnitOfWork.cs
│   ā”œā”€ā”€ IEmailService.cs
│   ā”œā”€ā”€ IPharmacyNotificationService.cs
│   └── ICurrentUser.cs
ā”œā”€ā”€ Common/
│   ā”œā”€ā”€ Result.cs
│   └── Error.cs
ā”œā”€ā”€ Patients/
│   ā”œā”€ā”€ Commands/
│   │   └── CreatePatient/
│   │       ā”œā”€ā”€ CreatePatientCommand.cs
│   │       ā”œā”€ā”€ CreatePatientCommandHandler.cs
│   │       └── CreatePatientCommandValidator.cs
│   ā”œā”€ā”€ Queries/
│   │   └── GetPatient/
│   │       ā”œā”€ā”€ GetPatientQuery.cs
│   │       ā”œā”€ā”€ GetPatientQueryHandler.cs
│   │       └── PatientResponse.cs
│   └── Events/
│       └── PatientRegisteredDomainEventHandler.cs
└── DrugOrders/
    ā”œā”€ā”€ Commands/
    │   └── ApproveOrder/
    │       ā”œā”€ā”€ ApproveOrderCommand.cs
    │       ā”œā”€ā”€ ApproveOrderCommandHandler.cs
    │       └── ApproveOrderCommandValidator.cs
    └── Events/
        └── DrugOrderApprovedDomainEventHandler.cs

Key Takeaway

The Application layer is the "what to do" layer — not the "how to do it" layer. It defines the interfaces for persistence and external services, orchestrates domain objects to fulfill use cases, and returns a Result rather than throwing exceptions. Because it only knows the Domain layer and its own interfaces, every handler can be tested by providing fake implementations of those interfaces — no database, no email server, no setup.

Enjoyed this article?

Explore the AI Systems learning path for more.

Found this helpful?

Share:š•

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.