How khiem-nguyen-ict/fhir-fullstack-architecture-playground works
1. Overall shape
- This repo is a three-tier demo stack: React frontend → GraphQL BFF → Spring Boot patient service → database.
- It is designed to show how a UI can stay decoupled from a domain service that exposes both CRUD and FHIR-shaped reads.
2. Frontend boundary
- frontend/ is a Vite + React single-page app.
- Its only backend dependency is the GraphQL endpoint exposed by the BFF; it does not call the patient service directly.
- The frontend’s responsibility is UI state, rendering, and user actions for listing/adding/deleting patients.
3. BFF boundary
- bff/ is a Node.js Apollo GraphQL gateway.
- It translates frontend operations into calls against the patient service’s REST API via patientServiceClient.js.
- schema.js defines the public GraphQL contract, while resolvers.js maps queries/mutations to backend calls.
4. Patient service boundary
- services/patient-service/ is the system of record for patient data.
- It is a Spring Boot 3 / Java 17 service with a clear internal split:
- PatientController for HTTP endpoints
- PatientRepository for persistence access
- Patient and PatientRequest for domain/input models
- FhirMapper for converting internal records to FHIR-shaped responses
- This service owns the data model and the persistence rules.
5. Persistence layer
- The patient service uses Postgres in Docker and H2 when run standalone.
- The architecture deliberately makes persistence swappable for local development versus the full stack demo.
- DataSeeder populates sample patients on first boot, which matters because the UI and smoke tests assume starter data exists.
6. FHIR-shaped API layer
- The patient service exposes two read models:
- a normal REST CRUD API under /api/patients
- a FHIR R4-shaped read API under /fhir/Patient
- The FHIR endpoint is not a full FHIR server; it is a FHIR-shaped projection of the same underlying patient records, likely wrapped as a Bundle and/or Patient resources.
7. Request flow for user actions
- A typical create/delete/list flow is:
- React UI triggers a GraphQL operation
- BFF resolver calls patient-service REST
- patient-service persists or reads from the database
- response returns back through the BFF to the browser
- This keeps the frontend insulated from REST details and lets the BFF act as an adaptation layer.
8. Direct service access paths
- The repo intentionally supports multiple access modes for the patient service:
- direct REST CRUD for operational use
- direct FHIR-shaped reads for interoperability demos
- GraphQL via BFF for the UI
- That separation is useful for comparing client-facing API styles against one shared domain service.
9. Containerization and runtime composition
- Each major piece has its own Dockerfile, and infra/docker-compose.yml wires them together.
- The compose setup includes Postgres + frontend + BFF + patient service, enabling a one-command full-circle demo.
- Port separation is part of the architecture: frontend on 5173, BFF on 4000, patient service on 8081.
10. Architecture diagram boundaries to show
- Draw three application boxes plus one database box:
- Browser/React UI
- GraphQL BFF
- Patient service
- Postgres/H2
- Show frontend only talking to BFF, BFF only talking to REST, and patient service owning persistence.
- Show a second read path from patient service to a FHIR-shaped response to emphasize interoperability without a dedicated FHIR server.
11. Operational concerns
- The patient service exposes /health, which suggests simple container/service health checking.
- The repository is optimized for local experimentation and architecture demos rather than production hardening; the clearest design goal is demonstrating layer separation, not maximizing framework complexity.
12. Key takeaway
- The architectural center of gravity is the BFF pattern: UI concerns are separated from service concerns, and the service itself provides both a conventional CRUD surface and a FHIR-like read projection over the same data.
Main entry points: PatientController.java Controller · docker-compose.yml Compose stack · PatientRepository.java Repository · PatientServiceApplication.java Service app
Components 17
Frontend
React browser UI
- React UI · Browser-facing patient list and mutation UI
frontend/src/App.jsx - GraphQL client · Browser-side GraphQL request helper
frontend/src/graphqlClient.js - App entry · Frontend startup and rendering entrypoint
frontend/src/main.jsx
BFF
GraphQL adaptation layer
- GraphQL server · Node.js GraphQL gateway exposed to the UI
bff/index.js - Schema · Public queries and mutations for the frontend
bff/schema.js - Resolvers · Maps GraphQL operations to patient-service calls
bff/resolvers.js - REST client · BFF client for the patient service REST API
bff/patientServiceClient.js
Patient Service
Spring Boot domain service
- Service app · Patient system of record runtime
services/patient-service/src/main/java/com/example/patient/PatientServiceApplication.java - Controller · CRUD and FHIR-shaped HTTP endpoints
services/patient-service/src/main/java/com/example/patient/PatientController.java - FHIR mapper · Converts patient records to FHIR-shaped responses
services/patient-service/src/main/java/com/example/patient/FhirMapper.java - Repository · Data access boundary for patients
services/patient-service/src/main/java/com/example/patient/PatientRepository.java - Patient model · Persistent patient entity
services/patient-service/src/main/java/com/example/patient/Patient.java - Request model · Create/update request payload model
services/patient-service/src/main/java/com/example/patient/PatientRequest.java - Seeder · Loads starter patients on first boot
services/patient-service/src/main/java/com/example/patient/DataSeeder.java - FHIR read · FHIR R4-shaped projection over patient records
services/patient-service/src/main/java/com/example/patient/PatientController.java
Infra
Container and persistence wiring
- Database · Swappable local or container persistence store
infra/docker-compose.yml - Compose stack · Wires frontend, BFF, service, and database together
infra/docker-compose.yml
Connections 20
- App entry → React UI: renders · Bootstraps the UI shell
- React UI → GraphQL client: queries · UI issues GraphQL operations
- GraphQL client → GraphQL server: GraphQL · Frontend talks only to the BFF endpoint
- GraphQL server → Schema: exposes · Serves the public GraphQL contract
- Schema → Resolvers: binds · Schema fields are resolved by implementation handlers
- Resolvers → REST client: calls · Resolvers translate operations into REST requests
- REST client → Controller: REST · BFF invokes the patient service API
- Controller → Repository: uses · HTTP layer delegates persistence work
- Controller → FHIR mapper: projects · FHIR endpoint uses the mapper for read projection
- Repository → Patient model: stores · Repository persists patient entities
- Controller → Request model: accepts · CRUD endpoints consume request DTOs
- Service app → Controller: hosts · Spring Boot runtime serves the HTTP layer
- Service app → Seeder: initializes · Bootstraps sample data on startup
- Seeder → Repository: seeds · Seeder populates the patient store
- Repository → Database: reads/writes · Data access goes to the underlying database
- Controller → FHIR read: serves · FHIR-shaped GETs are exposed alongside CRUD
- Compose stack → GraphQL server: runs · Compose starts the gateway service
- Compose stack → Service app: runs · Compose starts the patient service
- Compose stack → React UI: runs · Compose starts the frontend container
- Compose stack → Database: runs · Compose provides the database container