Tối Ưu Hóa Gọi API Dịch Vụ Cơ Bản Khi Spring Boot Khởi Động

Trong môi trường kiến trúc microservices, các dịch vụ thường xuyên giao tiếp qua lại thông qua các lời gọi API. Tuy nhiên, đối với những dữ liệu nền tảng ít thay đổi như từ điển hệ thống (dictionary) hay thông tin nhà cung cấp, việc liên tục gọi API mỗi khi cần sử dụng sẽ gây lãng phí tài nguyên và tăng độ trễ. Giải pháp tối ưu là thực hiện gọi dịch vụ này một lần duy nhất ngay khi container Spring khởi động xong, sau đó lưu trữ vào bộ nhớ đệm cục bộ (local cache) để truy xuất nhanh chóng.

Để thực hiện việc này, chúng ta có thể tận dụng sự kiện ApplicationReadyEvent của Spring Boot. Sự kiện này được kích hoạt khi ngữ cảnh ứng dụng đã được refresh và sẵn sàng nhận request. Cách triển khai cụ thể là tạo một class chính implement interface ApplicationListener<ApplicationReadyEvent> và ghi đè phương thức xử lý sự kiện.

Dưới đây là ví dụ về class khởi động chính:


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.ComponentScan;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;

@SpringBootApplication
@ComponentScan(basePackages = "com.example.core")
@Slf4j
@EnableFeignClients
public class SystemBootstrap implements ApplicationListener<ApplicationReadyEvent> {

    public static void main(String[] args) {
        SpringApplication.run(SystemBootstrap.class, args);
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        try {
            // Tải dữ liệu từ điển
            Map<String, DictionaryItem> dictionaryCache = DataRepository.loadDictionaryData();
            log.info("Đã tải {} bản ghi từ điển hệ thống", dictionaryCache.size());

            // Tải dữ liệu nhà cung cấp
            Map<Integer, ProviderDetail> providerCache = DataRepository.loadProviderData(null);
            log.info("Đã tải {} bản ghi thông tin nhà cung cấp", providerCache.size());

            // Cập nhật vào bộ nhớ tĩnh
            dictionaryCache.forEach((key, value) -> DataRepository.CACHE_DICTIONARY.put(key, value));
            providerCache.forEach((key, value) -> DataRepository.CACHE_PROVIDERS.put(key, value));
        } catch (Exception ex) {
            log.error("Lỗi khi khởi tạo dữ liệu nền tảng", ex);
        }
    }
}

Class tiện ích đóng vai trò lưu trữ dữ liệu tĩnh và thực hiện các lời gọi Feign client cần thiết. Do các trường lưu trữ là static, việc inject bean vào class này đòi hỏi một kỹ thuật đặc biệt thông qua constructor.


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Component
@Slf4j
public class DataRepository {

    // Bộ nhớ đệm cho thông tin nhà cung cấp
    public static ConcurrentHashMap<Integer, ProviderDetail> CACHE_PROVIDERS = new ConcurrentHashMap<>();
    
    // Bộ nhớ đệm cho dữ liệu từ điển
    public static ConcurrentHashMap<String, DictionaryItem> CACHE_DICTIONARY = new ConcurrentHashMap<>();
    
    private static IBaseServiceClient baseServiceClient;

    @Autowired
    public DataRepository(IBaseServiceClient client) {
        DataRepository.baseServiceClient = client;
    }

    public static Map<String, DictionaryItem> loadDictionaryData() {
        RequestPayload payload = new RequestPayload();
        payload.setHeader(new MessageHeader());
        Map<String, List<String>> params = new HashMap<>();
        params.put("types", Collections.singletonList("service_discount_type"));
        payload.setBody(params);

        ResponseMessage response = baseServiceClient.fetchDictionary(payload);
        List<DictionaryItem> items = JSONArray.parseArray(
            JSON.toJSONString(
                JSON.parseObject(JSON.toJSONString(response.getBody()), Map.class).get("service_discount_type")
            ),
            DictionaryItem.class
        );

        return items.stream().collect(Collectors.toMap(DictionaryItem::getCode, item -> item, (k1, k2) -> k1));
    }

    public static Map<Integer, ProviderDetail> loadProviderData(Integer providerId) {
        RequestPayload payload = new RequestPayload();
        payload.setHeader(new MessageHeader());
        Map<String, List<String>> params = new HashMap<>();
        
        if (providerId == null) {
            params.put("idList", Collections.emptyList());
        } else {
            params.put("idList", Collections.singletonList(String.valueOf(providerId)));
        }
        payload.setBody(params);

        ResponseMessage response = baseServiceClient.findProvidersById(payload);
        List<ProviderDetail> details = JSONArray.parseArray(
            JSON.toJSONString(response.getBody()), 
            ProviderDetail.class
        );

        return details.stream().collect(Collectors.toMap(ProviderDetail::getId, item -> item, (k1, k2) -> k1));
    }
}

Một lưu ý quan trọng khi làm việc với các class utility chứa biến static trong Spring là annotation @Autowired không thể hoạt động trực tiếp trên trường static. Để khắc phục, chúng ta cần inject dependency thông qua constructor của class đó, sau đó gán giá trị bean nhận được vào biến static. Cách này đảm bảo rằng bean Feign client được khởi tạo đúng quy trình của Spring container trước khi được sử dụng trong các phương thức static.

Thẻ: spring-boot application-ready-event static-injection feign-client microservices-caching

Đăng vào ngày 15 tháng 10 lúc 11:58