Skip to content

Best practices ​

This guide covers best practices for running the Lovrabet Java OpenSDK in production, to help you build fast, highly available applications.


Contents ​

    1. Client management
    1. Security
    1. Performance optimization
    1. Error handling
    1. Logging
    1. Testing strategy
    1. Deployment

1. Client management ​

1.1 Use a singleton ​

LovrabetSDKClient is thread-safe. Create it once as a singleton and reuse it — don't instantiate it on every call.

Java
package com.example.config;

import com.lovrabet.runtime.opensdk.client.LovrabetSDKClient;

/**
 * SDK 客户端单例管理器
 */
public class SdkClientManager {
    private static volatile LovrabetSDKClient instance;

    private SdkClientManager() {
        // 私有构造函数,防止外部实例化
    }

    public static LovrabetSDKClient getInstance() {
        if (instance == null) {
            synchronized (SdkClientManager.class) {
                if (instance == null) {
                    String accessKey = ConfigManager.getAccessKey();
                    String baseUrl = ConfigManager.getBaseUrl();
                    instance = new LovrabetSDKClient(accessKey, baseUrl);
                }
            }
        }
        return instance;
    }
}
Java
// 不推荐:每次调用都创建新实例
public void someMethod() {
    LovrabetSDKClient client = new LovrabetSDKClient(accessKey, baseUrl);
    // ...
}

1.2 Configuration in Spring Boot ​

Let Spring's dependency injection manage the SDK client:

Java
@Configuration
public class LovrabetConfig {

    @Value("${lovrabet.access-key}")
    private String accessKey;

    @Value("${lovrabet.base-url}")
    private String baseUrl;

    @Bean
    public LovrabetSDKClient lovrabetSDKClient() {
        return new LovrabetSDKClient(accessKey, baseUrl);
    }
}
Java
@Service
public class DataService {

    private final LovrabetSDKClient sdkClient;

    // 通过构造函数注入
    public DataService(LovrabetSDKClient sdkClient) {
        this.sdkClient = sdkClient;
    }
}

2. Security ​

2.1 AccessKey management ​

Option 1: environment variables

Java
String accessKey = System.getenv("LOVRABET_ACCESS_KEY");
if (accessKey == null || accessKey.isEmpty()) {
    throw new IllegalStateException("LOVRABET_ACCESS_KEY 环境变量未设置");
}
Bash
# 在系统环境中设置
export LOVRABET_ACCESS_KEY="ak-your-access-key"

# 或者在启动 Java 应用时传递
java -jar app.jar

Option 2: config file + config center

YAML
# application.yml
lovrabet:
  access-key: ${LOVRABET_ACCESS_KEY:}
  base-url: https://runtime.lovrabet.com/openapi
Java
@Value("${lovrabet.access-key}")
private String accessKey;

Option 3: a secrets management service

Java
// 使用云服务商的密钥管理服务
String accessKey = secretManager.getSecret("lovrabet-access-key");
Java
// ❌ 不要硬编码在代码中
String accessKey = "ak-OD8xWhMOsL5ChQ3Akhv4uYYiu1fPOFQGVF9BULIeov8";

// ❌ 不要提交到代码仓库
// git add config.properties  (包含明文 accessKey)

// ❌ 不要在日志中打印
logger.info("AccessKey: " + accessKey);  // 危险!

// ❌ 不要在异常信息中暴露
throw new Exception("AccessKey " + accessKey + " 无效");

2.2 Transport security ​

Java
// ✅ 始终使用 HTTPS
String baseUrl = "https://runtime.lovrabet.com/openapi";

// ❌ 不要使用 HTTP
// String baseUrl = "http://runtime.lovrabet.com/openapi";

2.3 Handling sensitive data ​

Java
// 在日志中脱敏敏感信息
public void logCustomerInfo(Map<String, Object> customer) {
    String phone = (String) customer.get("TEXT_2");
    // 脱敏手机号:138****1234
    String maskedPhone = phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    logger.info("客户电话: {}", maskedPhone);
}

