hapi-fhir-validation

Thư viện hapi-fhir-validation là một thành phần quan trọng trong hệ sinh thái HAPI FHIR, cung cấp các cơ chế toàn diện để kiểm tra tính hợp lệ (validation) của FHIR resources. Validation là một quá trình thiết yếu trong các ứng dụng y tế, đảm bảo rằng dữ liệu tuân thủ theo đặc tả FHIR và các ràng buộc bổ sung được định nghĩa trong profiles.

Thư viện này được xây dựng trên nền tảng hapi-fhir-base và hoạt động với tất cả các phiên bản FHIR (DSTU2, DSTU3, R4, R5), cung cấp các API mạnh mẽ để thực hiện validation ở nhiều cấp độ khác nhau, từ kiểm tra cấu trúc cơ bản đến validation phức tạp dựa trên terminologies và profiles.

Đặc điểm chính

1. Validation dựa trên StructureDefinition profiles

FHIR Profiles (StructureDefinitions) cho phép định nghĩa các ràng buộc và mở rộng đối với các FHIR resources. Thư viện hapi-fhir-validation cung cấp khả năng validation resources dựa trên các profiles này:

  • Kiểm tra tuân thủ với các ràng buộc cardinality (số lượng phần tử tối thiểu/tối đa)

  • Kiểm tra các ràng buộc về kiểu dữ liệu

  • Kiểm tra các invariants (ràng buộc phức tạp thông qua FHIRPath)

  • Hỗ trợ multiple profiles cho một resource

2. Validation terminologies (ValueSet, CodeSystem)

Một phần quan trọng của FHIR là sử dụng các bộ mã chuẩn (terminologies) như LOINC, SNOMED CT, ICD-10:

  • Kiểm tra tính hợp lệ của mã trong CodeSystem

  • Kiểm tra mã có thuộc ValueSet được chỉ định không

  • Kiểm tra binding strength (required, extensible, preferred, example)

  • Hỗ trợ kiểm tra hierarchical code systems (như SNOMED CT)

3. FhirValidator và ValidationResult APIs

Thư viện cung cấp API trực quan và dễ sử dụng:

  • FhirValidator: Interface chính để thực hiện validation

  • ValidationResult: Đối tượng chứa kết quả validation chi tiết

  • SingleValidationMessage: Thông tin về từng lỗi/cảnh báo

  • Hỗ trợ nhiều cấp độ severity (INFORMATION, WARNING, ERROR, FATAL)

4. Modules validation mở rộng

Thư viện được thiết kế theo kiến trúc module, cho phép sử dụng nhiều validator khác nhau:

  • SchemaBaseValidator: Kiểm tra tuân thủ XML Schema

  • SchematronBaseValidator: Kiểm tra tuân thủ Schematron rules

  • FhirInstanceValidator: Module validation chính, dựa trên StructureDefinition

5. Tích hợp với FHIR profiles validation

Hỗ trợ đầy đủ các tính năng của FHIR profiles:

  • Validation dựa trên snapshot và differential views

  • Hỗ trợ slicing (phân chia phần tử theo quy tắc)

  • Hỗ trợ constraints kế thừa

  • Validation extensions

Cài đặt và sử dụng

Thêm vào Maven Project

<!-- Thư viện cơ sở - luôn cần thiết -->
<dependency>
    <groupId>ca.uhn.hapi.fhir</groupId>
    <artifactId>hapi-fhir-base</artifactId>
    <version>6.4.0</version>
</dependency>

<!-- Thư viện validation -->
<dependency>
    <groupId>ca.uhn.hapi.fhir</groupId>
    <artifactId>hapi-fhir-validation</artifactId>
    <version>6.4.0</version>
</dependency>

<!-- Structures cho phiên bản FHIR bạn đang sử dụng (ví dụ R5) -->
<dependency>
    <groupId>ca.uhn.hapi.fhir</groupId>
    <artifactId>hapi-fhir-structures-r5</artifactId>
    <version>6.4.0</version>
</dependency>

<!-- Tài nguyên validation -->
<dependency>
    <groupId>ca.uhn.hapi.fhir</groupId>
    <artifactId>hapi-fhir-validation-resources-r5</artifactId>
    <version>6.4.0</version>
</dependency>

Thêm vào Gradle Project

implementation 'ca.uhn.hapi.fhir:hapi-fhir-base:6.4.0'
implementation 'ca.uhn.hapi.fhir:hapi-fhir-validation:6.4.0'
implementation 'ca.uhn.hapi.fhir:hapi-fhir-structures-r5:6.4.0'
implementation 'ca.uhn.hapi.fhir:hapi-fhir-validation-resources-r5:6.4.0'

Ví dụ sử dụng hapi-fhir-validation

Ví dụ 1: Validation cơ bản

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.r5.model.Patient;

public class BasicValidationExample {
    public static void main(String[] args) {
        // Khởi tạo FhirContext
        FhirContext ctx = FhirContext.forR5();
        
        // Tạo validator
        FhirValidator validator = ctx.newValidator();
        
        // Tạo resource hợp lệ
        Patient patient = new Patient();
        patient.addName().setFamily("Nguyễn").addGiven("Văn A");
        
        // Thực hiện validation
        ValidationResult result = validator.validateWithResult(patient);
        
        // Kiểm tra kết quả
        System.out.println("Validation successful: " + result.isSuccessful());
        System.out.println("Issues found: " + result.getMessages().size());
        
        // In ra chi tiết các vấn đề
        result.getMessages().forEach(message -> {
            System.out.println("Location: " + message.getLocationString());
            System.out.println("Severity: " + message.getSeverity());
            System.out.println("Message: " + message.getMessage());
            System.out.println();
        });
    }
}

