Publisert - 07.05.2026

Architecture

Overview

Nompd API is a data transformation and distribution layer. It fetches FHIR-structured healthcare data from upstream APIs, converts it into simplified REST models, and exposes it to consumers with versioned diff support.

┌─────────────────────────────────┐
│  Upstream FHIR APIs             │
│  (legemidler-api-test)          │
│  - PlanDefinition               │
│  - ActivityDefinition           │
│  - RegulatedAuthorization       │
└──────────────┬──────────────────┘
               │  POST /api/internal/sync
               │  (triggered by K8s CronJob)
               ▼
┌──────────────────────────────────┐
│  SyncController                  │
│    └─ SyncService                │
│         ├─ UpstreamApiClient     │  Fetch raw FHIR data
│         ├─ VersionService        │  Increment global version
│         ├─ AppDbContext          │  Upsert raw FHIR (versioned)
│         ├─ ConversionService     │  Map FHIR → Output models
│         └─ AppDbContext          │  Upsert output + write history
└──────────────┬───────────────────┘
               │
               ▼
┌──────────────────────────────────────┐
│  MongoDB (all VersionedDocument<T>)  │
│  Raw FHIR:                           │
│   - PlanDefinitions                  │
│   - ActivityDefinitions              │
│   - RegulatedAuthorizations          │
│  Converted output:                   │
│   - TreatmentGroups                  │
│   - ReimbursementGroups              │
│  Internal:                           │
│   - SyncMetadata (global counter)    │
│   - SyncHistory (one per sync run)   │
└──────────────┬───────────────────────┘
               │
               ▼
┌──────────────────────────────────┐
│  Output Controllers              │
│  - TreatmentGroupController      │
│  - ReimbursementGroupController  │
│                                  │
│  GET /api/v1/treatmentGroup      │
│  GET /api/v1/reimbursementGroup  │
│  (with optional ?sinceVersion=N) │
└──────────────────────────────────┘
               │
               ▼
         API consumers

Directory Structure

Api/
├── Controllers/          API endpoints
│   ├── SyncController              POST /api/internal/sync
│   ├── TreatmentGroupController     GET /api/v1/treatmentGroup
│   └── ReimbursementGroupController  GET /api/v1/reimbursementGroup
├── Data/
│   └── AppDbContext                 MongoDB collection accessors + indexes
├── Middleware/
│   └── ApiKeyAuthorizationFilter    X-API-KEY header validation
├── Models/
│   ├── Fhir/             Upstream FHIR models (deserialized from source APIs)
│   ├── Output/           Converted models (served to consumers)
│   └── Internal/         MongoDB wrapper types (versioning, sync metadata)
├── Services/
│   ├── ConversionService            FHIR → Output mapping (pure, no I/O)
│   ├── SyncService                  Orchestrates fetch → convert → store
│   ├── UpstreamApiClient            HTTP client for upstream FHIR APIs
│   └── VersionService               Atomic version counter (MongoDB)
├── Program.cs                       DI registration and startup
├── appsettings.json                 Base configuration
└── appsettings.Development.json     Dev overrides (API keys)

Separation of Concerns

Layer Responsibility I/O
UpstreamApiClient Fetch raw FHIR data from external APIs HTTP
ConversionService Transform FHIR models to output models None (pure)
VersionService Manage global version counter MongoDB
SyncService Orchestrate the full sync pipeline All (coordinates above)
AppDbContext Provide typed MongoDB collection access MongoDB
ApiKeyAuthorizationFilter Validate API keys on incoming requests Configuration
Output Controllers Query versioned data and return to clients MongoDB (read-only)

Key Design Decisions

  1. Stateless versioning — The server does not track per-client state. Clients are responsible for storing the currentVersion from responses and sending it back via ?sinceVersion=N.

  2. Soft deletes — When upstream data disappears, records are marked IsDeleted = true with the current version number rather than physically removed. This ensures diff consumers see the deletion.

  3. Sync via HTTP endpoint — Instead of a BackgroundService, sync is triggered by a Kubernetes CronJob calling POST /api/internal/sync. This provides retry policies, logging, and alerting via K8s, and allows manual triggering for debugging.

  4. ConversionService is pure — No injected dependencies, no database access. This makes it trivially testable and ensures the mapping logic is isolated from infrastructure concerns.

  5. Generic VersionedDocument — A single wrapper type handles versioning for any model (raw or converted), avoiding duplication of version/delete tracking logic.

  6. Content-hash-based change detection — Each VersionedDocument<T> stores a SHA256 hash of its BSON content. On sync, only records whose hash actually changed (or whose deletion state flipped) get a new version. This is what makes the version-based diff API meaningful: clients calling ?sinceVersion=N receive only the records that genuinely changed, not every record after every sync.

  7. Raw FHIR persistence — Upstream FHIR resources are stored alongside their converted output, sharing the same version number per sync. This enables re-conversion if mapping logic changes, debugging by comparing raw vs converted, and audit traceability back to the original source.

  8. Sync history — Every sync run produces a SyncHistoryEntry with timestamps, durations, per-collection counts, and the business keys of each affected record. This provides durable audit trail and answers "what changed in sync N?" without inspecting the data collections directly.

Søk i Utviklerportalen

Søket er fullført!