Registration Service — Application Logic
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · 03 platform-services · 02 DDD
1. Commands (write use cases)
| Use Case | Command DTO | Ports touched | Emits event |
|---|---|---|---|
RegisterPatientUseCase | RegisterPatientCommand | PatientRepository, MpiPort, EventPublisher, AuditClient, IdempotencyStore | patient.created |
UpdatePatientUseCase | UpdatePatientCommand | PatientRepository, EventPublisher, AuditClient | patient.updated |
RecordVitalStatusUseCase | RecordVitalStatusCommand | PatientRepository, EventPublisher, AuditClient | patient.vital-status-changed, patient.updated |
MergePatientUseCase | MergePatientCommand | PatientRepository, EventPublisher, AuditClient | patient.merged |
UnmergePatientUseCase | UnmergePatientCommand | PatientRepository, EventPublisher, AuditClient | patient.unmerged |
RegisterEncounterUseCase | RegisterEncounterCommand | EncounterRepository, PatientRepository, EventPublisher, AuditClient | encounter.registered |
TransitionEncounterStatusUseCase | TransitionEncounterStatusCommand | EncounterRepository, EventPublisher, AuditClient | encounter.status-changed |
UploadPortraitUseCase | UploadPortraitCommand | PatientRepository, PortraitStore, AuditClient | — |
SaveExtensionInstanceUseCase | SaveExtensionInstanceCommand | PatientRepository, ExtensionSchemaRegistry, AuditClient | — |
2. Queries (read use cases)
| Use Case | Query DTO | Notes |
|---|---|---|
SearchPatientsUseCase | SearchPatientsQuery | Minimum-necessary criteria enforced; break-glass header audited |
GetPatientByIdUseCase | GetPatientQuery | Optional includeIncomingRelatedLinks |
GetUnidentifiedReconciliationQueueUseCase | UnidentifiedQueueQuery | Optional SLA breach filter |
GetPatientForecastUseCase | — | Delegated to immunization-service; not owned here |
GetEncounterUseCase | GetEncounterQuery | — |
ListEncountersUseCase | ListEncountersQuery | Filter by patientId, status |
GetPortraitUseCase | GetPortraitQuery | Returns raw bytes; consent/retention check |
GetPortraitHistoryUseCase | — | Metadata list newest-first |
GetExtensionInstancesUseCase | GetExtensionsQuery | Privacy masking applied by role |
GetKinRelationshipCodesUseCase | — | Catalog read |
3. Orchestration Flows
3.1 Patient Registration Flow
3.2 Merge Flow
3.3 Encounter Status Transition Flow
4. Ports (Interfaces)
| Port | Direction | Description |
|---|---|---|
PatientRepository | Outbound | CRUD + search + MRN generation for patients |
EncounterRepository | Outbound | CRUD for encounters |
MpiPort | Outbound | Probabilistic duplicate scoring |
EventPublisher | Outbound | NATS JetStream CloudEvents publication |
AuditClient | Outbound | Emit structured audit events |
IdempotencyStore | Outbound | Cache idempotency key → response (Redis TTL 24h) |
PortraitStore | Outbound | Binary media storage (object store) |
ExtensionSchemaRegistry | Outbound | Retrieve and validate active JSON Schemas for bundles |
KinRelationshipCatalog | Outbound | Read relationship codes from kin_relationship_types |
5. Outbox / Saga Patterns
| Pattern | Where used | Details |
|---|---|---|
| Outbox | All event-emitting use cases | Events written to outbox table in same DB transaction; relay publishes to NATS |
| Idempotent create | RegisterPatientUseCase | Idempotency-Key / clientMutationId cached for 24h in Redis |
| Optimistic lock | Patient + Encounter mutations | version field incremented on save; 409 on mismatch |
| Fail-open CDS | Not applicable (CDS owned by orders-service) | — |
6. Error Handling
| Error code | HTTP | When |
|---|---|---|
DUPLICATE_DETECTED | 409 | MPI ≥ 85 on normal create |
IDENTIFIER_ALREADY_ASSIGNED | 409 | Same (system, value) exists on another patient |
DUPLICATE_IDENTIFIER_IN_REQUEST | 400 | Duplicate (system, value) within one payload |
INVALID_NATIONAL_IDENTIFIER | 400 | National ID fails digit-length validation |
MISSING_MANDATORY_FIELD | 400 | Tenant required field list not satisfied |
PATIENT_NOT_FOUND | 404 | Patient missing or cross-tenant |
ENCOUNTER_NOT_FOUND | 404 | Encounter missing or cross-tenant |
OPTIMISTIC_LOCK_CONFLICT | 409 | version mismatch |
INVALID_STATUS_TRANSITION | 409 | Encounter transition not in allowed graph |
MERGE_SELF | 409 | Survivor equals source |
UNMERGE_DISABLED_BY_POLICY | 403 | REGISTRATION_UNMERGE_ENABLED not true |
UNMERGE_INVALID_STATE | 400 | Source not recorded as merged into this survivor |
PORTRAIT_CONSENT_REQUIRED | 400 | Policy requires consent; consentGiven false |
EXTENSION_PAYLOAD_INVALID | 400 | JSON Schema validation failure |
PATIENT_SEARCH_INSUFFICIENT_CRITERIA | 400 | Minimum-necessary criteria not met |
RELATIONSHIP_CODE_INVALID | 400 | NOK relationship code not in catalog |