Ví dụ 2: Sử dụng FhirInstanceValidator

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator;

public class InstanceValidatorExample {
    public static void main(String[] args) {
        // Khởi tạo FhirContext
        FhirContext ctx = FhirContext.forR5();
        
        // Tạo validator
        FhirValidator validator = ctx.newValidator();
        
        // Thêm instance validator
        validator.registerValidatorModule(new FhirInstanceValidator(ctx));
        
        // Tạo patient không hợp lệ (thiếu giá trị bắt buộc)
        Patient invalidPatient = new Patient();
        invalidPatient.setGender(Enumerations.AdministrativeGender.OTHER);
        // Không thêm name (bắt buộc với nhiều profile)
        
        // Thực hiện validation
        ValidationResult result = validator.validateWithResult(invalidPatient);
        
        // Kiểm tra kết quả
        System.out.println("Validation successful: " + result.isSuccessful());
        System.out.println("Issues found: " + result.getMessages().size());
        
        // In ra chi tiết các vấn đề
        result.getMessages().forEach(message -> {
            System.out.println("Location: " + message.getLocationString());
            System.out.println("Severity: " + message.getSeverity());
            System.out.println("Message: " + message.getMessage());
            System.out.println();
        });
    }
}

Ví dụ 3: Validation dựa trên profile

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.common.hapi.validation.support.*;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r5.model.*;

public class ProfileValidationExample {
    public static void main(String[] args) {
        // Khởi tạo FhirContext
        FhirContext ctx = FhirContext.forR5();
        
        // Tạo ValidationSupport
        // 1. Default validation (mặc định từ FHIR)
        DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(ctx);
        
        // 2. Nạp tài nguyên từ JAR
        PrePopulatedValidationSupport prePopulatedSupport = new PrePopulatedValidationSupport(ctx);
        
        // 3. Nạp InMemoryTerminologyServer
        InMemoryTerminologyServerValidationSupport terminologySupport = new InMemoryTerminologyServerValidationSupport(ctx);
        
        // 4. Repository cho các tài nguyên validation
        SnapshotGeneratingValidationSupport snapshotSupport = new SnapshotGeneratingValidationSupport(ctx);
        
        // 5. Kết hợp các validation support
        ValidationSupportChain validationChain = new ValidationSupportChain(
            defaultSupport, 
            prePopulatedSupport, 
            terminologySupport, 
            snapshotSupport);
        
        // Tạo FhirInstanceValidator
        FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx);
        instanceValidator.setValidationSupport(validationChain);
        
        // Tạo validator và đăng ký module
        FhirValidator validator = ctx.newValidator();
        validator.registerValidatorModule(instanceValidator);
        
        // Tạo Patient với profile
        Patient patient = new Patient();
        patient.getMeta().addProfile("http://hl7.org/fhir/StructureDefinition/Patient");
        patient.addName().setFamily("Nguyễn").addGiven("Văn A");
        patient.setGender(Enumerations.AdministrativeGender.MALE);
        
        // Thêm identifier không đúng với profile
        Identifier identifier = patient.addIdentifier();
        identifier.setSystem("invalid-system");
        identifier.setValue("12345");
        
        // Thực hiện validation
        ValidationResult result = validator.validateWithResult(patient);
        
        // Kiểm tra kết quả
        System.out.println("Validation successful: " + result.isSuccessful());
        System.out.println("Issues found: " + result.getMessages().size());
        
        // In ra chi tiết các vấn đề
        result.getMessages().forEach(message -> {
            System.out.println("Location: " + message.getLocationString());
            System.out.println("Severity: " + message.getSeverity());
            System.out.println("Message: " + message.getMessage());
            System.out.println();
        });
    }
}

Ví dụ 4: Validation terminology

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.common.hapi.validation.support.*;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r5.model.*;

public class TerminologyValidationExample {
    public static void main(String[] args) {
        // Khởi tạo FhirContext
        FhirContext ctx = FhirContext.forR5();
        
        // Thiết lập validation support chain với terminology server
        ValidationSupportChain validationSupportChain = new ValidationSupportChain(
            new DefaultProfileValidationSupport(ctx),
            new InMemoryTerminologyServerValidationSupport(ctx),
            new CommonCodeSystemsTerminologyService(ctx)
        );
        
        // Tạo instance validator với validation support
        FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx);
        instanceValidator.setValidationSupport(validationSupportChain);
        
        // Tạo validator và đăng ký module
        FhirValidator validator = ctx.newValidator();
        validator.registerValidatorModule(instanceValidator);
        
        // Tạo Observation với coding hợp lệ
        Observation observation = new Observation();
        observation.setStatus(Observation.ObservationStatus.FINAL);
        
        // Thêm code từ LOINC
        CodeableConcept code = observation.getCode();
        code.addCoding()
            .setSystem("http://loinc.org")
            .setCode("8867-4")
            .setDisplay("Heart rate");
        
        // Thêm category với coding hợp lệ
        CodeableConcept category = observation.addCategory();
        category.addCoding()
            .setSystem("http://terminology.hl7.org/CodeSystem/observation-category")
            .setCode("vital-signs")
            .setDisplay("Vital Signs");
        
