Ghasi e-Prescribing Gateway Service — Application Logic
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 03 platform-services · 02 DDD
Use Cases — Commands
| Use case | Trigger | Actor constraint | Emits event |
|---|---|---|---|
CreateMedicationRequestUseCase | POST /fhir/MedicationRequest | ehr-backend only | eprescribing.medication_request.created.v1 |
UpdateMedicationRequestUseCase | PUT /fhir/MedicationRequest/:id | ehr-backend only | eprescribing.medication_request.updated.v1 |
CancelMedicationRequestUseCase | PATCH MR status=cancelled or via Task | ehr-backend or policy workflow | eprescribing.medication_request.cancelled.v1 |
CreateMedicationDispenseUseCase | POST /fhir/MedicationDispense | pharmacy-backend only | eprescribing.medication_dispense.created.v1 |
UpdateMedicationDispenseUseCase | PUT /fhir/MedicationDispense/:id | pharmacy-backend | eprescribing.medication_dispense.updated.v1 |
RegisterSubscriptionUseCase | POST /fhir/Subscription | ehr-backend, pharmacy-backend | — |
CreateWorkflowTaskUseCase | POST /fhir/Task | pharmacy or EHR per policy | eprescribing.task.status_changed.v1 |
ReplaySubscriptionUseCase | Admin replay trigger | platform-admin | eprescribing.subscription.replayed.v1 |
Use Cases — Queries
| Use case | Endpoint | Description |
|---|---|---|
GetMedicationRequestUseCase | GET /fhir/MedicationRequest/:id | Single MR with ETag header |
SearchMedicationRequestsUseCase | GET /fhir/MedicationRequest?patient=... | Paginated search by patient, status, date |
GetMedicationDispenseUseCase | GET /fhir/MedicationDispense/:id | Single MD |
SearchMedicationDispensesUseCase | GET /fhir/MedicationDispense?request=... | Fulfillments for a prescription |
DirectorySearchUseCase | GET /fhir/Organization?name=... | Pharmacy directory search (P1) |
GetSubscriptionStatusUseCase | GET /fhir/Subscription/:id | Channel status and cursor |
Ports (Interfaces)
| Port | Direction | Adapter |
|---|---|---|
MedicationRequestRepository | Outbound | Postgres / Drizzle (gepgw_medication_requests) |
MedicationDispenseRepository | Outbound | Postgres / Drizzle (gepgw_medication_dispenses) |
SubscriptionRepository | Outbound | Postgres / Drizzle (gepgw_subscriptions) |
IdempotencyStore | Outbound | Postgres (Phase 1) / Redis (Phase 2) |
IgValidator | Outbound | Zod (Phase 1) / HAPI FHIR (Phase 2) |
PharmacyRouter | Outbound | Organization/Endpoint resolution via provider-directory-service |
SubscriptionNotifier | Outbound | HTTPS delivery with HMAC signing |
EventPublisher | Outbound | NATS JetStream outbox relay |
AuditClient | Outbound | audit-service (fire-and-forget) |
TerminologyClient | Outbound | terminology-service (ATC/RxNorm validation) |
Core Orchestration Flow — Prescribe → Route → Dispense
Idempotency Flow
- Client sends
POST /fhir/MedicationRequestwithIdempotency-Key: <uuid>. - Gateway computes
payload_fingerprint = sha256(canonicalized_body). - Checks
gepgw_idempotency_recordsfor(tenant_id, idempotency_key, payload_fingerprint). - If found and outcome
success: return stored response (same201, sameid). - If found but in
processingstate: return409 Conflict(in-flight duplicate). - If not found: proceed with create; insert idempotency record atomically.
ETag / If-Match Concurrency
- Every GET returns
ETag: W/"<sha256>"header. - Update operations require
If-Match: W/"<stored-etag>". - Mismatch returns
412 Precondition Failedwith current resource in body for reconciliation. - Client must refresh and re-submit with updated ETag.
Outbox / Event Delivery Pattern
All domain events inserted into gepgw_outbox in the same DB transaction as the resource mutation. The OutboxRelayWorker polls and publishes to NATS JetStream. The SubscriptionNotifier reads from NATS and delivers signed HTTPS payloads to registered channels. Failed deliveries enter DLQ with metadata for manual replay.
Error Handling
| Condition | HTTP | Error code |
|---|---|---|
| Wrong persona (EHR tries to write MD) | 403 | FORBIDDEN_WRITE_PERSONA |
| FHIR profile validation failed | 422 | PROFILE_VALIDATION_FAILURE + OperationOutcome |
| MR not found for MD link | 422 | PRESCRIPTION_NOT_FOUND |
| Duplicate Idempotency-Key + different payload | 409 | IDEMPOTENCY_KEY_CONFLICT |
| Stale ETag | 412 | PRECONDITION_FAILED |
| Module not licensed | 403 | MODULE_NOT_LICENSED |
| Tenant isolation violation | 403 | TENANT_ISOLATION_VIOLATION |
| IG validator unavailable (degraded) | 503 | VALIDATOR_UNAVAILABLE |
| Rate limit exceeded | 429 | RATE_LIMIT_EXCEEDED |