3. Performance optimization ​

3.1 Optimizing paginated queries ​

Choose a sensible pageSize ​

Java
// ✅ 推荐:根据实际需求选择合适的 pageSize
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("pageSize", 50);   // 20-100 之间较为合理
paramMap.put("currentPage", 1);

// ❌ 不推荐:pageSize 过小,导致请求次数过多
paramMap.put("pageSize", 5);

// ❌ 不推荐:pageSize 过大,导致单次响应慢、内存占用高
paramMap.put("pageSize", 10000);

Avoid deep pagination ​

Java
// ❌ 避免查询过深的页码
if (currentPage > 100) {
    logger.warn("页码过深可能影响性能: {}", currentPage);
    // 考虑使用其他方式,如基于游标的分页
}

3.2 Optimizing batch operations ​

Use a thread pool for concurrency ​

Java
import java.util.concurrent.*;

/**
 * 批量创建数据 - 使用线程池
 */
public void batchCreate(List<Map<String, Object>> dataList) {
    // 创建固定大小的线程池
    ExecutorService executor = Executors.newFixedThreadPool(10);
    List<Future<Boolean>> futures = new ArrayList<>();

    for (Map<String, Object> data : dataList) {
        Future<Boolean> future = executor.submit(() -> {
            try {
                LovrabetRequest request = buildRequest(data);
                LovrabetResult<String> result = sdkClient.create(request);
                return result.isSuccess();
            } catch (Exception e) {
                logger.error("创建数据失败", e);
                return false;
            }
        });
        futures.add(future);
    }

    // 等待所有任务完成
    int successCount = 0;
    for (Future<Boolean> future : futures) {
        try {
            if (future.get(30, TimeUnit.SECONDS)) {
                successCount++;
            }
        } catch (Exception e) {
            logger.error("获取任务结果失败", e);
        }
    }

    executor.shutdown();
    logger.info("批量创建完成,成功: {}/{}", successCount, dataList.size());
}

Limit concurrency ​

Java
// ✅ 推荐:控制并发数避免过载服务器
ExecutorService executor = Executors.newFixedThreadPool(10);  // 限制 10 个并发

// ❌ 不推荐:无限制并发可能导致服务器过载
// ExecutorService executor = Executors.newCachedThreadPool();

3.3 Caching strategy ​

For data that rarely changes, cache it locally to cut down on API calls:

Java
import com.github.benmanes.caffeine.cache.*;

/**
 * 带缓存的数据服务
 */
public class CachedDataService {

    private final LovrabetSDKClient sdkClient;

    // 使用 Caffeine 缓存
    private final Cache<String, Map<String, Object>> cache = Caffeine.newBuilder()
        .maximumSize(1000)                 // 最大缓存 1000 条
        .expireAfterWrite(5, TimeUnit.MINUTES)  // 5 分钟后过期
        .build();

    public Map<String, Object> getData(String id) {
        // 先从缓存获取
        return cache.get(id, key -> {
            // 缓存未命中,从 API 获取
            logger.debug("缓存未命中,从 API 获取数据: {}", id);
            return fetchFromApi(id);
        });
    }

    private Map<String, Object> fetchFromApi(String id) {
        LovrabetRequest request = new LovrabetRequest();
        request.setAppCode(appCode);
        request.setModelCode(modelCode);
        request.setId(Long.parseLong(id));

        LovrabetResult<?> result = sdkClient.getOne(request);

        if (result.isSuccess()) {
            return (Map<String, Object>) result.getData();
        }
        return null;
    }

    // 更新数据时清除缓存
    public void updateData(String id, Map<String, Object> data) {
        // 更新 API
        updateApi(id, data);

        // 清除缓存
        cache.invalidate(id);
    }
}

3.4 Connection reuse ​

The SDK already uses an HTTP connection pool internally — no extra setup needed. Just keep this in mind:

