Skip to content

Architecture

Technical overview of FHIR Electronic Medical Records system architecture.

System Overview

graph TB
    subgraph "Presentation Layer"
        A[Web Browser]
        B[Thymeleaf Templates]
        C[JavaScript/CSS]
    end

    subgraph "Application Layer"
        D[Spring Boot 3.2]
        E[Spring MVC Controllers]
        F[Service Layer]
    end

    subgraph "FHIR Layer"
        G[HAPI FHIR Server]
        H[Resource Providers]
        I[FHIR Validators]
    end

    subgraph "Data Layer"
        J[JPA Repositories]
        K[Hibernate ORM]
        L[(PostgreSQL)]
    end

    subgraph "External Services"
        M[Clinicaltables API]
        N[SNOMED Local Library]
    end

    A --> B
    B --> E
    C --> M
    C --> N
    E --> F
    F --> G
    G --> H
    H --> J
    J --> K
    K --> L

    style D fill:#14b8a6
    style G fill:#0ea5e9
    style L fill:#8b5cf6

Technology Stack

Backend

Framework: Spring Boot 3.2.x - Dependency Injection - Auto-configuration - Embedded Tomcat - Production-ready features

FHIR: HAPI FHIR 6.x - R4 implementation - JPA server - Resource validation - Search parameters

ORM: Hibernate 6.x - Entity mapping - Lazy loading - Caching - Query optimization

Database: PostgreSQL 14+ - ACID compliance - JSON support - Full-text search - Replication

Frontend

Template Engine: Thymeleaf 3.x - Server-side rendering - Fragment support - Layout dialects - Security integration

JavaScript: - Vanilla JS (no framework) - Fetch API for AJAX - Medical autocomplete engine - SNOMED local library

CSS: - Custom modern CSS - Teal medical theme - Responsive design - Print optimization

External APIs

ICD-10 Codes: - NIH Clinicaltables API - No authentication - CORS-enabled - 200-400ms latency

SNOMED CT: - Local JavaScript library - 300+ common codes - <10ms response - Offline-capable

Project Structure

medicare-erp/
├── src/
│   ├── main/
│   │   ├── java/com/emr/fhir/
│   │   │   ├── controllers/          # MVC Controllers
│   │   │   │   ├── PatientController.java
│   │   │   │   ├── ConditionController.java
│   │   │   │   └── ...
│   │   │   ├── services/             # Business Logic
│   │   │   │   ├── PatientService.java
│   │   │   │   ├── FhirService.java
│   │   │   │   └── ...
│   │   │   ├── repositories/         # Data Access
│   │   │   │   └── (HAPI FHIR handles)
│   │   │   ├── dto/                  # Data Transfer Objects
│   │   │   │   ├── PatientDTO.java
│   │   │   │   └── ...
│   │   │   ├── config/               # Configuration
│   │   │   │   ├── FhirConfig.java
│   │   │   │   └── SecurityConfig.java
│   │   │   └── MedicareErpApplication.java
│   │   └── resources/
│   │       ├── templates/            # Thymeleaf Templates
│   │       │   ├── fragments/
│   │       │   │   ├── nav.html
│   │       │   │   ├── patient-bar.html
│   │       │   │   ├── allergy-banner.html
│   │       │   │   └── clinical-tabs.html
│   │       │   ├── patients/
│   │       │   ├── conditions/
│   │       │   ├── prescriptions/
│   │       │   └── ...
│   │       ├── static/
│   │       │   ├── css/
│   │       │   │   └── style.css
│   │       │   └── js/
│   │       │       ├── medical-autocomplete.js
│   │       │       └── snomed-codes.js
│   │       └── application.properties
│   └── test/                         # Unit Tests
└── pom.xml                           # Maven Configuration

Design Patterns

MVC (Model-View-Controller)

Controllers: Handle HTTP requests

@Controller
@RequestMapping("/patients")
public class PatientController {
    @GetMapping("/{id}")
    public String viewPatient(@PathVariable String id, Model model) {
        PatientDTO patient = patientService.getPatient(id);
        model.addAttribute("patient", patient);
        return "patients/view";
    }
}

Services: Business logic

