Terminology Service
XΓ’y dα»±ng vΓ triα»n khai Terminology Service trong FHIR R5
Triα»n khai mα»t Terminology Service lΓ mα»t thΓ‘ch thα»©c quan trα»ng trong hα» sinh thΓ‘i FHIR. BΓ i viαΊΏt nΓ y sαΊ½ hΖ°α»ng dαΊ«n chi tiαΊΏt vα» cΓ‘ch xΓ’y dα»±ng, triα»n khai vΓ tα»i Ζ°u hΓ³a mα»t Terminology Service theo chuαΊ©n FHIR R5, bao gα»m cΓ‘c chiαΊΏn lược quαΊ£n lΓ½ hiα»u quαΊ£ vΓ kαΊΏt nα»i vα»i cΓ‘c dα»ch vα»₯ bΓͺn ngoΓ i.
1. XΓ’y dα»±ng FHIR R5 Terminology Service
KiαΊΏn trΓΊc cΖ‘ bαΊ£n
Mα»t Terminology Service hoΓ n chα»nh trong FHIR R5 cαΊ§n hα» trợ cΓ‘c thΓ nh phαΊ§n sau:
ββββββββββββββββββββββββββββββββββββββββββββββββββ
β FHIR Terminology Server β
ββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββ ββββββββββββββββββββ β
β β REST API β β Terminology β β
β β Endpoints ββββββββΊβ Operations β β
β βββββββββββββββ ββββββββββββββββββββ β
β β² β² β
β β β β
β βΌ βΌ β
β βββββββββββββββ ββββββββββββββββββββ β
β β Resource β β Terminology β β
β β Management ββββββββΊβ Storage β β
β βββββββββββββββ ββββββββββββββββββββ β
β β² β² β
β β β β
β βΌ βΌ β
β βββββββββββββββ ββββββββββββββββββββ β
β β Caching β β Pre-expansion β β
β β Layer ββββββββΊβ Storage β β
β βββββββββββββββ ββββββββββββββββββββ β
β β² β² β
β β β β
β βΌ βΌ β
β βββββββββββββββ ββββββββββββββββββββ β
β β External β β Versioning & β β
β β Integrationsβ β History β β
β βββββββββββββββ ββββββββββββββββββββ β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββ
CΓ‘c endpoint REST API cαΊ§n thiαΊΏt
Δα» tuΓ’n thα»§ FHIR R5, bαΊ‘n cαΊ§n triα»n khai cΓ‘c endpoint sau:
Resource endpoints - Cho phΓ©p CRUD (Create, Read, Update, Delete) cΓ‘c resource:
/CodeSystem
/ValueSet
/ConceptMap
/NamingSystem
Operation endpoints - HỠtrợ cÑc thao tÑc Terminology:
/CodeSystem/$lookup
/CodeSystem/$validate-code
/CodeSystem/$subsumes
/CodeSystem/$find-matches
/CodeSystem/$closure
/ValueSet/$expand
/ValueSet/$validate-code
/ConceptMap/$translate
Triα»n khai bαΊ±ng Java vα»i HAPI FHIR
DΖ°α»i ΔΓ’y lΓ vΓ dα»₯ triα»n khai cΖ‘ bαΊ£n sα» dα»₯ng HAPI FHIR:
@Component
public class TerminologyProviderR5 implements ITerminologyServiceR5 {
private final IValidationSupport validationSupport;
private final FhirContext fhirContext;
private final TerminologyCacheService cacheService;
public TerminologyProviderR5(FhirContext fhirContext,
IValidationSupport validationSupport,
TerminologyCacheService cacheService) {
this.fhirContext = fhirContext;
this.validationSupport = validationSupport;
this.cacheService = cacheService;
}
@Override
public ValueSetExpansionOutcome expandValueSet(FhirContext theFhirContext,
ValueSetExpansionOptions theOptions,
IBaseResource theValueSetToExpand) {
// Kiα»m tra cache trΖ°α»c
String cacheKey = createCacheKey(theValueSetToExpand, theOptions);
ValueSetExpansionOutcome cachedExpansion = cacheService.getExpansion(cacheKey);
if (cachedExpansion != null) {
return cachedExpansion;
}
// NαΊΏu khΓ΄ng cΓ³ trong cache, thα»±c hiα»n expand
ValueSetExpansionOutcome expansion = validationSupport.expandValueSet(theOptions, theValueSetToExpand);
// LΖ°u kαΊΏt quαΊ£ vΓ o cache
cacheService.cacheExpansion(cacheKey, expansion);
return expansion;
}
@Override
public LookupCodeResult lookupCode(FhirContext theFhirContext,
String theSystem,
String theCode,
String theDisplayLanguage) {
// Triα»n khai logic lookup
return validationSupport.lookupCode(theFhirContext, theSystem, theCode, theDisplayLanguage);
}
// Triα»n khai cΓ‘c phΖ°Ζ‘ng thα»©c khΓ‘c theo chuαΊ©n R5
}
Triα»n khai bαΊ±ng .NET vα»i Firely SDK
VΓ dα»₯ sα» dα»₯ng Firely SDK (.NET):
public class TerminologyServiceR5 : ITerminologyService
{
private readonly IFhirTerminologyStore _terminologyStore;
private readonly IMemoryCache _cache;
private readonly ILogger<TerminologyServiceR5> _logger;
public TerminologyServiceR5(
IFhirTerminologyStore terminologyStore,
IMemoryCache cache,
ILogger<TerminologyServiceR5> logger)
{
_terminologyStore = terminologyStore;
_cache = cache;
_logger = logger;
}
public async Task<ValueSet> ExpandValueSetAsync(ValueSet valueSet, ExpandOptions options)
{
var cacheKey = $"expand:{valueSet.Url}:{valueSet.Version}:{options}";
if (_cache.TryGetValue(cacheKey, out ValueSet cachedResult))
{
return cachedResult;
}
var expandedVs = await _terminologyStore.ExpandValueSetAsync(valueSet, options);
var cacheOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromHours(2))
.SetSlidingExpiration(TimeSpan.FromMinutes(30));
_cache.Set(cacheKey, expandedVs, cacheOptions);
return expandedVs;
}
// Triα»n khai cΓ‘c operation khΓ‘c
}
2. Hosting vΓ quαΊ£n lΓ½ Code Systems
Chiến lược lưu trữ
CΓ³ ba phΖ°Ζ‘ng phΓ‘p chΓnh Δα» lΖ°u trα»― vΓ quαΊ£n lΓ½ CodeSystem vΓ ValueSet:
1. LΖ°u trα»― dΖ°α»i dαΊ‘ng FHIR Resources
@Service
public class FhirResourceTerminologyStore implements ITerminologyStore {
private final IFhirResourceDao<CodeSystem> codeSystemDao;
private final IFhirResourceDao<ValueSet> valueSetDao;
private final IFhirResourceDao<ConceptMap> conceptMapDao;
@Override
public CodeSystem getCodeSystem(String url, String version) {
SearchParameterMap map = new SearchParameterMap();
map.add(CodeSystem.SP_URL, new UriParam(url));
if (version != null) {
map.add(CodeSystem.SP_VERSION, new TokenParam(version));
}
IBundleProvider results = codeSystemDao.search(map);
if (results.size() == 0) {
return null;
}
return (CodeSystem) results.getResources(0, 1).get(0);
}
// Triα»n khai cΓ‘c phΖ°Ζ‘ng thα»©c khΓ‘c
}
2. Sα» dα»₯ng cΖ‘ sα» dα»― liα»u chuyΓͺn dα»₯ng
@Repository
public class SpecializedTerminologyRepository implements ITerminologyRepository {
private final JdbcTemplate jdbcTemplate;
@Override
public List<ConceptDto> findConceptsByCodeSystem(String codeSystemUrl, String filter) {
String sql = "SELECT code, display, definition " +
"FROM terminology_concept " +
"WHERE code_system_url = ? AND " +
"(LOWER(display) LIKE ? OR LOWER(code) LIKE ?)";
String filterPattern = "%" + filter.toLowerCase() + "%";
return jdbcTemplate.query(sql,
new Object[] { codeSystemUrl, filterPattern, filterPattern },
(rs, rowNum) -> new ConceptDto(
rs.getString("code"),
rs.getString("display"),
rs.getString("definition")
)
);
}
// CΓ‘c phΖ°Ζ‘ng thα»©c truy vαΊ₯n khΓ‘c
}
3. Kết hợp cả hai phưƑng phÑp
@Service
public class HybridTerminologyStore implements ITerminologyStore {
private final FhirResourceTerminologyStore resourceStore;
private final SpecializedTerminologyRepository specializedRepo;
@Override
public List<Concept> expandValueSet(String valueSetUrl, String filter) {
// LαΊ₯y Δα»nh nghΔ©a ValueSet tα»« FHIR Resource store
ValueSet valueSet = resourceStore.getValueSet(valueSetUrl, null);
if (valueSet == null) {
throw new ResourceNotFoundException("ValueSet not found: " + valueSetUrl);
}
// Sα» dα»₯ng kho lΖ°u trα»― chuyΓͺn dα»₯ng Δα» mα» rα»ng nhanh
return specializedRepo.expandValueSet(valueSet, filter);
}
// CΓ‘c phΖ°Ζ‘ng thα»©c khΓ‘c
}
ChiαΊΏn lược tαΊ£i vΓ cαΊp nhαΊt
Viα»c quαΊ£n lΓ½ CodeSystem lα»n nhΖ° SNOMED CT, LOINC ΔΓ²i hα»i chiαΊΏn lược:
@Service
public class CodeSystemLoaderService {
private final ITerminologyStore terminologyStore;
private final IFhirResourceDao<CodeSystem> codeSystemDao;
@Transactional
public void loadSnomedCt(InputStream rfFile, String version) {
try {
// 1. TαΊ‘o CodeSystem resource cΖ‘ bαΊ£n
CodeSystem cs = new CodeSystem();
cs.setUrl("http://snomed.info/sct");
cs.setVersion(version);
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
// LΖ°u metadata trΖ°α»c
IIdType resourceId = codeSystemDao.create(cs).getId();
// 2. TαΊ£i dα»― liα»u tα»« file RF2 cα»§a SNOMED
SnomedRf2Reader reader = new SnomedRf2Reader(rfFile);
List<SnomedConcept> concepts = reader.readConcepts();
// 3. Xα» lΓ½ theo batch Δα» trΓ‘nh quΓ‘ tαΊ£i bα» nhα»
BatchProcessingUtils.processBatch(concepts, 10000,
(conceptBatch) -> {
terminologyStore.saveSnomedConcepts(resourceId.getIdPart(), conceptBatch);
}
);
// 4. TαΊ‘o bαΊ£ng closure Δα» tα»i Ζ°u cΓ‘c truy vαΊ₯n phΓ’n cαΊ₯p
terminologyStore.buildClosureTable(resourceId.getIdPart());
} catch (Exception e) {
throw new TerminologyLoadException("Failed to load SNOMED CT", e);
}
}
}
QuαΊ£n lΓ½ phiΓͺn bαΊ£n
PhiΓͺn bαΊ£n R5 ΔαΊ·c biα»t chΓΊ trα»ng ΔαΊΏn quαΊ£n lΓ½ phiΓͺn bαΊ£n:
@Service
public class CodeSystemVersionManager {
private final IFhirResourceDao<CodeSystem> codeSystemDao;
private final ITerminologyStore specializedStore;
@Transactional
public void publishNewVersion(String codeSystemUrl, String newVersion,
List<CodeChange> changes) {
// 1. LαΊ₯y phiΓͺn bαΊ£n hiα»n tαΊ‘i
CodeSystem currentCs = getCurrentCodeSystem(codeSystemUrl);
// 2. TαΊ‘o mα»t bαΊ£n sao cho phiΓͺn bαΊ£n mα»i
CodeSystem newCs = cloneCodeSystem(currentCs);
newCs.setVersion(newVersion);
newCs.setDate(new Date());
// 3. ThΓͺm tham chiαΊΏu ΔαΊΏn phiΓͺn bαΊ£n trΖ°α»c
RelatedArtifact related = new RelatedArtifact();
related.setType(RelatedArtifact.RelatedArtifactType.PREDECESSOR);
related.setResource(codeSystemUrl + "|" + currentCs.getVersion());
newCs.addRelatedArtifact(related);
// 4. LΖ°u CodeSystem mα»i
IIdType newCsId = codeSystemDao.create(newCs).getId();
// 5. Γp dα»₯ng cΓ‘c thay Δα»i vΓ o kho lΖ°u trα»― chuyΓͺn dα»₯ng
specializedStore.applyCodeSystemChanges(newCsId.getIdPart(), changes);
// 6. CαΊp nhαΊt bαΊ£ng closure nαΊΏu cαΊ§n
if (changes.stream().anyMatch(c -> c.getType() == ChangeType.HIERARCHY)) {
specializedStore.rebuildClosureTable(newCsId.getIdPart());
}
}
}
3. Chiến lược Pre-expansion
Pre-expansion (mα» rα»ng trΖ°α»c) lΓ kα»Ή thuαΊt quan trα»ng Δα» tα»i Ζ°u hiα»u suαΊ₯t, ΔαΊ·c biα»t vα»i ValueSet lα»n vΓ phα»©c tαΊ‘p.
XΓ‘c Δα»nh ValueSet cαΊ§n pre-expand
@Component
public class PreExpansionService {
private final IFhirResourceDao<ValueSet> valueSetDao;
private final ITerminologyOperations terminologyOps;
private final TerminologyStorageService storageService;
@Scheduled(cron = "0 0 2 * * *") // ChαΊ‘y lΓΊc 2 giα» sΓ‘ng hΓ ng ngΓ y
public void schedulePreExpansions() {
// 1. TΓ¬m tαΊ₯t cαΊ£ ValueSet thΖ°α»ng Δược sα» dα»₯ng
List<ValueSet> frequentlyUsedVs = findFrequentlyUsedValueSets();
// 2. Mα» rα»ng tα»«ng ValueSet vΓ lΖ°u trα»―
for (ValueSet vs : frequentlyUsedVs) {
preExpandValueSet(vs.getUrl(), vs.getVersion());
}
}
public void preExpandValueSet(String url, String version) {
try {
ValueSet vs = valueSetDao.findByUrlAndVersion(url, version);
// Thα»±c hiα»n expand vα»i cΓ‘c parameter mαΊ·c Δα»nh
ValueSetExpansionParameters params = new ValueSetExpansionParameters();
ValueSet expanded = terminologyOps.expandValueSet(vs, params);
// LΖ°u trα»― kαΊΏt quαΊ£ expansion
storageService.savePreExpandedValueSet(expanded);
// Thα»±c hiα»n thΓͺm vα»i cΓ‘c bα» tham sα» phα» biαΊΏn
List<ValueSetExpansionParameters> commonParams = getCommonParameters();
for (ValueSetExpansionParameters param : commonParams) {
expanded = terminologyOps.expandValueSet(vs, param);
storageService.savePreExpandedValueSet(expanded, param);
}
} catch (Exception e) {
// Xα» lΓ½ lα»i vΓ ghi log
}
}
}
LΖ°u trα»― pre-expanded ValueSets
@Repository
public class PreExpandedValueSetRepository {
private final JdbcTemplate jdbcTemplate;
public void saveExpansion(String valueSetUrl, String version,
String parameters, String expansionJson) {
String sql = "INSERT INTO pre_expanded_valuesets " +
"(valueset_url, valueset_version, parameters_hash, parameters, " +
"expansion_json, created_at) VALUES (?, ?, ?, ?, ?, NOW()) " +
"ON CONFLICT (valueset_url, valueset_version, parameters_hash) " +
"DO UPDATE SET expansion_json = ?, created_at = NOW()";
String paramsHash = DigestUtils.md5Hex(parameters);
jdbcTemplate.update(sql, valueSetUrl, version, paramsHash,
parameters, expansionJson, expansionJson);
}
public String findExpansion(String valueSetUrl, String version, String parameters) {
String paramsHash = DigestUtils.md5Hex(parameters);
String sql = "SELECT expansion_json FROM pre_expanded_valuesets " +
"WHERE valueset_url = ? AND " +
"(valueset_version = ? OR valueset_version IS NULL) AND " +
"parameters_hash = ?";
try {
return jdbcTemplate.queryForObject(sql, String.class,
valueSetUrl, version, paramsHash);
} catch (EmptyResultDataAccessException e) {
return null;
}
}
}
Chiến lược nÒng cao
Mα»t sα» chiαΊΏn lược nΓ’ng cao bao gα»m:
PhΓ’n tΓch truy vαΊ₯n Δα» xΓ‘c Δα»nh α»©ng cα» viΓͺn:
@Service
public class ValueSetAnalysisService {
private final RequestLogRepository logRepository;
public List<ValueSetUsageStats> analyzeValueSetUsage(Date startDate, Date endDate) {
// PhΓ’n tΓch log Δα» xΓ‘c Δα»nh ValueSet nΓ o Δược sα» dα»₯ng nhiα»u nhαΊ₯t
return logRepository.getValueSetUsageStats(startDate, endDate);
}
public List<ParameterUsageStats> analyzeParameterUsage(String valueSetUrl) {
// PhΓ’n tΓch cΓ‘c tham sα» expansion phα» biαΊΏn nhαΊ₯t cho ValueSet
return logRepository.getParameterUsageStats(valueSetUrl);
}
}
Delta updates cho ValueSet lα»n:
@Service
public class DeltaUpdateService {
private final PreExpandedValueSetRepository preExpandedRepo;
private final ITerminologyOperations terminologyOps;
public void applyDeltaToExpansion(String valueSetUrl, String oldVersion,
String newVersion, List<ConceptChange> changes) {
// LαΊ₯y bαΊ£n pre-expanded cΕ©
String oldExpansionJson = preExpandedRepo.findExpansion(valueSetUrl, oldVersion, "{}");
if (oldExpansionJson != null) {
// Γp dα»₯ng cΓ‘c thay Δα»i trα»±c tiαΊΏp vΓ o expansion thay vΓ¬ expand lαΊ‘i
String newExpansionJson = applyChangesToExpansion(oldExpansionJson, changes);
// LΖ°u kαΊΏt quαΊ£ mα»i
preExpandedRepo.saveExpansion(valueSetUrl, newVersion, "{}", newExpansionJson);
} else {
// NαΊΏu khΓ΄ng tΓ¬m thαΊ₯y bαΊ£n cΕ©, thα»±c hiα»n expand ΔαΊ§y Δα»§
ValueSet vs = valueSetDao.findByUrlAndVersion(valueSetUrl, newVersion);
ValueSet expanded = terminologyOps.expandValueSet(vs, new ValueSetExpansionParameters());
preExpandedRepo.saveExpansion(valueSetUrl, newVersion, "{}",
FhirContext.forR5().newJsonParser().encodeResourceToString(expanded));
}
}
}
4. CΖ‘ chαΊΏ Caching
Caching lΓ yαΊΏu tα» quan trα»ng Δα» ΔαΊ£m bαΊ£o hiα»u suαΊ₯t cho Terminology Service.
Cache nhiα»u tαΊ§ng
@Configuration
public class TerminologyCacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
// Cache trong bα» nhα» cho cΓ‘c truy vαΊ₯n phα» biαΊΏn
Cache lookupCache = new ConcurrentMapCache("terminology-lookup",
false, 5000);
// Cache trong bα» nhα» cho cΓ‘c expansion nhα»
Cache smallExpansionCache = new ConcurrentMapCache("small-expansion",
false, 500);
// Cache dα»±a trΓͺn Redis cho cΓ‘c expansion lα»n
RedisCache largeExpansionCache = new RedisCache("large-expansion",
redisTemplate(), Duration.ofHours(24));
cacheManager.setCaches(Arrays.asList(
lookupCache, smallExpansionCache, largeExpansionCache
));
return cacheManager;
}
}
Cache key generation
@Component
public class TerminologyCacheKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName()).append(":");
sb.append(method.getName()).append(":");
for (Object param : params) {
if (param instanceof ValueSet) {
ValueSet vs = (ValueSet) param;
sb.append(vs.getUrl()).append("|");
sb.append(vs.getVersion() != null ? vs.getVersion() : "null");
} else if (param instanceof String) {
sb.append(param.toString());
} else if (param instanceof ValueSetExpansionOptions) {
ValueSetExpansionOptions options = (ValueSetExpansionOptions) param;
sb.append(generateOptionsKey(options));
} else {
sb.append(param != null ? param.hashCode() : "null");
}
sb.append(":");
}
return sb.toString();
}
private String generateOptionsKey(ValueSetExpansionOptions options) {
// Create a deterministic key from the options
Map<String, Object> optionsMap = new TreeMap<>();
if (options.getCount() != null) optionsMap.put("count", options.getCount());
if (options.getOffset() != null) optionsMap.put("offset", options.getOffset());
if (options.getFilter() != null) optionsMap.put("filter", options.getFilter());
// Add other options...
return new JSONObject(optionsMap).toString();
}
}
Cache invalidation
@Component
public class TerminologyCacheInvalidator {
private final CacheManager cacheManager;
@EventListener
public void handleCodeSystemUpdate(CodeSystemUpdateEvent event) {
// XΓ‘c Δα»nh cΓ‘c cache cαΊ§n xΓ³a
if (event.getType() == UpdateType.CONTENT) {
// NαΊΏu nα»i dung thay Δα»i, xΓ³a tαΊ₯t cαΊ£ cΓ‘c cache liΓͺn quan
evictAllCaches(event.getCodeSystemUrl());
} else if (event.getType() == UpdateType.METADATA) {
// NαΊΏu chα» metadata thay Δα»i, chα» xΓ³a cache lookup
evictLookupCache(event.getCodeSystemUrl());
}
}
@EventListener
public void handleValueSetUpdate(ValueSetUpdateEvent event) {
// XΓ³a tαΊ₯t cαΊ£ cΓ‘c expansion cache cho ValueSet
evictExpansionCaches(event.getValueSetUrl());
}
public void evictAllCaches(String codeSystemUrl) {
// XΓ³a tαΊ₯t cαΊ£ cache liΓͺn quan ΔαΊΏn CodeSystem
Cache lookupCache = cacheManager.getCache("terminology-lookup");
lookupCache.invalidate();
// XΓ³a cΓ‘c expansion cache vΓ¬ chΓΊng cΓ³ thα» phα»₯ thuα»c vΓ o CodeSystem
evictExpansionCaches(null); // XΓ³a tαΊ₯t cαΊ£
}
public void evictExpansionCaches(String valueSetUrl) {
Cache smallExpansionCache = cacheManager.getCache("small-expansion");
Cache largeExpansionCache = cacheManager.getCache("large-expansion");
if (valueSetUrl == null) {
// XΓ³a tαΊ₯t cαΊ£
smallExpansionCache.invalidate();
largeExpansionCache.invalidate();
} else {
// XΓ³a theo pattern
((ConcurrentMapCache) smallExpansionCache).getNativeCache().keySet().stream()
.filter(k -> k.toString().contains(valueSetUrl))
.forEach(k -> smallExpansionCache.evict(k));
// TΖ°Ζ‘ng tα»± cho largeExpansionCache
}
}
}
Chiến lược nÒng cao
Caching thΓ΄ng minh dα»±a trΓͺn phΓ’n tΓch sα» dα»₯ng:
@Service
public class SmartCachingService {
private final CacheManager cacheManager;
private final TerminologyUsageAnalyzer usageAnalyzer;
@Scheduled(fixedRate = 3600000) // 1 giα»
public void optimizeCacheSettings() {
// PhΓ’n tΓch mαΊ«u sα» dα»₯ng Δα» xΓ‘c Δα»nh kΓch thΖ°α»c cache tα»i Ζ°u
Map<String, CacheStatistics> stats = usageAnalyzer.getUsageStatistics();
for (Map.Entry<String, CacheStatistics> entry : stats.entrySet()) {
String cacheType = getCacheTypeFromUrl(entry.getKey());
CacheStatistics stat = entry.getValue();
if (cacheType.equals("lookup")) {
configureLookupCache(stat);
} else if (cacheType.equals("expansion")) {
configureExpansionCache(stat);
}
}
}
private void configureLookupCache(CacheStatistics stats) {
// Δiα»u chα»nh kΓch thΖ°α»c cache dα»±a trΓͺn hit rate vΓ memory pressure
CustomCache cache = (CustomCache) cacheManager.getCache("terminology-lookup");
if (stats.getHitRate() < 0.5 && stats.getAvgMemoryUsage() > 100000) {
// GiαΊ£m kΓch thΖ°α»c cache nαΊΏu hit rate thαΊ₯p vΓ memory usage cao
cache.resize(cache.getSize() * 8 / 10);
} else if (stats.getHitRate() > 0.8 && stats.getMemoryPressure() < 0.7) {
// TΔng kΓch thΖ°α»c cache nαΊΏu hit rate cao vΓ cΓ²n bα» nhα»
cache.resize(cache.getSize() * 12 / 10);
}
}
}
Caching phΓ’n tΓ‘n vα»i Redis:
@Configuration
public class DistributedCacheConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// Sα» dα»₯ng Snappy compression Δα» giαΊ£m kΓch thΖ°α»c dα»― liα»u
SnappySerializer serializer = new SnappySerializer(new JdkSerializationRedisSerializer());
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
return template;
}
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate<Object, Object> redisTemplate) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(12))
.prefixCacheNameWith("terminology-")
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
redisTemplate.getValueSerializer()));
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
// CαΊ₯u hΓ¬nh riΓͺng cho tα»«ng loαΊ‘i cache
configMap.put("lookup", config.entryTtl(Duration.ofDays(7)));
configMap.put("small-expansion", config.entryTtl(Duration.ofHours(24)));
configMap.put("large-expansion", config.entryTtl(Duration.ofHours(12)));
return RedisCacheManager.builder(redisTemplate.getConnectionFactory())
.cacheDefaults(config)
.withInitialCacheConfigurations(configMap)
.build();
}
}
5. TΓch hợp vα»i External Terminology Services
Nhiα»u tα» chα»©c sα» dα»₯ng dα»ch vα»₯ thuαΊt ngα»― bΓͺn ngoΓ i nhΖ° VSAC, NLM UMLS, hoαΊ·c TerminologyServer.io.
KαΊΏt nα»i vα»i dα»ch vα»₯ bΓͺn ngoΓ i
@Service
public class ExternalTerminologyService {
private final RestTemplate restTemplate;
private final String baseUrl;
private final String apiKey;
public ExternalTerminologyService(
@Value("${terminology.external.baseUrl}") String baseUrl,
@Value("${terminology.external.apiKey}") String apiKey) {
this.baseUrl = baseUrl;
this.apiKey = apiKey;
this.restTemplate = new RestTemplateBuilder()
.setConnectTimeout(Duration.ofSeconds(10))
.setReadTimeout(Duration.ofSeconds(30))
.build();
}
public ValueSet expandValueSet(String valueSetUrl, String filter, Integer count) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + apiKey);
headers.set("Accept", "application/fhir+json");
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(baseUrl)
.path("/ValueSet/$expand")
.queryParam("url", valueSetUrl);
if (filter != null) {
uriBuilder.queryParam("filter", filter);
}
if (count != null) {
uriBuilder.queryParam("count", count);
}
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
uriBuilder.toUriString(),
HttpMethod.GET,
requestEntity,
String.class
);
if (response.getStatusCode().is2xxSuccessful()) {
IParser parser = FhirContext.forR5().newJsonParser();
return parser.parseResource(ValueSet.class, response.getBody());
} else {
throw new TerminologyException("External terminology service returned: "
+ response.getStatusCode());
}
} catch (Exception e) {
throw new TerminologyException("Error connecting to external terminology service", e);
}
}
public ValidationResult validateCode(String valueSetUrl, String code,
String system, String display) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + apiKey);
headers.set("Accept", "application/fhir+json");
headers.setContentType(MediaType.APPLICATION_JSON);
Parameters parameters = new Parameters();
parameters.addParameter().setName("url").setValue(new UriType(valueSetUrl));
if (system != null) {
parameters.addParameter().setName("system").setValue(new UriType(system));
}
parameters.addParameter().setName("code").setValue(new StringType(code));
if (display != null) {
parameters.addParameter().setName("display").setValue(new StringType(display));
}
String requestBody = FhirContext.forR5().newJsonParser().encodeResourceToString(parameters);
HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers);
try {
ResponseEntity<String> response = restTemplate.exchange(
baseUrl + "/ValueSet/$validate-code",
HttpMethod.POST,
requestEntity,
String.class
);
if (response.getStatusCode().is2xxSuccessful()) {
IParser parser = FhirContext.forR5().newJsonParser();
Parameters result = parser.parseResource(Parameters.class, response.getBody());
ValidationResult validationResult = new ValidationResult();
validationResult.setValid(getParameterValueBool(result, "result"));
validationResult.setMessage(getParameterValueString(result, "message"));
validationResult.setDisplay(getParameterValueString(result, "display"));
return validationResult;
} else {
throw new TerminologyException("External service error: " + response.getStatusCode());
}
} catch (Exception e) {
throw new TerminologyException("Error connecting to external service", e);
}
}
// CΓ‘c phΖ°Ζ‘ng thα»©c Helper Δα» trΓch xuαΊ₯t tham sα»
private Boolean getParameterValueBool(Parameters params, String name) {
return params.getParameter(name) != null &&
params.getParameter(name).getValue() instanceof BooleanType ?
((BooleanType) params.getParameter(name).getValue()).getValue() : null;
}
private String getParameterValueString(Parameters params, String name) {
return params.getParameter(name) != null &&
params.getParameter(name).getValue() instanceof StringType ?
((StringType) params.getParameter(name).getValue()).getValue() : null;
}
}
Triα»n khai Proxy Facade
MαΊ«u Proxy Facade giΓΊp cΓ‘ch ly logic α»©ng dα»₯ng vα»i dα»ch vα»₯ bΓͺn ngoΓ i:
@Service
public class TerminologyServiceFacade implements ITerminologyService {
private final LocalTerminologyService localService;
private final ExternalTerminologyService externalService;
private final TerminologyConfigService configService;
@Override
public ValueSet expandValueSet(String valueSetUrl, ValueSetExpansionOptions options) {
// Kiα»m tra xem ValueSet nΓ y nΓͺn Δược xα» lΓ½ cα»₯c bα» hay bα»i dα»ch vα»₯ bΓͺn ngoΓ i
if (shouldUseLocalService(valueSetUrl)) {
return localService.expandValueSet(valueSetUrl, options);
} else {
return externalService.expandValueSet(valueSetUrl,
options.getFilter(),
options.getCount());
}
}
@Override
public ValidationResult validateCode(String valueSetUrl, String code,
String system, String display) {
if (shouldUseLocalService(valueSetUrl, system)) {
return localService.validateCode(valueSetUrl, code, system, display);
} else {
return externalService.validateCode(valueSetUrl, code, system, display);
}
}
private boolean shouldUseLocalService(String valueSetUrl) {
// Kiα»m tra cαΊ₯u hΓ¬nh Δα» quyαΊΏt Δα»nh dΓΉng dα»ch vα»₯ nΓ o
return configService.isLocallyManaged(valueSetUrl);
}
private boolean shouldUseLocalService(String valueSetUrl, String system) {
return configService.isLocallyManaged(valueSetUrl) ||
configService.isLocallyManaged(system);
}
}
Federated Terminology Service
Mα»t cαΊ₯p Δα» tΓch hợp cao hΖ‘n lΓ dα»ch vα»₯ thuαΊt ngα»― liΓͺn kαΊΏt (federated):
@Service
public class FederatedTerminologyService implements ITerminologyService {
private final List<TerminologyServiceProvider> providers;
private final TerminologyRoutingService routingService;
public FederatedTerminologyService(
List<TerminologyServiceProvider> providers,
TerminologyRoutingService routingService) {
this.providers = providers;
this.routingService = routingService;
}
@Override
public ValueSet expandValueSet(String valueSetUrl, ValueSetExpansionOptions options) {
// XΓ‘c Δα»nh nhΓ cung cαΊ₯p dα»ch vα»₯ phΓΉ hợp nhαΊ₯t
TerminologyServiceProvider provider = routingService.selectProviderForValueSet(valueSetUrl);
try {
return provider.expandValueSet(valueSetUrl, options);
} catch (Exception e) {
// Thα» vα»i nhΓ cung cαΊ₯p dα»± phΓ²ng nαΊΏu cΓ³ lα»i
TerminologyServiceProvider fallbackProvider = routingService.getFallbackProvider(valueSetUrl);
if (fallbackProvider != null && !fallbackProvider.equals(provider)) {
return fallbackProvider.expandValueSet(valueSetUrl, options);
}
throw new TerminologyException("Failed to expand ValueSet: " + valueSetUrl, e);
}
}
@Override
public LookupCodeResult lookupCode(String system, String code) {
// XΓ‘c Δα»nh nhΓ cung cαΊ₯p phΓΉ hợp cho CodeSystem
TerminologyServiceProvider provider = routingService.selectProviderForCodeSystem(system);
try {
return provider.lookupCode(system, code);
} catch (Exception e) {
// Thα» vα»i nhΓ cung cαΊ₯p dα»± phΓ²ng
TerminologyServiceProvider fallbackProvider = routingService.getFallbackProvider(system);
if (fallbackProvider != null && !fallbackProvider.equals(provider)) {
return fallbackProvider.lookupCode(system, code);
}
throw new TerminologyException("Failed to lookup code: " + code + " in system: " + system, e);
}
}
// Triα»n khai cΓ‘c phΖ°Ζ‘ng thα»©c khΓ‘c vα»i cΓΉng logic Δα»nh tuyαΊΏn
}
Δα»ng bα» hΓ³a vα»i dα»ch vα»₯ bΓͺn ngoΓ i
@Service
public class TerminologySynchronizationService {
private final ExternalTerminologyService externalService;
private final LocalTerminologyRepository localRepository;
private final SyncStatusRepository syncStatusRepo;
@Scheduled(cron = "0 0 2 * * *") // 2:00 AM mα»i ngΓ y
public void synchronizeTerminology() {
List<TerminologySync> syncItems = syncStatusRepo.findDueForSync();
for (TerminologySync item : syncItems) {
try {
syncCodeSystem(item.getUrl(), item.getVersion());
// CαΊp nhαΊt trαΊ‘ng thΓ‘i Δα»ng bα»
item.setLastSyncTime(new Date());
item.setStatus(SyncStatus.SUCCESS);
syncStatusRepo.save(item);
} catch (Exception e) {
item.setStatus(SyncStatus.FAILED);
item.setErrorMessage(e.getMessage());
syncStatusRepo.save(item);
// Ghi log vΓ thΓ΄ng bΓ‘o
logSyncFailure(item, e);
}
}
}
private void syncCodeSystem(String url, String version) {
// 1. Kiα»m tra phiΓͺn bαΊ£n mα»i nhαΊ₯t
CodeSystemVersion latestVersion = externalService.getLatestCodeSystemVersion(url);
if (version != null && version.equals(latestVersion.getVersion())) {
// ΔΓ£ lΓ phiΓͺn bαΊ£n mα»i nhαΊ₯t, khΓ΄ng cαΊ§n Δα»ng bα»
return;
}
// 2. TαΊ£i metadata
CodeSystem cs = externalService.getCodeSystem(url, latestVersion.getVersion());
// 3. LΖ°u metadata vΓ o local repository
localRepository.saveCodeSystemMetadata(cs);
// 4. TαΊ£i vΓ lΖ°u dα»― liα»u concept
boolean hasMore = true;
int offset = 0;
int batchSize = 1000;
while (hasMore) {
ConceptBatch batch = externalService.getCodeSystemConcepts(
url, latestVersion.getVersion(), offset, batchSize);
localRepository.saveCodeSystemConcepts(
url, latestVersion.getVersion(), batch.getConcepts());
offset += batchSize;
hasMore = batch.isHasMore();
}
// 5. XΓ’y dα»±ng lαΊ‘i bαΊ£ng closure nαΊΏu cαΊ§n
if (localRepository.supportsHierarchy(url)) {
localRepository.rebuildClosureTable(url, latestVersion.getVersion());
}
}
}
6. ChiαΊΏn lược tα»i Ζ°u hΓ³a hiα»u suαΊ₯t
MΓ΄ hΓ¬nh dα»― liα»u ΔαΊ·c biα»t cho CodeSystem lα»n
Vα»i cΓ‘c hα» thα»ng mΓ£ lα»n nhΖ° SNOMED CT (hΖ‘n 350,000 khΓ‘i niα»m), cαΊ§n mα»t mΓ΄ hΓ¬nh dα»― liα»u Δược tα»i Ζ°u hΓ³a:
@Entity
@Table(name = "snomed_concept")
public class SnomedConcept {
@Id
private String id;
@Column(nullable = false)
private String conceptId;
@Column(nullable = false)
private String term;
@Column
private String fullySpecifiedName;
@Column(nullable = false)
private boolean active;
@ElementCollection
@CollectionTable(name = "snomed_concept_reference",
joinColumns = @JoinColumn(name = "concept_id"))
private Set<String> referencedComponentIds = new HashSet<>();
// CΓ‘c trΖ°α»ng khΓ‘c vΓ getter/setter
}
@Entity
@Table(name = "snomed_relationship")
@IdClass(SnomedRelationshipId.class)
public class SnomedRelationship {
@Id
private String sourceId;
@Id
private String destinationId;
@Id
private String typeId;
@Column(nullable = false)
private boolean active;
@Column(name = "relationship_group")
private int relationshipGroup;
// CΓ‘c trΖ°α»ng khΓ‘c vΓ getter/setter
}
@Entity
@Table(name = "snomed_transitive_closure")
@IdClass(SnomedClosureId.class)
public class SnomedTransitiveClosure {
@Id
private String sourceId;
@Id
private String destinationId;
@Column(nullable = false)
private int depth;
// Getter/setter
}
Tα»i Ζ°u hΓ³a truy vαΊ₯n
@Repository
public class OptimizedTerminologyRepository {
@PersistenceContext
private EntityManager entityManager;
public List<ConceptDto> searchSnomedConcepts(String term, String ecl, int offset, int limit) {
// Sα» dα»₯ng truy vαΊ₯n SQL native hoαΊ·c JPQL tα»i Ζ°u
if (ecl != null && !ecl.isEmpty()) {
// NαΊΏu cΓ³ ECL (Expression Constraint Language), sα» dα»₯ng phΓ’n tΓch ECL
SnomedEclParser eclParser = new SnomedEclParser();
EclExpression expression = eclParser.parse(ecl);
return executeEclQuery(expression, term, offset, limit);
} else {
// NαΊΏu chα» tΓ¬m kiαΊΏm theo text, sα» dα»₯ng full-text search
return executeFullTextQuery(term, offset, limit);
}
}
private List<ConceptDto> executeFullTextQuery(String term, int offset, int limit) {
// Sα» dα»₯ng Full-Text Search vα»i MySQL, PostgreSQL hoαΊ·c Elasticsearch
String sql = "SELECT c.concept_id, c.term, c.fully_specified_name, c.active " +
"FROM snomed_concept c " +
"WHERE MATCH(c.term, c.fully_specified_name) AGAINST(:term IN BOOLEAN MODE) " +
"AND c.active = true " +
"ORDER BY MATCH(c.term) AGAINST(:term IN BOOLEAN MODE) DESC " +
"LIMIT :limit OFFSET :offset";
Query query = entityManager.createNativeQuery(sql)
.setParameter("term", term)
.setParameter("offset", offset)
.setParameter("limit", limit);
List<Object[]> results = query.getResultList();
// Chuyα»n Δα»i kαΊΏt quαΊ£ thΓ nh DTO
return results.stream()
.map(row -> new ConceptDto(
(String) row[0], // conceptId
(String) row[1], // term
(String) row[2], // fullySpecifiedName
(Boolean) row[3] // active
))
.collect(Collectors.toList());
}
private List<ConceptDto> executeEclQuery(EclExpression expression,
String term, int offset, int limit) {
// Logic phα»©c tαΊ‘p Δα» chuyα»n ECL thΓ nh truy vαΊ₯n SQL
// ...
return conceptResults;
}
}
Tα»i Ζ°u hΓ³a bαΊ£ng closure
BαΊ£ng closure lΓ quan trα»ng Δα» tα»i Ζ°u cΓ‘c truy vαΊ₯n phΓ’n cαΊ₯p, nhΖ°ng cΓ³ thα» rαΊ₯t lα»n:
@Service
public class ClosureTableOptimizer {
private final EntityManager entityManager;
@Transactional
public void buildOptimizedClosureTable(String codeSystemId) {
// 1. TαΊ‘o bαΊ£ng tαΊ‘m thα»i nαΊΏu cαΊ§n
entityManager.createNativeQuery(
"CREATE TEMPORARY TABLE IF NOT EXISTS temp_closure " +
"(source_id VARCHAR(50), destination_id VARCHAR(50), depth INT, " +
"PRIMARY KEY (source_id, destination_id))"
).executeUpdate();
// 2. LαΊ₯p ΔαΊ§y vα»i cΓ‘c quan hα» trα»±c tiαΊΏp (Δα» sΓ’u = 1)
entityManager.createNativeQuery(
"INSERT INTO temp_closure " +
"SELECT source_id, destination_id, 1 FROM terminology_relationship " +
"WHERE code_system_id = :csId AND relationship_type = 'is-a' AND active = true"
).setParameter("csId", codeSystemId)
.executeUpdate();
// 3. LαΊ·p lαΊ‘i Δα» tΓnh toΓ‘n closure Δα» quy
boolean hasMore = true;
int currentDepth = 1;
int maxDepth = 30; // giα»i hαΊ‘n Δα» trΓ‘nh vΓ²ng lαΊ·p vΓ΄ hαΊ‘n
while (hasMore && currentDepth < maxDepth) {
// ThΓͺm cΓ‘c quan hα» giΓ‘n tiαΊΏp tαΊ‘i Δα» sΓ’u tiαΊΏp theo
int inserted = entityManager.createNativeQuery(
"INSERT IGNORE INTO temp_closure " +
"SELECT a.source_id, b.destination_id, :newDepth " +
"FROM temp_closure a " +
"JOIN temp_closure b ON a.destination_id = b.source_id " +
"WHERE a.depth = :currentDepth AND b.depth = 1"
)
.setParameter("currentDepth", currentDepth)
.setParameter("newDepth", currentDepth + 1)
.executeUpdate();
currentDepth++;
hasMore = inserted > 0;
}
// 4. CαΊp nhαΊt bαΊ£ng closure chΓnh vα»i dα»― liα»u mα»i tΓnh toΓ‘n
entityManager.createNativeQuery("TRUNCATE TABLE terminology_closure").executeUpdate();
entityManager.createNativeQuery(
"INSERT INTO terminology_closure (code_system_id, source_id, destination_id, depth) " +
"SELECT :csId, source_id, destination_id, depth FROM temp_closure"
).setParameter("csId", codeSystemId)
.executeUpdate();
// 5. XΓ³a bαΊ£ng tαΊ‘m
entityManager.createNativeQuery("DROP TEMPORARY TABLE temp_closure").executeUpdate();
}
}
Tα»i Ζ°u hΓ³a expand ValueSet phα»©c tαΊ‘p
@Service
public class OptimizedValueSetExpander {
private final TerminologyRepository repository;
public ValueSet expandComplexValueSet(ValueSet valueSet, ValueSetExpansionOptions options) {
// 1. LαΊ₯y ra tαΊ₯t cαΊ£ cΓ‘c include vΓ exclude rules
List<ValueSetRule> includeRules = extractRules(valueSet, true);
List<ValueSetRule> excludeRules = extractRules(valueSet, false);
// 2. Thα»±c hiα»n cΓ‘c phΓ©p include trΖ°α»c
Set<ConceptReference> includedConcepts = new HashSet<>();
for (ValueSetRule rule : includeRules) {
Set<ConceptReference> conceptsFromRule = expandRule(rule, options);
includedConcepts.addAll(conceptsFromRule);
}
// 3. Thα»±c hiα»n cΓ‘c phΓ©p exclude
for (ValueSetRule rule : excludeRules) {
Set<ConceptReference> conceptsToExclude = expandRule(rule, options);
includedConcepts.removeIf(includedConcept ->
conceptsToExclude.stream().anyMatch(excludedConcept ->
includedConcept.getSystem().equals(excludedConcept.getSystem()) &&
includedConcept.getCode().equals(excludedConcept.getCode())
)
);
}
// 4. Γp dα»₯ng cΓ‘c bα» lα»c
if (options.getFilter() != null && !options.getFilter().isEmpty()) {
includedConcepts = applyTextFilter(includedConcepts, options.getFilter(),
options.getFilterLanguage());
}
// 5. PhΓ’n trang
List<ConceptReference> paginatedConcepts = applyPagination(
new ArrayList<>(includedConcepts), options.getOffset(), options.getCount());
// 6. XΓ’y dα»±ng kαΊΏt quαΊ£
return buildExpandedValueSet(valueSet, paginatedConcepts,
includedConcepts.size(), options);
}
private Set<ConceptReference> expandRule(ValueSetRule rule, ValueSetExpansionOptions options) {
if (rule.getValueSet() != null) {
// Expand another ValueSet
return expandValueSetReference(rule.getValueSet());
} else if (rule.hasFilter()) {
// Use filter-based expansion
return repository.findConceptsByFilter(rule.getSystem(), rule.getFilter());
} else if (rule.hasConcepts()) {
// Direct concepts
return new HashSet<>(rule.getConcepts());
} else {
// Entire CodeSystem
return repository.findAllConceptsInSystem(rule.getSystem());
}
}
// CÑc phưƑng thức hỠtrợ khÑc
}
7. Monitoring vΓ Logging
Δα» ΔαΊ£m bαΊ£o dα»ch vα»₯ thuαΊt ngα»― hoαΊ‘t Δα»ng hiα»u quαΊ£, cαΊ§n cΓ³ monitoring vΓ logging tα»t:
@Aspect
@Component
public class TerminologyOperationMonitoring {
private final MeterRegistry meterRegistry;
private final Logger logger = LoggerFactory.getLogger(TerminologyOperationMonitoring.class);
@Around("execution(* com.example.terminology.service.*TerminologyService.*(..))")
public Object monitorOperation(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
String operationName = className + "." + methodName;
Timer.Sample sample = Timer.start(meterRegistry);
// Log operation start with parameters
Object[] args = joinPoint.getArgs();
logger.debug("Starting terminology operation: {} with args: {}",
operationName, summarizeArgs(args));
try {
Object result = joinPoint.proceed();
// Record success
sample.stop(Timer.builder("terminology.operation.duration")
.tag("operation", operationName)
.tag("status", "success")
.register(meterRegistry));
meterRegistry.counter("terminology.operation.count",
"operation", operationName,
"status", "success").increment();
// Log result summary
logger.debug("Completed terminology operation: {} with result: {}",
operationName, summarizeResult(result));
return result;
} catch (Throwable e) {
// Record failure
sample.stop(Timer.builder("terminology.operation.duration")
.tag("operation", operationName)
.tag("status", "error")
.tag("error", e.getClass().getSimpleName())
.register(meterRegistry));
meterRegistry.counter("terminology.operation.count",
"operation", operationName,
"status", "error",
"error", e.getClass().getSimpleName()).increment();
// Log error
logger.error("Error in terminology operation: " + operationName, e);
throw e;
}
}
private String summarizeArgs(Object[] args) {
// Logic Δα» tΓ³m tαΊ―t tham sα» mα»t cΓ‘ch an toΓ n
// ...
}
private String summarizeResult(Object result) {
// Logic Δα» tΓ³m tαΊ―t kαΊΏt quαΊ£
// ...
}
}
Dashboard giΓ‘m sΓ‘t
@RestController
@RequestMapping("/admin/terminology")
public class TerminologyMonitoringController {
private final MeterRegistry meterRegistry;
private final TerminologyStatsService statsService;
@GetMapping("/stats")
public TerminologyStats getStats() {
TerminologyStats stats = new TerminologyStats();
// 1. SỠlượng CodeSystem và ValueSet
stats.setCodeSystemCount(statsService.getCodeSystemCount());
stats.setValueSetCount(statsService.getValueSetCount());
// 2. Thα»ng kΓͺ Cache
Map<String, Double> cacheHitRates = new HashMap<>();
List<String> cacheNames = Arrays.asList("lookup-cache", "expand-cache", "validate-cache");
for (String cacheName : cacheNames) {
Double hitRate = meterRegistry.get("cache.gets")
.tag("cache", cacheName)
.tag("result", "hit")
.gauge(n -> n.value());
Double missRate = meterRegistry.get("cache.gets")
.tag("cache", cacheName)
.tag("result", "miss")
.gauge(n -> n.value());
if (hitRate != null && missRate != null && (hitRate + missRate > 0)) {
cacheHitRates.put(cacheName, hitRate / (hitRate + missRate));
}
}
stats.setCacheHitRates(cacheHitRates);
// 3. Operation stats
stats.setOperationCounts(statsService.getOperationCounts());
stats.setAverageOperationDurations(statsService.getAverageOperationDurations());
// 4. Popular CodeSystems/ValueSets
stats.setPopularCodeSystems(statsService.getMostUsedCodeSystems(10));
stats.setPopularValueSets(statsService.getMostUsedValueSets(10));
return stats;
}
@GetMapping("/health")
public Map<String, Object> checkHealth() {
Map<String, Object> health = new HashMap<>();
// Kiα»m tra kαΊΏt nα»i database
health.put("database", statsService.isDatabaseHealthy());
// Kiα»m tra external terminology services
health.put("externalServices", statsService.checkExternalServices());
// Kiα»m tra cache
health.put("cache", statsService.isCacheHealthy());
return health;
}
}
8. HΖ°α»ng dαΊ«n triα»n khai ΔαΊ§y Δα»§
DΖ°α»i ΔΓ’y lΓ mα»t quy trΓ¬nh triα»n khai ΔαΊ§y Δα»§ cho Terminology Service:
BΖ°α»c 1: ChuαΊ©n bα» cΖ‘ sα» dα»― liα»u
TαΊ‘o schema database vα»i cΓ‘c bαΊ£ng tα»i Ζ°u:
CodeSystem metadata
Concept storage
ValueSet definitions
Closure tables
Cache tables
Monitoring tables
TαΊ‘o cΓ‘c index cho hiα»u suαΊ₯t tα»t:
Full-text indexes trΓͺn trΖ°α»ng display
Composite indexes cho truy vαΊ₯n phα» biαΊΏn
Index cho cΓ‘c khΓ³a ngoαΊ‘i
BΖ°α»c 2: ThiαΊΏt lαΊp dα»± Γ‘n
TαΊ‘o dα»± Γ‘n Spring Boot hoαΊ·c .NET Core
CαΊ₯u hΓ¬nh FHIR R5 framework (HAPI FHIR hoαΊ·c Firely SDK)
CαΊ₯u hΓ¬nh kαΊΏt nα»i database
CαΊ₯u hΓ¬nh cache (Redis, Caffeine)
CαΊ₯u hΓ¬nh logging vΓ monitoring
BΖ°α»c 3: Triα»n khai cΓ‘c lα»p core
Repository layer Δα» truy cαΊp database
Service layer cho business logic
REST controller layer Δα» phΖ‘i bΓ y API
Cache vΓ pre-expansion services
TΓch hợp vα»i dα»ch vα»₯ bΓͺn ngoΓ i
BΖ°α»c 4: TαΊ£i dα»― liα»u thuαΊt ngα»―
TαΊ‘o scripts Δα» import cΓ‘c CodeSystem phα» biαΊΏn:
SNOMED CT
LOINC
ICD-10
UCUM
RxNorm
TαΊ‘o ValueSets hα»―u Γch:
Common diagnosis
Medication classes
Lab panels
Vital signs
BΖ°α»c 5: ThiαΊΏt lαΊp monitoring vΓ cαΊ£nh bΓ‘o
CαΊ₯u hΓ¬nh Prometheus/Grafana cho metrics
ThiαΊΏt lαΊp cαΊ£nh bΓ‘o cho:
High error rates
Slow operation times
Low cache hit rates
External service failures
BΖ°α»c 6: Load testing vΓ tα»i Ζ°u hΓ³a
TαΊ‘o bα» test cases
Thα»±c hiα»n load testing α» cΓ‘c mα»©c khΓ‘c nhau
Tα»i Ζ°u hΓ³a cΓ‘c bottlenecks
ThiαΊΏt lαΊp auto-scaling nαΊΏu cαΊ§n
KαΊΏt luαΊn
Triα»n khai mα»t Terminology Service theo chuαΊ©n FHIR R5 lΓ mα»t nhiα»m vα»₯ phα»©c tαΊ‘p nhΖ°ng cαΊ§n thiαΊΏt cho hα» thα»ng y tαΊΏ hiα»n ΔαΊ‘i. Vα»i kiαΊΏn trΓΊc ΔΓΊng ΔαΊ―n, chiαΊΏn lược caching thΓ΄ng minh, vΓ tα»i Ζ°u hΓ³a phΓΉ hợp, bαΊ‘n cΓ³ thα» xΓ’y dα»±ng mα»t dα»ch vα»₯ thuαΊt ngα»― mαΊ‘nh mαΊ½ vΓ hiα»u quαΊ£.
CΓ‘c yαΊΏu tα» then chα»t Δα» thΓ nh cΓ΄ng bao gα»m:
KiαΊΏn trΓΊc tα»i Ζ°u: ThiαΊΏt kαΊΏ hα» thα»ng Δα» xα» lΓ½ cΓ‘c bα» dα»― liα»u thuαΊt ngα»― lα»n
Caching thΓ΄ng minh: Sα» dα»₯ng cache nhiα»u tαΊ§ng vΓ pre-expansion
TΓch hợp linh hoαΊ‘t: KαΊΏt hợp cΓ‘c dα»ch vα»₯ thuαΊt ngα»― nα»i bα» vΓ bΓͺn ngoΓ i
Monitoring toΓ n diα»n: ΔαΊ£m bαΊ£o hiα»u suαΊ₯t vΓ Δα» tin cαΊy
TuΓ’n thα»§ chuαΊ©n: Triα»n khai ΔαΊ§y Δα»§ cΓ‘c thao tΓ‘c theo FHIR R5
BαΊ±ng cΓ‘ch tuΓ’n theo hΖ°α»ng dαΊ«n nΓ y, bαΊ‘n cΓ³ thα» xΓ’y dα»±ng mα»t nα»n tαΊ£ng thuαΊt ngα»― mαΊ‘nh mαΊ½ Δα» hα» trợ trao Δα»i dα»― liα»u y tαΊΏ, phΓ’n tΓch lΓ’m sΓ ng, vΓ hα» trợ quyαΊΏt Δα»nh - tαΊ₯t cαΊ£ dα»±a trΓͺn chuαΊ©n FHIR R5 mα»i nhαΊ₯t.
Last updated