Architecture
Ignixa follows Clean Architecture principles with strict layer separation, ensuring maintainability and testability.
Layer Overview
┌─────────────────────────────────────────────────────────────┐
│ API Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Endpoints │ │ Middleware │ │ Request Pipeline │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Application Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Queries │ │ Commands │ │ Handlers │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Domain Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Entities │ │ Interfaces │ │ Value Objects │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Data Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ SQL Server │ │ FileSystem │ │ Blob Storage │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Core SDK (Shared) │
│ ┌──────────────┐ ┌─────────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Abstractions │ │Serialization│ │ FHIRPath │ │Validation│ │
│ └──────────────┘ └─────────────┘ └──────────┘ └─────────┘ │
│ ┌──────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │Specification │ │ Search │ │FhirFakes │ │
│ └──────────────┘ └─────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
The Core SDK packages are shared building blocks used across all layers. They provide:
- Abstractions: Core interfaces (
ISourceNode,IElement) - Serialization: JSON parsing and writing
- FHIRPath: Expression evaluation
- Validation: Three-tier validation engine
- Search: Parameter indexing and extraction
- Specification: FHIR structure definitions
CQRS Pattern
Ignixa uses Command Query Responsibility Segregation (CQRS) via the Medino library.
Query Example
// Query definition
public record GetPatientQuery(string Id) : IRequest<FhirResponse>;
// Handler implementation
public class GetPatientHandler(IFhirRepository repository)
: IRequestHandler<GetPatientQuery, FhirResponse>
{
public async Task<FhirResponse> HandleAsync(
GetPatientQuery request,
CancellationToken cancellationToken)
{
var patient = await repository.ReadAsync(
"Patient",
request.Id,
cancellationToken);
return patient is not null
? FhirResponse.Ok(patient)
: FhirResponse.NotFound();
}
}
Command Example
// Command definition
public record CreatePatientCommand(ISourceNode Resource) : IRequest<FhirResponse>;
// Handler with validation
public class CreatePatientHandler(
IFhirRepository repository,
IValidator validator)
: IRequestHandler<CreatePatientCommand, FhirResponse>
{
public async Task<FhirResponse> HandleAsync(
CreatePatientCommand request,
CancellationToken cancellationToken)
{
var outcome = await validator.ValidateAsync(request.Resource);
if (!outcome.Success)
{
return FhirResponse.BadRequest(outcome);
}
var result = await repository.CreateAsync(
request.Resource,
cancellationToken);
return FhirResponse.Created(result);
}
}
API Layer
The API layer uses ASP.NET Core Minimal APIs for low overhead:
public static class PatientEndpoints
{
public static IEndpointRouteBuilder MapPatientEndpoints(
this IEndpointRouteBuilder endpoints)
{
endpoints.MapGet("/Patient/{id}", GetPatient);
endpoints.MapPost("/Patient", CreatePatient);
endpoints.MapPut("/Patient/{id}", UpdatePatient);
endpoints.MapDelete("/Patient/{id}", DeletePatient);
return endpoints;
}
private static async Task<IResult> GetPatient(
string id,
IMediator mediator,
CancellationToken cancellationToken)
{
var result = await mediator.SendAsync(
new GetPatientQuery(id),
cancellationToken);
return result.ToHttpResult();
}
}
Dependency Rules
Strict dependency rules ensure clean separation:
✅ API → Application → Domain
✅ DataLayer → Domain
❌ Domain → Application (violation)
❌ Application → DataLayer (use interfaces)
Allowed Dependencies
| Layer | Can Reference |
|---|---|
| API | Application, Domain |
| Application | Domain |
| Domain | Nothing (pure) |
| DataLayer | Domain (implements interfaces) |
FHIR Data Flow
Request processing flow:
HTTP Request
│
▼
┌─────────────┐
│ Endpoint │ Parse request, extract parameters
└──────┬──────┘
│
▼
┌─────────────┐
│ Query/ │ Create immutable request object
│ Command │
└──────┬──────┘
│
▼
┌─────────────┐
│ Handler │ Execute business logic
└──────┬──────┘
│
▼
┌─────────────┐
│ Repository │ Data access via interface
└──────┬──────┘
│
▼
┌─────────────┐
│ DataLayer │ Concrete storage implementation
└─────────────┘
Multi-Tenancy Architecture
See Multi-Tenancy for tenant isolation details.
┌──────────────────────────────────────────┐
│ Request Pipeline │
├──────────────────────────────────────────┤
│ Tenant Resolution Middleware │
│ Extract tenant from /tenant/{id}/... │
├────────────────┬────────────────┬────────┤
│ Tenant 1 │ Tenant 2 │ ... │
│ (Partition) │ (Partition) │ │
└────────────────┴────────────────┴────────┘