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:

  1. Resource endpoints - Cho phΓ©p CRUD (Create, Read, Update, Delete) cΓ‘c resource:

    • /CodeSystem

    • /ValueSet

    • /ConceptMap

    • /NamingSystem

  2. 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:

  1. 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);
    }
}
  1. 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

  1. 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);
        }
    }
}
  1. 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

  1. 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

  2. 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

  1. TαΊ‘o dα»± Γ‘n Spring Boot hoαΊ·c .NET Core

  2. CαΊ₯u hΓ¬nh FHIR R5 framework (HAPI FHIR hoαΊ·c Firely SDK)

  3. CαΊ₯u hΓ¬nh kαΊΏt nα»‘i database

  4. CαΊ₯u hΓ¬nh cache (Redis, Caffeine)

  5. CαΊ₯u hΓ¬nh logging vΓ  monitoring

BΖ°α»›c 3: Triển khai cΓ‘c lα»›p core

  1. Repository layer để truy cαΊ­p database

  2. Service layer cho business logic

  3. REST controller layer để phΖ‘i bΓ y API

  4. Cache vΓ  pre-expansion services

  5. TΓ­ch hợp vα»›i dα»‹ch vα»₯ bΓͺn ngoΓ i

BΖ°α»›c 4: TαΊ£i dα»― liệu thuαΊ­t ngα»―

  1. TαΊ‘o scripts để import cΓ‘c CodeSystem phα»• biαΊΏn:

    • SNOMED CT

    • LOINC

    • ICD-10

    • UCUM

    • RxNorm

  2. 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

  1. CαΊ₯u hΓ¬nh Prometheus/Grafana cho metrics

  2. 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

  1. TαΊ‘o bα»™ test cases

  2. Thα»±c hiện load testing ở cΓ‘c mα»©c khΓ‘c nhau

  3. Tα»‘i Ζ°u hΓ³a cΓ‘c bottlenecks

  4. 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:

  1. 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

  2. Caching thΓ΄ng minh: Sα»­ dα»₯ng cache nhiều tαΊ§ng vΓ  pre-expansion

  3. 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

  4. Monitoring toΓ n diện: Đảm bαΊ£o hiệu suαΊ₯t vΓ  Δ‘α»™ tin cαΊ­y

  5. 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