hapi-fhir-jpaserver-base là một thành phần trọng tâm trong hệ sinh thái HAPI FHIR, cung cấp triển khai đầy đủ của FHIR Server dựa trên JPA (Java Persistence API). Thư viện này cung cấp một giải pháp "out-of-the-box" để lưu trữ, truy vấn và quản lý FHIR resources trong cơ sở dữ liệu quan hệ, đồng thời triển khai đầy đủ RESTful API theo chuẩn FHIR.
Trong khi hapi-fhir-base cung cấp các thành phần cốt lõi và hapi-fhir-structures-r5 định nghĩa các model classes, hapi-fhir-jpaserver-base mở rộng những thành phần này với lớp persistence hoàn chỉnh - biến dữ liệu FHIR thành các entities trong cơ sở dữ liệu quan hệ, cho phép lưu trữ lâu dài và tìm kiếm hiệu quả.
Kiến trúc và Thành phần
hapi-fhir-jpaserver-base được xây dựng theo kiến trúc nhiều lớp, với các thành phần chính sau:
1. DAO Layer (Data Access Objects)
Lớp DAO định nghĩa các interfaces và implementations để tương tác với cơ sở dữ liệu:
IFhirResourceDao: Interface cơ bản cho các operations CRUD
IFhirResourceDaoXXX: Các interface cụ thể cho từng resource type (Patient, Observation, etc.)
FhirResourceDaoImpl: Implementation chung cho tất cả resource types
JpaResourceDao: Mở rộng implementation với các chức năng JPA-specific
// Ví dụ về DAO trong ứng dụng
@Autowired
private IFhirResourceDao<Patient> patientDao;
// Tìm kiếm patients
SearchParameterMap searchParams = new SearchParameterMap();
searchParams.add("family", new StringParam("Nguyen"));
searchParams.setLoadSynchronous(true);
IBundleProvider results = patientDao.search(searchParams);
List<IBaseResource> patients = results.getResources(0, results.size());
2. Search Framework
Một framework search mạnh mẽ cho phép thực hiện các truy vấn phức tạp:
SearchBuilder: Chuyển đổi search parameters thành SQL queries
SearchParameterRegistry: Quản lý và đăng ký search parameters
JpaPredicateBuilder: Tạo các SQL predicates dựa trên search criteria
MatchOperation: Supports cho nhiều loại matching (exact, contains, regex)
// Ví dụ tìm kiếm phức tạp
SearchParameterMap params = new SearchParameterMap();
params.add(Patient.SP_FAMILY, new StringParam("Nguyen"));
params.add(Patient.SP_BIRTHDATE, new DateRangeParam(
new DateParam("ge", "1980-01-01"),
new DateParam("le", "2000-12-31")));
params.add(Patient.SP_GENDER, new TokenParam("male"));
params.setIncludes(Collections.singleton(new Include("Patient:organization")));
params.setSort(new SortSpec(Patient.SP_FAMILY));
params.setCount(20);
IBundleProvider results = patientDao.search(params);
3. Resource Providers
Resource Providers xử lý các RESTful API requests cho mỗi resource type:
JpaResourceProviderXXX: Các providers cụ thể cho từng resource type
PlainProvider: Xử lý system-level operations
JpaConformanceProviderR5: Quản lý CapabilityStatement và metadata
// Đăng ký Resource Providers trong một RestfulServer
@Bean
public ServletRegistrationBean<RestfulServer> fhirServerServlet() {
RestfulServer server = new RestfulServer(fhirContext);
// Đăng ký resource providers từ registry
DaoRegistry daoRegistry = appCtx.getBean(DaoRegistry.class);
for (Class<? extends IBaseResource> nextResourceType : daoRegistry.getRegisteredDaoTypes()) {
IFhirResourceDao<?> dao = daoRegistry.getResourceDao(nextResourceType);
JpaResourceProvider<? extends IBaseResource> provider = new JpaResourceProvider<>(fhirContext, dao, nextResourceType);
server.registerProvider(provider);
}
return new ServletRegistrationBean<>(server, "/fhir/*");
}
4. Transaction Processing
Framework xử lý transaction mạnh mẽ cho phép các operations batch và atomic:
JpaTransactionManager: Quản lý transactions JPA
TransactionProcessor: Xử lý FHIR transaction bundles
SystemDao: Triển khai transaction/batch operations
// Ví dụ xử lý transaction
@Transactional
public Bundle processTransaction(Bundle theBundle) {
TransactionProcessor processor = new TransactionProcessor(daoRegistry, systemDao);
return processor.transaction(null, theBundle);
}
5. History và Versioning
JPA Server duy trì lịch sử đầy đủ của resources:
ResourceHistoryTable: Lưu trữ tất cả phiên bản resources
ResourceHistoryTag: Quản lý tags cho resource versions
ResourceHistoryProvenanceEntity: Lưu trữ thông tin provenance
// Truy vấn history của resource
IFhirResourceDao<Patient> patientDao = daoRegistry.getResourceDao(Patient.class);
IBundleProvider history = patientDao.history(new IdType("Patient/123"), null, null, null);
Tính năng chính
1. Lưu trữ Persistence đầy đủ
hapi-fhir-jpaserver-base cung cấp lưu trữ persistence toàn diện:
Hỗ trợ nhiều loại DB: PostgreSQL, MySQL, Oracle, Microsoft SQL Server, H2
Schema Management: Migrations và schema updates
Optimized Indexing: Indexing strategies tự động cho search parameters
Resource Versioning: Lưu trữ tất cả phiên bản cũ của resources
Binary Support: Lưu trữ hiệu quả Binary resources
// Cấu hình EntityManagerFactory
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource());
entityManagerFactory.setPackagesToScan("ca.uhn.fhir.jpa.entity");
entityManagerFactory.setPersistenceUnitName("HAPI_PU");
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setDatabasePlatform("org.hibernate.dialect.PostgreSQL95Dialect");
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.format_sql", "true");
jpaProperties.put("hibernate.show_sql", "false");
jpaProperties.put("hibernate.hbm2ddl.auto", "update");
jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL95Dialect");
entityManagerFactory.setJpaProperties(jpaProperties);
return entityManagerFactory;
}
2. Tìm kiếm nâng cao
Framework tìm kiếm mạnh mẽ hỗ trợ tất cả tính năng tìm kiếm FHIR:
Chaining: Tìm kiếm trên các resources liên quan (e.g., Patient?organization.name=Hospital)
Reverse Chaining: Tìm kiếm ngược theo references (e.g., Organization?_has:Patient:organization:name=Smith)
Include/Revinclude: Tải resources liên quan
Composite Search Parameters: Kết hợp nhiều parameters (e.g., Observation?code-value-quantity=http://loinc.org|8480-6$gt150)
Token Indexing: Tìm kiếm hiệu quả codes và identifiers
// Ví dụ tìm kiếm Observations của bệnh nhân có blood pressure cao
SearchParameterMap params = new SearchParameterMap();
params.add(Observation.SP_CODE, new TokenParam("http://loinc.org", "8480-6"));
params.add(Observation.SP_VALUE_QUANTITY, new QuantityParam()
.setValue(new BigDecimal("120"))
.setSystem("http://unitsofmeasure.org")
.setCode("mm[Hg]")
.setComparator(ParamPrefixEnum.GREATERTHAN));
params.add(Observation.SP_SUBJECT, new ReferenceParam("Patient", "123"));
IBundleProvider results = observationDao.search(params);
3. Subscriptions
JPA Server triển khai đầy đủ subscription framework để thông báo các thay đổi dữ liệu:
REST Hook: Gửi HTTP requests khi có thay đổi
Websocket: Thông báo real-time qua websockets
Email: Gửi email notifications
Message Queue: Tích hợp với RabbitMQ, Kafka
FHIR Messaging: Gửi FHIR messages đến endpoints
// Cấu hình Subscription Support
@Bean
public SubscriptionTriggeringSvc subscriptionTriggeringSvc() {
return new SubscriptionTriggeringSvcImpl();
}
@Bean
public SubscriptionMatcherInterceptor subscriptionMatcherInterceptor() {
SubscriptionMatcherInterceptor interceptor = new SubscriptionMatcherInterceptor();
interceptor.setSubscriptionTriggeringSvc(subscriptionTriggeringSvc());
return interceptor;
}
@Bean
public SubscriptionMatchingStrategy subscriptionMatchingStrategy() {
return new SubscriptionMatchingStrategyDstu3Plus();
}
4. Terminology Support
Hỗ trợ terminology tích hợp cho validation và expansion:
CodeSystem & ValueSet Storage: Lưu trữ và quản lý terminologies
External Terminology Services: Tích hợp với SNOMED CT, LOINC
Lazy Loading: Tải và cache terminology data khi cần
// Ví dụ ValueSet Expansion
@Bean
public ITermReadSvc termReadSvc() {
return new TermReadSvcImpl();
}
// Mở rộng một ValueSet
ValueSetExpansionOptions options = new ValueSetExpansionOptions();
options.setCount(1000);
ValueSet expandedVs = termReadSvc().expandValueSet(options, valueSet);
5. Bulk Data Access
Triển khai FHIR Bulk Data API để export/import dữ liệu lớn:
$export Operation: Xuất dữ liệu theo định dạng NDJSON
Group Export: Xuất dữ liệu cho nhóm bệnh nhân cụ thể
Patient Export: Xuất tất cả dữ liệu cho một bệnh nhân
Asynchronous Processing: Xử lý không đồng bộ cho data lớn
// Cấu hình BulkDataExportProvider
@Bean
public BulkDataExportProvider bulkDataExportProvider() {
return new BulkDataExportProvider();
}
// Thêm bulk data provider vào server
server.registerProvider(bulkDataExportProvider());
6. Interceptor Framework
Framework interceptor mạnh mẽ cho phép can thiệp vào lifecycle của requests:
JpaStorageInterceptor: Can thiệp vào storage operations
RequestInterceptor: Can thiệp vào các HTTP requests
PointcutLatch: Tùy chỉnh các "pointcuts" cho hooks
@Bean
public DaoConfig daoConfig() {
DaoConfig config = new DaoConfig();
// Cấu hình cơ bản
config.setAllowExternalReferences(true);
config.setAllowMultipleDelete(true);
config.setDeleteEnabled(true);
config.setExpungeEnabled(true);
// Cấu hình subscription
config.setSubscriptionEnabled(true);
config.setSubscriptionMatchingEnabled(true);
// Cấu hình resource validation
config.setValidateResourcesForStorage(true);
config.setEnforceReferentialIntegrityOnDelete(true);
// Cấu hình pagination
config.setDefaultPageSize(20);
config.setMaximumPageSize(500);
// Cấu hình client ID routing
config.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY);
return config;
}
2. ModelConfig
ModelConfig cấu hình các aspects của FHIR model:
@Bean
public ModelConfig modelConfig() {
ModelConfig config = new ModelConfig();
// Cấu hình default encoding
config.setDefaultEncoding(EncodingEnum.JSON);
// Bao gồm references
config.setRespectBundleOrder(true);
// Cấu hình narrative generation
config.setGenerateNarratives(true);
// Cấu hình các field nào để lưu trữ
config.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
return config;
}
3. Custom Resource Providers
Tạo resource providers tùy chỉnh để thêm behavior cụ thể:
@Component
public class CustomPatientProvider extends JpaResourceProviderR5<Patient> {
public CustomPatientProvider(FhirContext context, IFhirResourceDao<Patient> dao) {
super(context, dao, Patient.class);
}
@Operation(name = "$find-duplicate", idempotent = true)
public Parameters findDuplicates(
@OperationParam(name = "criteria") StringType criteria) {
// Custom logic to find duplicate patients
List<Patient> duplicates = findDuplicatePatients(criteria.getValue());
// Return results
Parameters parameters = new Parameters();
for (Patient patient : duplicates) {
parameters.addParameter().setName("result").setResource(patient);
}
return parameters;
}
private List<Patient> findDuplicatePatients(String criteria) {
// Implementation of duplicate detection algorithm
return new ArrayList<>();
}
}
4. Custom Search Parameters
Đăng ký search parameters tùy chỉnh:
@PostConstruct
public void initializeSearchParameters() {
// Tạo custom search parameter cho Patient
SearchParameter searchParameter = new SearchParameter();
searchParameter.setId("Patient-custom-fullname");
searchParameter.setName("fullname");
searchParameter.setCode("fullname");
searchParameter.setDescription("Search by patient's full name");
searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE);
searchParameter.setType(Enumerations.SearchParamType.STRING);
searchParameter.setExpression("Patient.name.given + ' ' + Patient.name.family");
searchParameter.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
searchParameter.addBase("Patient");
// Đăng ký search parameter
mySearchParamRegistry.addSearchParameter(searchParameter);
}
Triển khai và Tích hợp
1. Spring Boot Integration
Tích hợp hapi-fhir-jpaserver-base với Spring Boot:
@Configuration
@EnableTransactionManagement
public class FhirServerConfig {
@Autowired
private DataSource dataSource;
@Bean
public FhirContext fhirContext() {
return FhirContext.forR5();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setPackagesToScan("ca.uhn.fhir.jpa.entity");
entityManagerFactory.setPersistenceUnitName("HAPI_PU");
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setDatabasePlatform("org.hibernate.dialect.PostgreSQL95Dialect");
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.format_sql", "true");
jpaProperties.put("hibernate.show_sql", "false");
jpaProperties.put("hibernate.hbm2ddl.auto", "update");
jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL95Dialect");
entityManagerFactory.setJpaProperties(jpaProperties);
return entityManagerFactory;
}
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
@Bean
public DaoConfig daoConfig() {
DaoConfig config = new DaoConfig();
config.setSubscriptionEnabled(true);
return config;
}
@Bean
public ModelConfig modelConfig() {
ModelConfig config = new ModelConfig();
config.setDefaultEncoding(EncodingEnum.JSON);
return config;
}
@Bean
public ServletRegistrationBean<RestfulServer> fhirServlet() {
ServletRegistrationBean<RestfulServer> servletRegistrationBean = new ServletRegistrationBean<>();
RestfulServer restfulServer = new RestfulServer(fhirContext());
// Cấu hình resource providers
IFhirSystemDao<Bundle, Meta> systemDao = appCtx.getBean(IFhirSystemDao.class);
JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(restfulServer, systemDao, daoConfig());
confProvider.setImplementationDescription("HAPI FHIR R5 Server");
restfulServer.setServerConformanceProvider(confProvider);
// Đăng ký providers cho các resource types
DaoRegistry registry = appCtx.getBean(DaoRegistry.class);
for (Class<? extends IBaseResource> resourceType : registry.getRegisteredDaoTypes()) {
IFhirResourceDao<? extends IBaseResource> dao = registry.getResourceDao(resourceType);
ResourceProvider provider = new JpaResourceProvider<>(fhirContext(), dao, resourceType);
restfulServer.registerProvider(provider);
}
servletRegistrationBean.setServlet(restfulServer);
servletRegistrationBean.addUrlMappings("/fhir/*");
return servletRegistrationBean;
}
}
2. Standalone Server
Triển khai standalone server:
public class StandaloneServer {
private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(StandaloneServer.class);
public static void main(String[] args) throws Exception {
// Tạo Jetty server
Server server = new Server(8080);
// Tạo web app context
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/");
webAppContext.setDisplayName("HAPI FHIR");
// Cấu hình Spring
webAppContext.setInitParameter("contextConfigLocation", "classpath:application-context.xml");
webAppContext.setInitParameter("contextClass", "org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
webAppContext.addEventListener(new ContextLoaderListener());
// Register servlets
ServletHolder fhirServletHolder = new ServletHolder();
fhirServletHolder.setServlet(new RestfulServer());
fhirServletHolder.setInitParameter("ImplementationDescription", "HAPI FHIR JPA Server");
webAppContext.addServlet(fhirServletHolder, "/fhir/*");
// Cấu hình CORS filter
FilterHolder corsFilterHolder = webAppContext.addFilter(CrossOriginFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
corsFilterHolder.setInitParameter("allowedOrigins", "*");
corsFilterHolder.setInitParameter("allowedMethods", "GET,POST,PUT,DELETE,OPTIONS");
corsFilterHolder.setInitParameter("allowedHeaders", "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization");
server.setHandler(webAppContext);
server.start();
LOGGER.info("Server started on port 8080");
server.join();
}
}
3. Docker Deployment
Triển khai JPA Server với Docker:
FROM openjdk:17-jdk-slim
WORKDIR /app
# Copy application JAR
COPY target/hapi-fhir-jpaserver.jar /app/
# Configuration for PostgreSQL
ENV SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/hapi
ENV SPRING_DATASOURCE_USERNAME=postgres
ENV SPRING_DATASOURCE_PASSWORD=postgres
ENV SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver
ENV SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT=org.hibernate.dialect.PostgreSQL95Dialect
ENV SPRING_JPA_PROPERTIES_HIBERNATE_SEARCH_ENABLED=true
# Configuration for FHIR server
ENV HAPI_FHIR_VERSION=R5
ENV HAPI_SUBSCRIPTION_WEBSOCKET_ENABLED=true
ENV HAPI_SUBSCRIPTION_EMAIL_ENABLED=false
ENV HAPI_ALLOW_EXTERNAL_REFERENCES=true
ENV HAPI_ALLOW_PLACEHOLDER_REFERENCES=true
ENV HAPI_REUSE_CACHED_SEARCH_RESULTS_MILLIS=60000
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/hapi-fhir-jpaserver.jar"]
hapi-fhir-jpaserver-base được sử dụng trong nhiều tình huống:
Clinical Data Repository: Lưu trữ dữ liệu lâm sàng theo chuẩn FHIR
Health Information Exchange: Nền tảng cho trao đổi dữ liệu y tế
Patient Portals: Backend cho cổng thông tin bệnh nhân
Research Databases: Lưu trữ dữ liệu nghiên cứu
Mobile Health Apps: Backend cho ứng dụng di động y tế
Analytics Platforms: Source data cho phân tích y tế
Kết luận
hapi-fhir-jpaserver-base là một thành phần mạnh mẽ trong hệ sinh thái HAPI FHIR, cung cấp giải pháp toàn diện để lưu trữ và quản lý FHIR resources. Với hỗ trợ đầy đủ cho FHIR R5 và các tính năng như: persistence, search nâng cao, subscriptions, terminology support và interceptor framework, thư viện này là nền tảng lý tưởng cho việc xây dựng các ứng dụng y tế tuân thủ FHIR.
Kiến trúc module và khả năng tùy biến cao cho phép các nhà phát triển điều chỉnh server theo nhu cầu cụ thể của họ, từ các triển khai đơn giản đến các hệ thống phức tạp, quy mô lớn. Với sự phát triển liên tục từ cộng đồng, hapi-fhir-jpaserver-base tiếp tục cải thiện và mở rộng, giữ vị trí là một trong những triển khai FHIR server mã nguồn mở hàng đầu.