        // Thêm coding không hợp lệ để minh họa lỗi
        CodeableConcept invalidCategory = observation.addCategory();
        invalidCategory.addCoding()
            .setSystem("http://terminology.hl7.org/CodeSystem/observation-category")
            .setCode("INVALID-CODE")
            .setDisplay("Invalid Code");
        
        // Thực hiện validation
        ValidationResult result = validator.validateWithResult(observation);
        
        // Kiểm tra kết quả
        System.out.println("Validation successful: " + result.isSuccessful());
        System.out.println("Issues found: " + result.getMessages().size());
        
        // In ra chi tiết các vấn đề
        result.getMessages().forEach(message -> {
            System.out.println("Location: " + message.getLocationString());
            System.out.println("Severity: " + message.getSeverity());
            System.out.println("Message: " + message.getMessage());
            System.out.println();
        });
    }
}

Ví dụ 5: Validation custom profile

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.common.hapi.validation.support.*;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r5.model.*;

import java.io.FileReader;

public class CustomProfileValidationExample {
    public static void main(String[] args) throws Exception {
        // Khởi tạo FhirContext
        FhirContext ctx = FhirContext.forR5();
        
        // Thiết lập các validation support
        DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(ctx);
        
        // Support để lưu trữ profile tùy chỉnh
        PrePopulatedValidationSupport prePopulatedSupport = new PrePopulatedValidationSupport(ctx);
        
        // Nạp custom profile từ file JSON
        String profileJson = // đọc từ file hoặc resource
            "{\n" +
            "  \"resourceType\": \"StructureDefinition\",\n" +
            "  \"id\": \"patient-vn\",\n" +
            "  \"url\": \"http://example.org/fhir/StructureDefinition/patient-vn\",\n" +
            "  \"name\": \"PatientVN\",\n" +
            "  \"status\": \"active\",\n" +
            "  \"fhirVersion\": \"5.0.0\",\n" +
            "  \"kind\": \"resource\",\n" +
            "  \"abstract\": false,\n" +
            "  \"type\": \"Patient\",\n" +
            "  \"baseDefinition\": \"http://hl7.org/fhir/StructureDefinition/Patient\",\n" +
            "  \"derivation\": \"constraint\",\n" +
            "  \"differential\": {\n" +
            "    \"element\": [\n" +
            "      {\n" +
            "        \"id\": \"Patient\",\n" +
            "        \"path\": \"Patient\"\n" +
            "      },\n" +
            "      {\n" +
            "        \"id\": \"Patient.identifier\",\n" +
            "        \"path\": \"Patient.identifier\",\n" +
            "        \"min\": 1\n" +
            "      },\n" +
            "      {\n" +
            "        \"id\": \"Patient.name\",\n" +
            "        \"path\": \"Patient.name\",\n" +
            "        \"min\": 1\n" +
            "      },\n" +
            "      {\n" +
            "        \"id\": \"Patient.name.family\",\n" +
            "        \"path\": \"Patient.name.family\",\n" +
            "        \"min\": 1\n" +
            "      },\n" +
            "      {\n" +
            "        \"id\": \"Patient.name.given\",\n" +
            "        \"path\": \"Patient.name.given\",\n" +
            "        \"min\": 1\n" +
            "      },\n" +
            "      {\n" +
            "        \"id\": \"Patient.gender\",\n" +
            "        \"path\": \"Patient.gender\",\n" +
            "        \"min\": 1\n" +
            "      }\n" +
            "    ]\n" +
            "  }\n" +
            "}";
        
        // Parse profile và thêm vào PrePopulatedValidationSupport
        StructureDefinition profile = (StructureDefinition) ctx.newJsonParser().parseResource(profileJson);
        prePopulatedSupport.addStructureDefinition(profile);
        
        // Tạo ValidationSupportChain
        ValidationSupportChain validationSupportChain = new ValidationSupportChain(
            defaultSupport,
            prePopulatedSupport,
            new InMemoryTerminologyServerValidationSupport(ctx),
            new SnapshotGeneratingValidationSupport(ctx)
        );
        
        // Tạo instance validator
        FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx);
        instanceValidator.setValidationSupport(validationSupportChain);
        
        // Tạo validator và đăng ký module
        FhirValidator validator = ctx.newValidator();
        validator.registerValidatorModule(instanceValidator);
        
        // Tạo Patient hợp lệ theo profile
        Patient validPatient = new Patient();
        validPatient.getMeta().addProfile("http://example.org/fhir/StructureDefinition/patient-vn");
        validPatient.addIdentifier()
            .setSystem("http://hospital.example.org/patients")
            .setValue("12345");
        validPatient.addName()
            .setFamily("Nguyễn")
            .addGiven("Văn A");
        validPatient.setGender(Enumerations.AdministrativeGender.MALE);
        
        // Validate Patient hợp lệ
        ValidationResult validResult = validator.validateWithResult(validPatient);
        
        System.out.println("Valid Patient Validation:");
        System.out.println("Is successful: " + validResult.isSuccessful());
        validResult.getMessages().forEach(message -> 
            System.out.println(" - " + message.getSeverity() + ": " + message.getMessage()));
        
