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

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