Java
// ✅ 推荐:复用 SDK 客户端实例
LovrabetSDKClient client = SdkClientManager.getInstance();

// ❌ 不推荐:频繁创建和销毁客户端
for (int i = 0; i < 1000; i++) {
    LovrabetSDKClient client = new LovrabetSDKClient(accessKey, baseUrl);
    // ...
}

4. Error handling ​

4.1 Centralized error handling ​

Create a single error-handling utility:

Java
package com.example.util;

import com.lovrabet.runtime.opensdk.model.LovrabetResult;

/**
 * SDK 结果处理工具
 */
public class ResultHandler {

    /**
     * 处理结果,失败时抛出异常
     */
    public static <T> T unwrap(LovrabetResult<T> result) {
        if (result == null) {
            throw new LovrabetException("API 响应为空");
        }

        if (result.isSuccess()) {
            return result.getData();
        } else {
            throw new LovrabetException(
                result.getResultCode(),
                result.getResultMsg()
            );
        }
    }

    /**
     * 处理结果,失败时返回默认值
     */
    public static <T> T unwrapOrDefault(LovrabetResult<T> result, T defaultValue) {
        if (result == null || !result.isSuccess()) {
            return defaultValue;
        }
        return result.getData();
    }

    /**
     * 处理结果,失败时返回 Optional
     */
    public static <T> Optional<T> unwrapOptional(LovrabetResult<T> result) {
        if (result != null && result.isSuccess()) {
            return Optional.ofNullable(result.getData());
        }
        return Optional.empty();
    }
}

/**
 * 自定义异常类
 */
public class LovrabetException extends RuntimeException {
    private final String errorCode;

    public LovrabetException(String message) {
        super(message);
        this.errorCode = "UNKNOWN";
    }

    public LovrabetException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }
}

Usage:

Java
// 方式 1: 失败时抛出异常
try {
    Map<String, Object> data = ResultHandler.unwrap(result);
    // 处理数据
} catch (LovrabetException e) {
    logger.error("API 调用失败 [{}]: {}", e.getErrorCode(), e.getMessage());
}

// 方式 2: 失败时使用默认值
List<Map<String, Object>> dataList =
    ResultHandler.unwrapOrDefault(result, Collections.emptyList());

// 方式 3: 使用 Optional
ResultHandler.unwrapOptional(result)
    .ifPresent(data -> processData(data));

4.2 Retry logic ​

Retry transient errors (like network jitter) automatically:

Java
package com.example.util;

import java.util.function.Supplier;

/**
 * 重试工具类
 */
public class RetryHelper {

    /**
     * 带指数退避的重试
     */
    public static <T> LovrabetResult<T> executeWithRetry(
            Supplier<LovrabetResult<T>> operation,
            int maxRetries,
            long initialDelayMs) {

        int attempt = 0;
        long delay = initialDelayMs;

        while (attempt <= maxRetries) {
            try {
                LovrabetResult<T> result = operation.get();

                // 成功则返回
                if (result != null && result.isSuccess()) {
                    if (attempt > 0) {
                        logger.info("重试成功,尝试次数: {}", attempt + 1);
                    }
                    return result;
                }

                // 已达最大重试次数
                if (attempt == maxRetries) {
                    logger.error("达到最大重试次数 {},操作失败", maxRetries);
                    return result;
                }

                // 判断是否需要重试
                if (!shouldRetry(result)) {
                    logger.warn("错误不可重试,直接返回");
                    return result;
                }

                // 等待后重试
                logger.warn("操作失败,{} ms 后进行第 {} 次重试", delay, attempt + 2);
                Thread.sleep(delay);

                // 指数退避:每次重试间隔翻倍
                delay *= 2;
                attempt++;

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("重试被中断", e);
            } catch (Exception e) {
                logger.error("执行操作时发生异常", e);
                if (attempt == maxRetries) {
                    throw new RuntimeException("重试后仍然失败", e);
                }
                attempt++;
            }
        }

        throw new RuntimeException("不应该到达这里");
    }

