Trong hệ thống microservices phức tạp như một ứng dụng y tế dựa trên FHIR R5, việc quản lý cấu hình một cách hiệu quả và khám phá dịch vụ là vô cùng quan trọng. Bài viết này sẽ trình bày cách kết hợp Spring Cloud Config Server sử dụng backend lưu trữ file cục bộ và Eureka Service Discovery để xây dựng một nền tảng vững chắc cho hệ thống y tế.
Tại sao cần Config Server và Eureka trong hệ thống y tế?
Các hệ thống y tế hiện đại sử dụng kiến trúc microservices có những thách thức đặc thù:
- Nhiều microservices riêng biệt - Mỗi service như patient-service, encounter-service cần cấu hình riêng và khả năng định vị các dịch vụ khác
- Môi trường đa dạng - Từ development, testing, staging đến production
- Thông tin nhạy cảm - Thông tin kết nối database, credentials API cần được bảo vệ
- Cập nhật linh hoạt - Khả năng cập nhật cấu hình mà không cần triển khai lại dịch vụ
- Định vị dịch vụ động - Khả năng tự động phát hiện vị trí của các dịch vụ khác trong mạng
Spring Cloud Config Server và Eureka cùng nhau giải quyết tất cả những vấn đề này.
Phần 1: Thiết lập Config Server với File System Backend
1. Tạo dự án Config Server
Đầu tiên, tạo một dự án Spring Boot với dependency Spring Cloud Config Server:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
2. Cấu hình application.yml với File System Backend
Thay vì sử dụng Git repository, chúng ta sẽ cấu hình Config Server để đọc cấu hình từ file system:
server:
port: 8888
spring:
application:
name: config-server
profiles:
active: native # Kích hoạt native profile để sử dụng file system
cloud:
config:
server:
native:
search-locations:
- file:./config-repo
- file:./config-repo/{application}
- file:./config-repo/{application}/{profile}
security:
user:
name: ${CONFIG_SERVER_USERNAME:configuser}
password: ${CONFIG_SERVER_PASSWORD:configpassword}
# Cấu hình Eureka Client
eureka:
client:
serviceUrl:
defaultZone: http://${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}/eureka/
register-with-eureka: true
fetch-registry: true
instance:
prefer-ip-address: true
lease-renewal-interval-in-seconds: 10
metadata-map:
management.context-path: /actuator
management:
endpoints:
web:
exposure:
include: health,info,refresh
endpoint:
health:
show-details: when_authorized
3. Kích hoạt Config Server trong class chính
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
4. Tạo cấu trúc thư mục và file cấu hình
Tạo thư mục config-repo trong thư mục gốc của dự án Config Server với cấu trúc như sau:
config-repo/
├── application.yml # Cấu hình chung cho tất cả dịch vụ
├── terminology-service/
│ ├── terminology-service.yml # Cấu hình mặc định
│ ├── terminology-service-dev.yml # Cấu hình môi trường development
│ └── terminology-service-prod.yml # Cấu hình môi trường production
└── ...các service khác...
5. Cấu hình bảo mật
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/actuator/health/**").permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
}
Phần 2: Ví dụ file cấu hình trong file system
1. Ví dụ file cấu hình chung (application.yml)
# config-repo/application.yml
eureka:
client:
serviceUrl:
defaultZone: http://${EUREKA_USERNAME:eurekauser}:${EUREKA_PASSWORD:eurekapassword}@${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}/eureka/
instance:
prefer-ip-address: true
management:
endpoints:
web:
exposure:
include: health,info,refresh
endpoint:
health:
show-details: when-authorized
spring:
sleuth:
sampler:
probability: 1.0
zipkin:
base-url: http://zipkin:9411
logging:
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
level:
root: INFO
org.springframework.web: INFO
com.healthcare: DEBUG
2. Ví dụ cấu hình patient-service
# config-repo/terminology-service/terminology-service.yml
spring:
datasource:
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: none
show-sql: false
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
fhir:
version: R5
resource:
patient:
validation:
enabled: true
extensions:
ethnicity:
url: http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity
birthsex:
url: http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex
server:
port: 8101
# config-repo/terminology-service/terminology-service-dev.yml
spring:
data:
mongodb:
uri: mongodb://${MONGODB_HOST:localhost}:${MONGODB_PORT:27017}/terminology_dev
username: ${MONGODB_USERNAME:admin}
password: ${MONGODB_PASSWORD:admin}
logging:
level:
com.healthcare.patient: DEBUG
org.hibernate.SQL: DEBUG
fhir:
resource:
patient:
validation:
severity: WARNING
# config-repo/patient-service/terminology-service-prod.yml
spring:
datasource:
url: jdbc:postgresql://postgres:5432/terminology_prod
username: ${PATIENT_DB_USERNAME}
password: ${PATIENT_DB_PASSWORD}
hikari:
maximum-pool-size: 20
minimum-idle: 5
logging:
level:
com.healthcare.patient: INFO
org.hibernate.SQL: INFO
fhir:
resource:
patient:
validation:
severity: ERROR
Phần 3: Kết nối các Service với Config Server và Eureka
1. Thêm dependencies vào service client
Các service client cần thêm dependencies sau để kết nối với Config Server và Eureka:
<dependencies>
<!-- Config Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
</dependencies>
2. Cấu hình bootstrap.yml cho service client
# bootstrap.yml
spring:
application:
name: terminology-service
cloud:
config:
uri: http://${CONFIG_SERVER_HOST:localhost}:${CONFIG_SERVER_PORT:8888}
fail-fast: true
username: ${CONFIG_CLIENT_USERNAME:configuser}
password: ${CONFIG_CLIENT_PASSWORD:configpassword}
profiles:
active: ${SPRING_PROFILES_ACTIVE:dev}
# Cấu hình Eureka client nếu Config Server không hoạt động
eureka:
client:
serviceUrl:
defaultZone: http://${EUREKA_USERNAME:eurekauser}:${EUREKA_PASSWORD:eurekapassword}@${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}/eureka/
3. Kích hoạt Eureka Client trong class chính
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class TerminologyServiceApplication {
public static void main(String[] args) {
SpringApplication.run(PatientServiceApplication.class, args);
}
}
Phần 5: Triển khai và Mở rộng
1. Triển khai trên Docker Compose
version: '3.8'
services:
config-server:
build: ./config-server
container_name: config-server
ports:
- "8888:8888"
volumes:
- ./config-repo:/app/config-repo # Mount thư mục cấu hình vào container
environment:
- CONFIG_SERVER_USERNAME=configuser
- CONFIG_SERVER_PASSWORD=securepassword
- SPRING_PROFILES_ACTIVE=native,docker
- EUREKA_HOST=discovery-service
- EUREKA_PORT=8761
- EUREKA_USERNAME=eurekauser
- EUREKA_PASSWORD=eurekapassword
networks:
- healthcare-network
depends_on:
- discovery-service
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8888/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
healthcare-network:
driver: bridge
2. Kubernetes Deployment
Triển khai Config Server trên Kubernetes với ConfigMap để mount file cấu hình:
apiVersion: v1
kind: ConfigMap
metadata:
name: config-repo
namespace: healthcare
data:
"application.yml": |
eureka:
client:
serviceUrl:
defaultZone: http://${EUREKA_USERNAME}:${EUREKA_PASSWORD}@discovery-service:8761/eureka/
instance:
prefer-ip-address: true
# ... nội dung file application.yml ...
"terminology-service.yml": |
spring:
datasource:
driver-class-name: org.postgresql.Driver
# ... nội dung file patient-service.yml ...
"terminology-service-dev.yml": |
spring:
datasource:
url: jdbc:postgresql://postgres:5432/patient_dev
# ... nội dung file patient-service-dev.yml ...
apiVersion: apps/v1
kind: Deployment
metadata:
name: config-server
namespace: healthcare
spec:
replicas: 2
selector:
matchLabels:
app: config-server
template:
metadata:
labels:
app: config-server
spec:
containers:
- name: config-server
image: healthcare/config-server:latest
ports:
- containerPort: 8888
env:
- name: SPRING_PROFILES_ACTIVE
value: "native,kubernetes"
- name: CONFIG_SERVER_USERNAME
valueFrom:
secretKeyRef:
name: config-server-secrets
key: username
- name: CONFIG_SERVER_PASSWORD
valueFrom:
secretKeyRef:
name: config-server-secrets
key: password
volumeMounts:
- name: config-repo-volume
mountPath: /app/config-repo
volumes:
- name: config-repo-volume
configMap:
name: config-repo
Phần 6: Lợi ích và Nhược điểm của File System Backend
Lợi ích
- Đơn giản hóa: Không cần cấu hình Git repository hoặc công cụ quản lý phiên bản
- Performance: Truy cập nhanh hơn vì đọc trực tiếp từ file system
- Kiểm soát trực tiếp: Sửa đổi file cấu hình trực tiếp mà không cần commit
- Không phụ thuộc mạng: Hoạt động offline, không cần kết nối đến Git server
- Tích hợp dễ dàng: Phù hợp với các môi trường containerized như Docker và Kubernetes
Nhược điểm
- Thiếu phiên bản: Không có lịch sử phiên bản như Git repository
- Thiếu collaboration: Không có các tính năng như pull requests, review
- Manual synchronization: Cần đồng bộ hóa thủ công giữa nhiều instance Config Server
- Không có audit trail: Khó theo dõi ai đã thay đổi cấu hình và khi nào
Phần 7: Best Practices
1. Quản lý cấu hình trong Docker
Khi sử dụng Config Server với file system backend trong Docker, nên sử dụng volume để mount thư mục cấu hình từ host:
volumes:
- ./config-repo:/app/config-repo
2. Quản lý cấu hình trong Kubernetes
Trong Kubernetes, có hai cách quản lý cấu hình cho Config Server:
Sử dụng ConfigMap
Phù hợp cho cấu hình không nhạy cảm:
apiVersion: v1
kind: ConfigMap
metadata:
name: config-repo
data:
"application.yml": |
# nội dung file application.yml
"terminology-service.yml": |
# nội dung file patient-service.yml
Sử dụng Persistent Volume
Phù hợp khi cần lưu trữ nhiều file cấu hình hoặc cấu hình lớn:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: config-repo-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
volumeMounts:
- name: config-repo-volume
mountPath: /app/config-repo
volumes:
- name: config-repo-volume
persistentVolumeClaim:
claimName: config-repo-pvc
3. Refresh Configuration
Để refresh cấu hình mà không khởi động lại service:
POST /actuator/refresh
Hoặc sử dụng Spring Cloud Bus để refresh tất cả các service:
POST /actuator/busrefresh
4. Bảo mật thông tin nhạy cảm
Không nên lưu trữ thông tin nhạy cảm trực tiếp trong file cấu hình. Thay vào đó, sử dụng một trong các phương pháp sau:
Sử dụng biến môi trường
spring:
datasource:
username: ${PATIENT_DB_USERNAME}
password: ${PATIENT_DB_PASSWORD}
Mã hóa các thuộc tính nhạy cảm
Sử dụng Spring Cloud Config Server's encryption và decryption tính năng:
encrypt:
key: ${ENCRYPT_KEY}
spring:
datasource:
username: '{cipher}AQA6UNBa...'
password: '{cipher}AQA1kQjd...'
Kết luận
Kết hợp Spring Cloud Config Server với File System Backend và Eureka Service Discovery tạo nên một nền tảng vững chắc cho hệ thống y tế dựa trên FHIR R5 với kiến trúc microservices. Lựa chọn File System Backend giúp đơn giản hóa việc quản lý cấu hình trong các môi trường container hóa như Docker và Kubernetes.
Những lợi ích chính bao gồm:
- Quản lý cấu hình tập trung: Thay đổi cấu hình mà không cần triển khai lại dịch vụ
- Đơn giản hóa triển khai: Sử dụng file system thay vì Git repository
- Service Discovery tự động: Các dịch vụ tự động phát hiện và giao tiếp với nhau
- High Availability: Khả năng mở rộng và chịu lỗi cao
- Bảo mật: Bảo vệ thông tin nhạy cảm
- Giám sát thời gian thực: Theo dõi trạng thái và sức khỏe của các service
Việc triển khai đúng cách Spring Cloud Config Server với File System Backend và Eureka Service Discovery sẽ giúp đơn giản hóa quá trình quản lý, vận hành và mở rộng hệ thống y tế dựa trên FHIR, đồng thời tăng cường tính linh hoạt và độ tin cậy của toàn bộ hệ thống.