        // Tạo Patient không hợp lệ theo profile (thiếu identifier bắt buộc)
        Patient invalidPatient = new Patient();
        invalidPatient.getMeta().addProfile("http://example.org/fhir/StructureDefinition/patient-vn");
        invalidPatient.addName()
            .setFamily("Nguyễn")
            .addGiven("Văn A");
        invalidPatient.setGender(Enumerations.AdministrativeGender.MALE);
        // Không thêm identifier mặc dù bắt buộc trong profile
        
        // Validate Patient không hợp lệ
        ValidationResult invalidResult = validator.validateWithResult(invalidPatient);
        
        System.out.println("\nInvalid Patient Validation:");
        System.out.println("Is successful: " + invalidResult.isSuccessful());
        invalidResult.getMessages().forEach(message -> 
            System.out.println(" - " + message.getSeverity() + ": " + message.getMessage()));
    }
}

Kiến trúc của thư viện hapi-fhir-validation

Thư viện hapi-fhir-validation được tổ chức theo kiến trúc module, với các thành phần chính sau:

1. FhirValidator

Interface chính cung cấp các phương thức để thực hiện validation:

ValidationResult validateWithResult(IBaseResource resource);
ValidationResult validateWithResult(String input, FhirFormat format);

2. IValidatorModule

Interface cho các module validator. HAPI FHIR cung cấp các triển khai:

  • SchemaBaseValidator: Kiểm tra tuân thủ XML Schema

  • SchematronBaseValidator: Kiểm tra tuân thủ Schematron rules

  • FhirInstanceValidator: Module validator chính, dựa trên StructureDefinition và terminologies

3. ValidationSupportChain

Cung cấp khả năng kết hợp nhiều IValidationSupport, với các triển khai phổ biến:

  • DefaultProfileValidationSupport: Hỗ trợ validation với profiles mặc định của FHIR

  • PrePopulatedValidationSupport: Lưu trữ và cung cấp các tài nguyên validation

  • InMemoryTerminologyServerValidationSupport: Cung cấp terminology server trong bộ nhớ

  • CachingValidationSupport: Caching để cải thiện hiệu suất

  • SnapshotGeneratingValidationSupport: Tạo snapshot từ differential definitions

4. ValidationResult và SingleValidationMessage

Cấu trúc để lưu trữ kết quả validation:

  • ValidationResult: Chứa danh sách các messages và phương thức kiểm tra kết quả

  • SingleValidationMessage: Thông điệp validation cụ thể (location, severity, message)

Tích hợp với Spring Boot

Để tích hợp hapi-fhir-validation với Spring Boot, bạn có thể tạo một Bean configuration như sau:

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.validation.FhirValidator;
import org.hl7.fhir.common.hapi.validation.support.*;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FhirValidationConfig {
    
    @Bean
    public FhirContext fhirContext() {
        return FhirContext.forR5();
    }
    
    @Bean
    public DefaultProfileValidationSupport defaultProfileValidationSupport(FhirContext fhirContext) {
        return new DefaultProfileValidationSupport(fhirContext);
    }
    
    @Bean
    public InMemoryTerminologyServerValidationSupport terminologyServerValidationSupport(FhirContext fhirContext) {
        return new InMemoryTerminologyServerValidationSupport(fhirContext);
    }
    
    @Bean
    public PrePopulatedValidationSupport prePopulatedValidationSupport(FhirContext fhirContext) {
        return new PrePopulatedValidationSupport(fhirContext);
    }
    
    @Bean
    public SnapshotGeneratingValidationSupport snapshotGeneratingValidationSupport(FhirContext fhirContext) {
        return new SnapshotGeneratingValidationSupport(fhirContext);
    }
    
    @Bean
    public ValidationSupportChain validationSupportChain(
            DefaultProfileValidationSupport defaultSupport,
            InMemoryTerminologyServerValidationSupport terminologySupport,
            PrePopulatedValidationSupport prePopulatedSupport,
            SnapshotGeneratingValidationSupport snapshotSupport) {
        
        return new ValidationSupportChain(
            defaultSupport,
            terminologySupport,
            prePopulatedSupport,
            snapshotSupport
        );
    }
    
    @Bean
    public FhirInstanceValidator fhirInstanceValidator(FhirContext fhirContext, ValidationSupportChain validationSupportChain) {
        FhirInstanceValidator instanceValidator = new FhirInstanceValidator(fhirContext);
        instanceValidator.setValidationSupport(validationSupportChain);
        return instanceValidator;
    }
    
    @Bean
    public FhirValidator fhirValidator(FhirContext fhirContext, FhirInstanceValidator instanceValidator) {
        FhirValidator validator = fhirContext.newValidator();
        validator.registerValidatorModule(instanceValidator);
        return validator;
    }
}

Sau đó bạn có thể inject FhirValidator vào các service:

import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.r5.model.Patient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class PatientValidationService {
    
    private final FhirValidator validator;
    
    @Autowired
    public PatientValidationService(FhirValidator validator) {
        this.validator = validator;
    }
    
    public ValidationResult validatePatient(Patient patient) {
        return validator.validateWithResult(patient);
    }
    
    public boolean isPatientValid(Patient patient) {
        ValidationResult result = validator.validateWithResult(patient);
        return result.isSuccessful();
    }
}

Các loại validation trong FHIR

Thư viện hapi-fhir-validation hỗ trợ nhiều loại validation khác nhau:

1. Structural Validation