    /**
     * 判断错误是否可重试
     */
    private static <T> boolean shouldRetry(LovrabetResult<T> result) {
        if (result == null) {
            return true;  // 空结果可重试
        }

        String errorCode = result.getResultCode();

        // 以下错误码不应重试
        if ("INVALID_PARAM".equals(errorCode) ||
            "ACCESS_DENIED".equals(errorCode) ||
            "RESOURCE_NOT_FOUND".equals(errorCode)) {
            return false;
        }

        // 其他错误可重试
        return true;
    }
}

Usage:

Java
// 最多重试 3 次,初始间隔 1000ms,使用指数退避
LovrabetResult<?> result = RetryHelper.executeWithRetry(
    () -> sdkClient.getOne(request),
    3,      // 最多重试 3 次
    1000L   // 初始延迟 1 秒
);

4.3 Timeout handling ​

Put timeouts around long-running operations:

Java
import java.util.concurrent.*;

/**
 * 带超时的操作执行
 */
public <T> LovrabetResult<T> executeWithTimeout(
        Supplier<LovrabetResult<T>> operation,
        long timeoutSeconds) {

    ExecutorService executor = Executors.newSingleThreadExecutor();

    try {
        Future<LovrabetResult<T>> future = executor.submit(operation::get);
        return future.get(timeoutSeconds, TimeUnit.SECONDS);
    } catch (TimeoutException e) {
        logger.error("操作超时,超过 {} 秒", timeoutSeconds);
        throw new RuntimeException("操作超时", e);
    } catch (Exception e) {
        logger.error("操作执行失败", e);
        throw new RuntimeException("操作失败", e);
    } finally {
        executor.shutdown();
    }
}

5. Logging ​

5.1 Structured logging ​

Structured logs are easier to analyze later:

Java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DataService {
    private static final Logger logger = LoggerFactory.getLogger(DataService.class);

    public void createData(Map<String, Object> data) {
        long startTime = System.currentTimeMillis();
        String operation = "createData";

        try {
            logger.info("开始操作 - operation={}, dataSize={}",
                operation, data.size());

            LovrabetResult<String> result = sdkClient.create(request);
            long duration = System.currentTimeMillis() - startTime;

            if (result.isSuccess()) {
                logger.info("操作成功 - operation={}, id={}, duration={}ms",
                    operation, result.getData(), duration);
            } else {
                logger.error("操作失败 - operation={}, errorCode={}, errorMsg={}, duration={}ms",
                    operation, result.getResultCode(), result.getResultMsg(), duration);
            }

        } catch (Exception e) {
            long duration = System.currentTimeMillis() - startTime;
            logger.error("操作异常 - operation={}, duration={}ms",
                operation, duration, e);
            throw e;
        }
    }
}

5.2 Log levels ​

Use log levels deliberately:

Java
// DEBUG: 详细的调试信息
logger.debug("请求参数: {}", paramMap);

// INFO: 重要的业务流程信息
logger.info("开始同步客户数据,数量: {}", customers.size());

// WARN: 警告信息,不影响主流程
logger.warn("数据字段 {} 为空,将使用默认值", fieldName);

// ERROR: 错误信息,需要关注
logger.error("创建数据失败: {}", result.getResultMsg());

// ERROR with exception: 异常堆栈
logger.error("处理数据时发生异常", e);

5.3 Masking sensitive data ​

Java
/**
 * 日志脱敏工具
 */
public class LogMaskUtil {

    /**
     * 脱敏手机号
     */
    public static String maskPhone(String phone) {
        if (phone == null || phone.length() < 11) {
            return phone;
        }
        return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }

    /**
     * 脱敏邮箱
     */
    public static String maskEmail(String email) {
        if (email == null || !email.contains("@")) {
            return email;
        }
        String[] parts = email.split("@");
        String username = parts[0];
        if (username.length() <= 2) {
            return email;
        }
        String masked = username.substring(0, 2) + "***" +
            username.substring(username.length() - 1);
        return masked + "@" + parts[1];
    }