@Service
public class PatientService {
    public PatientDTO getPatient(String id) {
        Patient fhirPatient = fhirClient.read()
            .resource(Patient.class)
            .withId(id)
            .execute();
        return convertToDTO(fhirPatient);
    }
}

Views: Thymeleaf templates

<h1 th:text="${patient.fullName}">Patient Name</h1>

Repository Pattern

HAPI FHIR provides repositories:

IGenericClient client = fhirContext.newRestfulGenericClient(serverBase);

// Create
Patient patient = new Patient();
MethodOutcome outcome = client.create()
    .resource(patient)
    .execute();

// Read
Patient patient = client.read()
    .resource(Patient.class)
    .withId("123")
    .execute();

// Search
Bundle results = client.search()
    .forResource(Patient.class)
    .where(Patient.NAME.matches().value("John"))
    .returnBundle(Bundle.class)
    .execute();

DTO Pattern

Convert between FHIR resources and application DTOs:

public PatientDTO convertToDTO(Patient fhirPatient) {
    PatientDTO dto = new PatientDTO();
    dto.setId(fhirPatient.getIdElement().getIdPart());
    dto.setFirstName(fhirPatient.getNameFirstRep().getGivenAsSingleString());
    dto.setLastName(fhirPatient.getNameFirstRep().getFamily());
    // ... more mappings
    return dto;
}

Component Interaction

Request Flow

  1. Browser → HTTP GET /patients/123
  2. Controller → Receive request, extract ID
  3. Service → Call FHIR client
  4. FHIR Client → Query HAPI FHIR server
  5. HAPI FHIR → JPA query to database
  6. Database → Return patient record
  7. Service → Convert FHIR to DTO
  8. Controller → Add to model
  9. Thymeleaf → Render template
  10. Browser → Display HTML

Autocomplete Flow

  1. User types → "diab"
  2. JavaScript → Waits 300ms (debounce)
  3. Fetch API → GET to Clinicaltables
  4. API → Returns ICD-10 matches
  5. JavaScript → Displays dropdown
  6. User selects → E11.9
  7. JavaScript → Auto-fills code field
  8. Form submit → POST to controller
  9. Service → Creates FHIR Condition
  10. HAPI FHIR → Saves to database

Data Flow

FHIR Resource Creation

sequenceDiagram
    User->>Browser: Fill form
    Browser->>Controller: POST /conditions/save
    Controller->>Service: save(ConditionDTO)
    Service->>FHIR Client: create Condition
    FHIR Client->>HAPI Server: POST /Condition
    HAPI Server->>Validator: validate(Condition)
    Validator->>HAPI Server: valid
    HAPI Server->>JPA: persist(Condition)
    JPA->>PostgreSQL: INSERT
    PostgreSQL->>JPA: success
    JPA->>HAPI Server: Condition ID
    HAPI Server->>Service: MethodOutcome
    Service->>Controller: success
    Controller->>Browser: redirect
    Browser->>User: Show success

Configuration

Application Properties

# Server
server.port=8080

# Database
spring.datasource.url=jdbc:postgresql://localhost:5432/medicare_erp
spring.datasource.username=medicare_user
spring.datasource.password=secure_password

# JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

# HAPI FHIR
hapi.fhir.server_address=http://localhost:8080/api/fhir
hapi.fhir.default_page_size=20
hapi.fhir.max_page_size=100

# Logging
logging.level.ca.uhn.fhir=INFO
logging.level.com.emr.fhir=DEBUG

Performance Considerations

Database Optimization

  • Connection pooling: HikariCP
  • Index strategy: On search fields
  • Lazy loading: For related entities
  • Query optimization: JPA criteria queries

Caching

  • HTTP caching: ETag support
  • Application caching: Spring Cache
  • Autocomplete caching: Browser localStorage

Scalability

  • Horizontal scaling: Stateless application
  • Database replication: Read replicas
  • Load balancing: Nginx/HAProxy
  • CDN: Static assets

Security Architecture

Authentication

  • Spring Security
  • Session-based auth
  • Role-based access control (RBAC)

Authorization

Roles: - ADMIN - Full access - DOCTOR - Clinical records - NURSE - Limited clinical access - STAFF - Demographics only

HTTPS

Production deployment:

Browser → HTTPS → Load Balancer → HTTP → App Server

Next Steps