Kiểm tra cấu trúc của resource theo đặc tả FHIR:

  • Các thành phần bắt buộc

  • Kiểu dữ liệu đúng

  • Cardinality hợp lệ

2. Content Validation

Kiểm tra nội dung của resource:

  • References hợp lệ

  • Ràng buộc chéo giữa các trường

  • FHIRPath invariants

3. Business Rules Validation

Kiểm tra theo các quy tắc nghiệp vụ:

  • Ràng buộc theo profiles tùy chỉnh

  • Invariants phức tạp

4. Terminology Validation

Kiểm tra mã và terminologies:

  • Mã có tồn tại trong CodeSystem

  • Mã thuộc ValueSet được chỉ định

  • Binding strength (required, extensible, preferred, example)

Best Practices khi sử dụng hapi-fhir-validation

  1. Tái sử dụng FhirContext và Validator: Các đối tượng này tốn nhiều tài nguyên để khởi tạo, nên được tạo một lần và tái sử dụng.

  2. Sử dụng caching: Tích hợp CachingValidationSupport để cải thiện hiệu suất.

  3. Validate theo cấp độ: Phân chia validation thành nhiều cấp độ để dễ quản lý:

    • Basic validation (cấu trúc)

    • Profile validation (tuân thủ profile)

    • Terminology validation (mã)

    • Business rules validation (quy tắc nghiệp vụ)

  4. Tùy chỉnh severity: Điều chỉnh mức độ nghiêm trọng của các lỗi validation.

  5. Tạo custom validation: Triển khai các IValidatorModule tùy chỉnh cho các quy tắc nghiệp vụ đặc thù của ứng dụng.

import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.Patient;

import java.util.ArrayList;
import java.util.List;

public class CustomPatientValidator implements IValidatorModule {
    
    @Override
    public ValidationResult validateResource(IBaseResource resource) {
        List<SingleValidationMessage> messages = new ArrayList<>();
        
        // Kiểm tra nếu resource là Patient
        if (resource instanceof Patient) {
            Patient patient = (Patient) resource;
            
            // Kiểm tra quy tắc tùy chỉnh: tên phải có ít nhất 2 từ
            if (patient.hasName() && patient.getNameFirstRep().hasGiven()) {
                String givenName = patient.getNameFirstRep().getGivenAsSingleString();
                if (givenName.split(" ").length < 2) {
                    SingleValidationMessage message = new SingleValidationMessage();
                    message.setLocationString("Patient.name.given");
                    message.setSeverity(ResultSeverityEnum.WARNING);
                    message.setMessage("Given name should have at least 2 words for Vietnamese patients");
                    messages.add(message);
                }
            }
            
            // Kiểm tra quy tắc tùy chỉnh: địa chỉ phải có thành phố
            if (patient.hasAddress() && !patient.getAddressFirstRep().hasCity()) {
                SingleValidationMessage message = new SingleValidationMessage();
                message.setLocationString("Patient.address");
                message.setSeverity(ResultSeverityEnum.ERROR);
                message.setMessage("Address must include city");
                messages.add(message);
            }
        }
        
        return new ValidationResult(messages);
    }
}
  1. Xử lý kết quả validation: Phát triển chiến lược xử lý phù hợp cho các vấn đề validation:

    • Lọc và nhóm các lỗi theo severity

    • Cung cấp phản hồi rõ ràng cho người dùng

    • Cho phép ghi đè một số lỗi validation khi cần thiết

  2. Kết hợp với FhirPath: Sử dụng FhirPath để kiểm tra các ràng buộc phức tạp.

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.fhirpath.IFhirPath;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.Patient;

import java.util.List;

public class FhirPathValidationExample {
    public static void main(String[] args) {
        FhirContext ctx = FhirContext.forR5();
        IFhirPath fhirPath = ctx.newFhirPath();
        
        Patient patient = new Patient();
        patient.addIdentifier()
            .setSystem("http://hospital.example.org/patients")
            .setValue("12345");
        
        // Kiểm tra bằng FHIRPath
        String expression = "identifier.where(system = 'http://hospital.example.org/patients').exists()";
        List<BooleanType> result = fhirPath.evaluate(patient, expression, BooleanType.class);
        
        if (!result.isEmpty() && result.get(0).booleanValue()) {
            System.out.println("Patient has valid hospital identifier");
        } else {
            System.out.println("Patient is missing required hospital identifier");
        }
    }
}

Tính năng nâng cao của hapi-fhir-validation

1. Validation bất đồng bộ

Đối với các ứng dụng xử lý lượng lớn dữ liệu, validation bất đồng bộ có thể cải thiện hiệu suất:

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.r5.model.Patient;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AsyncValidationExample {
    public static void main(String[] args) throws Exception {
        FhirContext ctx = FhirContext.forR5();
        FhirValidator validator = ctx.newValidator();
        
        // Tạo danh sách patients cần validation
        List<Patient> patients = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Patient patient = new Patient();
            patient.addName().setFamily("Patient" + i);
            patients.add(patient);
        }
        
        // Tạo thread pool
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        // Thực hiện validation bất đồng bộ
        List<CompletableFuture<ValidationResult>> futures = new ArrayList<>();
        
        for (Patient patient : patients) {
            CompletableFuture<ValidationResult> future = CompletableFuture.supplyAsync(
                () -> validator.validateWithResult(patient),
                executor
            );
            futures.add(future);
        }
        
        // Đợi tất cả validation hoàn thành
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(
            futures.toArray(new CompletableFuture[0])
        );
        
        // Đợi hoàn thành
        allFutures.get();
        
        // Đếm số lượng resources hợp lệ
        long validCount = futures.stream()
            .map(CompletableFuture::join)
            .filter(ValidationResult::isSuccessful)
            .count();
        
        System.out.println("Total patients: " + patients.size());
        System.out.println("Valid patients: " + validCount);
        
        // Đóng executor
        executor.shutdown();
    }
}