    /**
     * 脱敏 AccessKey
     */
    public static String maskAccessKey(String accessKey) {
        if (accessKey == null || accessKey.length() < 20) {
            return "***";
        }
        return accessKey.substring(0, 10) + "***";
    }
}

Usage:

Java
logger.info("客户信息 - 姓名: {}, 电话: {}, 邮箱: {}",
    customer.getName(),
    LogMaskUtil.maskPhone(customer.getPhone()),
    LogMaskUtil.maskEmail(customer.getEmail())
);

6. Testing strategy ​

6.1 Unit tests ​

Unit-test with a mocking framework:

Java
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

public class CustomerServiceTest {

    @Test
    public void testCreateCustomer() {
        // 创建 Mock 对象
        LovrabetSDKClient mockClient = Mockito.mock(LovrabetSDKClient.class);

        // 设置 Mock 行为
        LovrabetResult<String> mockResult = new LovrabetResult<>();
        mockResult.setSuccess(true);
        mockResult.setData("12345");

        Mockito.when(mockClient.create(Mockito.any()))
            .thenReturn(mockResult);

        // 测试
        CustomerService service = new CustomerService(mockClient);
        String id = service.createCustomer(buildTestCustomer());

        // 验证
        assertEquals("12345", id);
        Mockito.verify(mockClient, Mockito.times(1)).create(Mockito.any());
    }
}

6.2 Integration tests ​

Java
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class DataServiceIntegrationTest {

    @Autowired
    private DataService dataService;

    @Test
    public void testGetCustomers() {
        List<Map<String, Object>> customers = dataService.getCustomers(1, 10);

        assertNotNull(customers);
        assertTrue(customers.size() > 0);
    }

    @Test
    public void testCreateAndQuery() {
        // 创建测试数据
        Map<String, Object> customerData = buildTestData();
        String id = dataService.createCustomer(customerData);

        assertNotNull(id);

        // 查询验证
        Map<String, Object> retrieved = dataService.getCustomer(id);
        assertEquals(customerData.get("TEXT_1"), retrieved.get("TEXT_1"));
    }
}

6.3 Test data isolation ​

Java
/**
 * 测试数据管理
 */
public class TestDataManager {

    private List<String> createdIds = new ArrayList<>();

    /**
     * 创建测试数据
     */
    public String createTestData(LovrabetSDKClient sdkClient) {
        LovrabetRequest request = new LovrabetRequest();
        // 设置测试数据标识
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("TEXT_1", "TEST_" + System.currentTimeMillis());
        request.setParamMap(paramMap);

        LovrabetResult<String> result = sdkClient.create(request);
        if (result.isSuccess()) {
            String id = result.getData();
            createdIds.add(id);  // 记录创建的 ID
            return id;
        }
        return null;
    }

    /**
     * 清理测试数据
     */
    public void cleanup(LovrabetSDKClient sdkClient) {
        for (String id : createdIds) {
            // 清理测试数据的逻辑
            logger.debug("清理测试数据: {}", id);
        }
        createdIds.clear();
    }
}

7. Deployment ​

7.1 Environment configuration ​

Use separate config files per environment:

YAML
# application-dev.yml (开发环境)
lovrabet:
  access-key: ${LOVRABET_ACCESS_KEY}
  base-url: https://runtime.lovrabet.com/openapi
  app-code: app-dev-xxx

---
# application-prod.yml (生产环境)
lovrabet:
  access-key: ${LOVRABET_ACCESS_KEY}
  base-url: https://runtime.lovrabet.com/openapi
  app-code: app-prod-xxx

7.2 Health checks ​

Expose a health-check endpoint:

Java
@RestController
public class HealthController {

    private final LovrabetSDKClient sdkClient;

    @GetMapping("/health/lovrabet")
    public Map<String, Object> checkLovrabetHealth() {
        Map<String, Object> health = new HashMap<>();

        try {
            // 尝试调用一个简单的 API
            LovrabetRequest request = new LovrabetRequest();
            request.setAppCode(appCode);

            long startTime = System.currentTimeMillis();
            LovrabetResult<?> result = sdkClient.getDatasetCodeList(request);
            long duration = System.currentTimeMillis() - startTime;

            health.put("status", result.isSuccess() ? "UP" : "DOWN");
            health.put("responseTime", duration + "ms");
            health.put("message", result.getResultMsg());

        } catch (Exception e) {
            health.put("status", "DOWN");
            health.put("error", e.getMessage());
        }

        return health;
    }
}

7.3 Monitoring metrics ​

Collect metrics with Micrometer:

Java
import io.micrometer.core.instrument.*;

@Service
public class MonitoredDataService {

    private final LovrabetSDKClient sdkClient;
    private final MeterRegistry meterRegistry;
    private final Counter successCounter;
    private final Counter failureCounter;
    private final Timer responseTimer;

    public MonitoredDataService(
            LovrabetSDKClient sdkClient,
            MeterRegistry meterRegistry) {
        this.sdkClient = sdkClient;
        this.meterRegistry = meterRegistry;

        // 注册指标
        this.successCounter = Counter.builder("lovrabet.api.success")
            .description("API 调用成功次数")
            .register(meterRegistry);

        this.failureCounter = Counter.builder("lovrabet.api.failure")
            .description("API 调用失败次数")
            .register(meterRegistry);

        this.responseTimer = Timer.builder("lovrabet.api.response.time")
            .description("API 响应时间")
            .register(meterRegistry);
    }

    public Map<String, Object> getData(String id) {
        return responseTimer.record(() -> {
            LovrabetResult<?> result = sdkClient.getOne(buildRequest(id));

            if (result.isSuccess()) {
                successCounter.increment();
                return (Map<String, Object>) result.getData();
            } else {
                failureCounter.increment();
                return null;
            }
        });
    }
}

7.4 Containerized deployment ​

Example Dockerfile:

Dockerfile
FROM openjdk:11-jre-slim

# 设置工作目录
WORKDIR /app

# 复制 jar 文件
COPY target/myapp.jar app.jar

# 设置环境变量(生产环境应通过 Secret 管理)
ENV LOVRABET_ACCESS_KEY=""

# 暴露端口
EXPOSE 8080

# 启动应用
ENTRYPOINT ["java", "-jar", "app.jar"]

Kubernetes deployment:

YAML
apiVersion: v1
kind: Secret
metadata:
  name: lovrabet-secret
type: Opaque
data:
  access-key: <base64-encoded-access-key>

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        env:
        - name: LOVRABET_ACCESS_KEY
          valueFrom:
            secretKeyRef:
              name: lovrabet-secret
              key: access-key
        ports:
        - containerPort: 8080
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health/lovrabet
            port: 8080
          initialDelaySeconds: 20
          periodSeconds: 5

Summary ​

Follow these best practices to build production applications that are fast, highly available, and secure:

  1. Client management - reuse the client as a singleton
  2. Security - manage AccessKeys properly; never hard-code them
  3. Performance - paginate sensibly, use caching, control concurrency
  4. Error handling - centralize error handling and add retries
  5. Logging - structured logs and sensitive-data masking
  6. Testing - solid unit and integration tests
  7. Deployment - health checks, monitoring metrics, containers

Next steps ​

With the best practices under your belt, we suggest continuing in this order:

  1. 📖 Quick start — build your first CRUD program in 5 minutes
  2. 📚 Core concepts — understand how the SDK works
  3. 📋 API reference — the complete API documentation
  4. 💡 Examples — 5 real-world implementations
  5. 🚀 Best practices ← you are here — production optimization tips
  6. ❓ FAQ ← recommended next — answers to common questions

Need help? See the FAQ or contact service@lovrabet.com

基于飞书知识库同步生成,内容以飞书源文档为准