2. Chỉnh sửa resource để khắc phục lỗi validation

Đôi khi bạn cần tự động khắc phục các lỗi validation:

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator;

public class AutoCorrectValidationExample {
    public static void main(String[] args) {
        FhirContext ctx = FhirContext.forR5();
        
        // Tạo validator
        FhirValidator validator = ctx.newValidator();
        validator.registerValidatorModule(new FhirInstanceValidator(ctx));
        
        // Tạo patient có lỗi
        Patient patient = new Patient();
        // Không thêm tên
        // Không thêm identifier
        
        // Validate patient
        ValidationResult result = validator.validateWithResult(patient);
        
        // In kết quả validation
        System.out.println("Initial validation:");
        System.out.println("Successful: " + result.isSuccessful());
        System.out.println("Issues found: " + result.getMessages().size());
        
        // Tự động khắc phục lỗi
        for (SingleValidationMessage message : result.getMessages()) {
            if (message.getSeverity() == ResultSeverityEnum.ERROR) {
                // Xử lý các lỗi cụ thể
                if (message.getMessage().contains("Patient.name")) {
                    // Thêm tên
                    patient.addName().setFamily("Nguyễn").addGiven("Văn A");
                    System.out.println("Fixed: Added name to patient");
                }
                
                if (message.getMessage().contains("Patient.identifier")) {
                    // Thêm identifier
                    patient.addIdentifier()
                        .setSystem("http://hospital.example.org/patients")
                        .setValue("AUTO-" + System.currentTimeMillis());
                    System.out.println("Fixed: Added identifier to patient");
                }
            }
        }
        
        // Validate lại sau khi sửa
        ValidationResult fixedResult = validator.validateWithResult(patient);
        
        // In kết quả validation sau khi sửa
        System.out.println("\nValidation after auto-correction:");
        System.out.println("Successful: " + fixedResult.isSuccessful());
        System.out.println("Issues found: " + fixedResult.getMessages().size());
        
        // In chi tiết lỗi còn lại
        if (!fixedResult.isSuccessful()) {
            System.out.println("\nRemaining issues:");
            fixedResult.getMessages().forEach(message -> 
                System.out.println(" - " + message.getSeverity() + ": " + message.getMessage()));
        }
    }
}

3. Validation từ xa (Remote Validation)

Bạn có thể tích hợp với FHIR server từ xa để thực hiện validation:

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.model.StringType;

public class RemoteValidationExample {
    public static void main(String[] args) {
        // Khởi tạo FhirContext
        FhirContext ctx = FhirContext.forR5();
        
        // Tạo client kết nối đến FHIR server
        IGenericClient client = ctx.newRestfulGenericClient("http://hapi.fhir.org/baseR5");
        
        // Tạo patient cần validation
        Patient patient = new Patient();
        patient.addName().setFamily("Nguyễn").addGiven("Văn A");
        
        // Tạo Parameters cho $validate operation
        Parameters inParams = new Parameters();
        inParams.addParameter().setName("resource").setResource(patient);
        inParams.addParameter().setName("profile").setValue(new StringType("http://hl7.org/fhir/StructureDefinition/Patient"));
        
        // Gọi $validate operation
        Parameters outParams = client.operation()
            .onType(Patient.class)
            .named("$validate")
            .withParameters(inParams)
            .execute();
        
        // Lấy OperationOutcome từ kết quả
        OperationOutcome outcome = (OperationOutcome) outParams.getParameter().get(0).getResource();
        
        // In kết quả validation
        System.out.println("Remote validation result:");
        
        if (outcome.getIssue().isEmpty()) {
            System.out.println("No issues found - validation successful");
        } else {
            System.out.println("Issues found:");
            outcome.getIssue().forEach(issue -> 
                System.out.println(" - " + issue.getSeverity() + ": " + issue.getDiagnostics()));
        }
    }
}

4. Tinh chỉnh các thông báo validation

Bạn có thể tùy chỉnh cách hiển thị các thông báo validation:

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator;

import java.util.HashMap;
import java.util.Map;

public class CustomValidationMessageExample {
    public static void main(String[] args) {
        FhirContext ctx = FhirContext.forR5();
        
        // Tạo validator
        FhirValidator validator = ctx.newValidator();
        FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx);
        validator.registerValidatorModule(instanceValidator);
        
        // Tạo patient có lỗi
        Patient patient = new Patient();
        // Không thêm name
        
        // Thực hiện validation
        ValidationResult result = validator.validateWithResult(patient);
        
        // Map để chuyển đổi thông báo lỗi
        Map<String, String> errorTranslations = new HashMap<>();
        errorTranslations.put("Element 'Patient.name': minimum required = 1", 
                              "Bệnh nhân phải có ít nhất một tên");
        errorTranslations.put("Element 'Patient.identifier': minimum required = 1", 
                              "Bệnh nhân phải có ít nhất một mã định danh");
        
        // Hiển thị thông báo tùy chỉnh
        System.out.println("Validation issues (user-friendly):");
        for (SingleValidationMessage message : result.getMessages()) {
            // Lấy thông báo gốc
            String originalMessage = message.getMessage();
            
            // Tìm thông báo tùy chỉnh
            String customMessage = errorTranslations.getOrDefault(originalMessage, originalMessage);
            
            // Hiển thị mức độ nghiêm trọng và thông báo
            String severity = message.getSeverity().name();
            String friendlySeverity = "THÔNG TIN";
            if (severity.equals("ERROR")) friendlySeverity = "LỖI";
            if (severity.equals("WARNING")) friendlySeverity = "CẢNH BÁO";
            
            System.out.println(friendlySeverity + ": " + customMessage);
        }
    }
}

5. Validation dựa trên context

Đôi khi quy tắc validation phụ thuộc vào context sử dụng:

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.Patient;

import java.util.ArrayList;
import java.util.List;

public class ContextAwareValidationExample {
    
    public enum ValidationContext {
        REGISTRATION,   // Đăng ký ban đầu
        EMERGENCY,      // Cấp cứu 
        FOLLOW_UP       // Tái khám
    }
    
    public static class ContextAwareValidator implements IValidatorModule {
        
        private final ValidationContext context;
        
        public ContextAwareValidator(ValidationContext context) {
            this.context = context;
        }
        
        @Override
        public ValidationResult validateResource(IBaseResource resource) {
            List<SingleValidationMessage> messages = new ArrayList<>();
            
            if (resource instanceof Patient) {
                Patient patient = (Patient) resource;
                
                switch (context) {
                    case REGISTRATION:
                        // Đăng ký cần nhiều thông tin
                        validateForRegistration(patient, messages);
                        break;
                    case EMERGENCY:
                        // Cấp cứu chỉ cần thông tin cơ bản
                        validateForEmergency(patient, messages);
                        break;
                    case FOLLOW_UP:
                        // Tái khám cần kiểm tra identifier
                        validateForFollowUp(patient, messages);
                        break;
                }
            }
            
            return new ValidationResult(messages);
        }
        
        private void validateForRegistration(Patient patient, List<SingleValidationMessage> messages) {
            // Kiểm tra tên đầy đủ
            if (!patient.hasName() || !patient.getNameFirstRep().hasFamily()) {
                addError(messages, "Patient.name", "Đăng ký cần tên đầy đủ của bệnh nhân");
            }
            
            // Kiểm tra địa chỉ
            if (!patient.hasAddress()) {
                addError(messages, "Patient.address", "Đăng ký cần địa chỉ của bệnh nhân");
            }
            
            // Kiểm tra ngày sinh
            if (!patient.hasBirthDate()) {
                addError(messages, "Patient.birthDate", "Đăng ký cần ngày sinh của bệnh nhân");
            }
            
            // Kiểm tra giới tính
            if (!patient.hasGender()) {
                addError(messages, "Patient.gender", "Đăng ký cần giới tính của bệnh nhân");
            }
        }
        
        private void validateForEmergency(Patient patient, List<SingleValidationMessage> messages) {
            // Trong trường hợp cấp cứu, chỉ cần tên
            if (!patient.hasName()) {
                addError(messages, "Patient.name", "Cần ít nhất một tên để nhận dạng bệnh nhân");
            }
        }
        
        private void validateForFollowUp(Patient patient, List<SingleValidationMessage> messages) {
            // Tái khám cần identifier
            if (!patient.hasIdentifier()) {
                addError(messages, "Patient.identifier", "Tái khám cần mã định danh của bệnh nhân");
            }
        }
        
        private void addError(List<SingleValidationMessage> messages, String path, String message) {
            SingleValidationMessage validationMessage = new SingleValidationMessage();
            validationMessage.setLocationString(path);
            validationMessage.setSeverity(ResultSeverityEnum.ERROR);
            validationMessage.setMessage(message);
            messages.add(validationMessage);
        }
    }
    
    public static void main(String[] args) {
        FhirContext ctx = FhirContext.forR5();
        
        // Tạo patient cho test
        Patient patient = new Patient();
        patient.addName().addGiven("A"); // Chỉ có tên, không có họ
        
        // Tạo các validator cho từng context
        ContextAwareValidator registrationValidator = new ContextAwareValidator(ValidationContext.REGISTRATION);
        ContextAwareValidator emergencyValidator = new ContextAwareValidator(ValidationContext.EMERGENCY);
        ContextAwareValidator followUpValidator = new ContextAwareValidator(ValidationContext.FOLLOW_UP);
        
        // Validate trong context đăng ký
        ValidationResult registrationResult = registrationValidator.validateResource(patient);
        System.out.println("Registration Context Validation:");
        registrationResult.getMessages().forEach(message -> 
            System.out.println(" - " + message.getMessage()));
        
        // Validate trong context cấp cứu
        ValidationResult emergencyResult = emergencyValidator.validateResource(patient);
        System.out.println("\nEmergency Context Validation:");
        if (emergencyResult.getMessages().isEmpty()) {
            System.out.println(" - No issues found");
        } else {
            emergencyResult.getMessages().forEach(message -> 
                System.out.println(" - " + message.getMessage()));
        }
        
        // Validate trong context tái khám
        ValidationResult followUpResult = followUpValidator.validateResource(patient);
        System.out.println("\nFollow-up Context Validation:");
        followUpResult.getMessages().forEach(message -> 
            System.out.println(" - " + message.getMessage()));
    }
}

Tích hợp với các công nghệ khác

Tích hợp với RESTful API

Để validation các resource gửi đến thông qua RESTful API:

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.Patient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/patients")
public class PatientController {

    private final FhirValidator validator;
    private final FhirContext fhirContext;
    
    @Autowired
    public PatientController(FhirValidator validator, FhirContext fhirContext) {
        this.validator = validator;
        this.fhirContext = fhirContext;
    }
    
    @PostMapping
    public ResponseEntity<?> createPatient(@RequestBody String patientJson) {
        try {
            // Parse resource từ JSON
            Patient patient = fhirContext.newJsonParser().parseResource(Patient.class, patientJson);
            
            // Validate resource
            ValidationResult result = validator.validateWithResult(patient);
            
            if (result.isSuccessful()) {
                // Xử lý khi resource hợp lệ
                // ...
                
                return new ResponseEntity<>("Patient created successfully", HttpStatus.CREATED);
            } else {
                // Tạo OperationOutcome chứa các lỗi validation
                OperationOutcome outcome = new OperationOutcome();
                
                result.getMessages().forEach(message -> {
                    OperationOutcome.OperationOutcomeIssueComponent issue = outcome.addIssue();
                    
                    // Map severity
                    switch (message.getSeverity()) {
                        case ERROR:
                            issue.setSeverity(OperationOutcome.IssueSeverity.ERROR);
                            break;
                        case WARNING:
                            issue.setSeverity(OperationOutcome.IssueSeverity.WARNING);
                            break;
                        case INFORMATION:
                            issue.setSeverity(OperationOutcome.IssueSeverity.INFORMATION);
                            break;
                        default:
                            issue.setSeverity(OperationOutcome.IssueSeverity.INFORMATION);
                    }
                    
                    issue.setCode(OperationOutcome.IssueType.INVARIANT);
                    issue.setDiagnostics(message.getMessage());
                    issue.setLocation(List.of(message.getLocationString()));
                });
                
                // Trả về lỗi validation dưới dạng OperationOutcome
                String outcomeJson = fhirContext.newJsonParser().setPrettyPrint(true)
                    .encodeResourceToString(outcome);
                
                return new ResponseEntity<>(outcomeJson, HttpStatus.UNPROCESSABLE_ENTITY);
            }
        } catch (Exception e) {
            return new ResponseEntity<>("Error parsing resource: " + e.getMessage(), 
                                       HttpStatus.BAD_REQUEST);
        }
    }
}

Tích hợp với Message Queue

Validation trong môi trường xử lý message:

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.r5.model.Patient;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

@Component
public class FhirMessageConsumer {

    private final FhirContext fhirContext;
    private final FhirValidator validator;
    
    @Autowired
    public FhirMessageConsumer(FhirContext fhirContext, FhirValidator validator) {
        this.fhirContext = fhirContext;
        this.validator = validator;
    }
    
    @RabbitListener(queues = "fhir.patient.queue")
    public void processPatientMessage(@Payload String patientJson) {
        try {
            // Parse resource từ JSON
            Patient patient = fhirContext.newJsonParser().parseResource(Patient.class, patientJson);
            
            // Validate resource
            ValidationResult result = validator.validateWithResult(patient);
            
            if (result.isSuccessful()) {
                // Xử lý patient hợp lệ
                System.out.println("Received valid patient: " + patient.getNameFirstRep().getNameAsSingleString());
                // Tiếp tục xử lý...
            } else {
                // Xử lý patient không hợp lệ
                System.out.println("Received invalid patient with " + result.getMessages().size() + " issues");
                // Có thể gửi đến dead-letter queue hoặc lưu lỗi
                result.getMessages().forEach(message -> 
                    System.out.println(" - " + message.getSeverity() + ": " + message.getMessage()));
            }
        } catch (Exception e) {
            System.err.println("Error processing message: " + e.getMessage());
            // Xử lý lỗi
        }
    }
}

Kết luận

Thư viện hapi-fhir-validation là một công cụ mạnh mẽ và toàn diện để đảm bảo tính hợp lệ của dữ liệu FHIR trong các ứng dụng y tế. Với khả năng validation đa cấp độ từ cấu trúc đến nội dung, terminologies và profiles, thư viện này giúp đảm bảo chất lượng dữ liệu và tính tương tác giữa các hệ thống y tế.

Các lợi ích chính của thư viện bao gồm:

  1. Đa dạng loại validation: Hỗ trợ nhiều cấp độ validation từ cấu trúc đến nội dung và terminologies

  2. Tích hợp với profiles: Validation dựa trên StructureDefinition profiles cho phép tùy chỉnh các ràng buộc

  3. Kiến trúc module: Thiết kế module hóa cho phép mở rộng và tùy chỉnh

  4. API trực quan: API dễ sử dụng với FhirValidator và ValidationResult

  5. Hiệu suất tốt: Cơ chế caching và hỗ trợ xử lý bất đồng bộ

Khi xây dựng các ứng dụng y tế với FHIR, việc tích hợp hapi-fhir-validation giúp đảm bảo dữ liệu tuân thủ các tiêu chuẩn và quy định, đồng thời cải thiện khả năng tương tác giữa các hệ thống khác nhau trong hệ sinh thái chăm sóc sức khỏe.

Last updated