3、接口适配实战:遗留系统接口适配、第三方SDK适配、多数据源统一适配
接口适配这件事,说白了就是「让不听话的接口听话」。我做了十几年系统集成,见过太多「接口能用但不好用」的案例。这一章咱们就聊聊三种最常见的适配场景:老系统的倔强接口、第三方SDK的黑盒、以及多数据源的混乱局面。
核心观点:适配不是改造对方,而是在中间层做翻译。你改变不了遗留系统的SQL,也改不了SDK的签名算法,但你可以让调用方感觉不到这些差异。
3.1 遗留系统接口适配
老系统为什么难搞?因为它们往往没有接口文档,或者文档早就过期了。我记得有一次对接一个2003年的ERP系统,对方说「我们支持SOAP」,结果连WSDL都找不到了。
遗留系统接口的常见问题:
- 协议老旧:RMI、CORBA、私有TCP协议,甚至直接读数据库表
- 数据格式混乱:固定长度报文、CSV、自定义XML
- 无状态或状态管理诡异:需要手动维护Session
- 性能瓶颈:单线程处理,并发一高就挂
我的做法是:在遗留系统前面加一层「防腐层」(Anti-Corruption Layer)。这个层只做三件事——协议转换、数据清洗、异常隔离。
// 遗留系统适配器示例:固定长度报文解析
public class LegacyOrderAdapter {
public OrderDTO parseFixedLengthMessage(String message) {
// 报文格式:订单号(20) + 客户名(30) + 金额(12) + 日期(8)
if (message == null || message.length() != 70) {
throw new AdapterException("报文长度不合法,期望70位,实际" +
(message == null ? 0 : message.length()));
}
OrderDTO order = new OrderDTO();
order.setOrderId(message.substring(0, 20).trim());
order.setCustomerName(message.substring(20, 50).trim());
// 金额处理:老系统用分存储,我们转成元
String amountStr = message.substring(50, 62).trim();
order.setAmount(new BigDecimal(amountStr).divide(new BigDecimal("100")));
// 日期格式:yyyymmdd -> yyyy-MM-dd
String dateStr = message.substring(62, 70);
order.setOrderDate(dateStr.substring(0,4) + "-" +
dateStr.substring(4,6) + "-" +
dateStr.substring(6,8));
return order;
}
}
我的经验:遗留系统适配最容易踩的坑是「隐式规则」。比如某个字段看似是可选,但传空会导致老系统崩溃。我曾经因为一个空格问题排查了三天——老系统的COBOL程序对全角空格会直接抛异常。
3.2 第三方SDK适配
第三方SDK适配,核心难点在于「你控制不了它」。SDK可能版本升级不兼容,可能内部有线程安全问题,甚至可能偷偷往你的classpath里塞依赖。
我一般会做三层隔离:
- 接口抽象层:定义业务需要的接口,不暴露SDK的任何类型
- 适配器层:实现接口,内部调用SDK,做异常转换和重试
- 健康检查层:监控SDK的调用状态,超时或异常时自动降级
// 第三方支付SDK适配器
public class PaymentGatewayAdapter implements PaymentService {
private final ThirdPartySdk sdk;
private final RetryTemplate retryTemplate;
@Override
public PaymentResult charge(PaymentRequest request) {
// 适配器内部做参数转换
SdkPayRequest sdkRequest = new SdkPayRequest();
sdkRequest.setAmount(request.getAmount().longValue()); // SDK用分,我们用元
sdkRequest.setOrderNo(request.getOrderId());
sdkRequest.setNotifyUrl(config.getNotifyUrl());
try {
// 用重试模板包裹,SDK偶尔会网络抖动
SdkPayResponse response = retryTemplate.execute(
ctx -> sdk.pay(sdkRequest)
);
return convertToResult(response);
} catch (SdkTimeoutException e) {
// 超时不等于失败,需要查询最终状态
return handleTimeout(request);
} catch (SdkBusinessException e) {
// 业务异常直接返回,不需要重试
return PaymentResult.failed(e.getCode(), e.getMessage());
}
}
private PaymentResult convertToResult(SdkPayResponse response) {
// 把SDK的响应转成统一格式
PaymentResult result = new PaymentResult();
result.setSuccess(response.isSuccess());
result.setTransactionId(response.getTradeNo());
result.setMessage(response.getMsg());
return result;
}
}
避坑指南:我曾经遇到一个SDK,它的HttpClient内部用了连接池,但没做空闲连接回收。系统运行三天后,所有请求都卡在获取连接上。从那以后,我接手第三方SDK的第一件事就是检查它的资源管理方式——连接池、线程池、文件句柄,一个都不能放过。
3.3 多数据源统一适配
多数据源适配,说白了就是「让不同的数据库说同一种话」。MySQL、Oracle、Redis、Elasticsearch,它们的查询语法、事务模型、连接方式都不一样。但业务层不想关心这些,它只想要数据。
我常用的方案是「数据源路由 + 统一查询接口」:
| 数据源类型 | 适配策略 | 典型场景 | 注意事项 |
|---|---|---|---|
| 关系型数据库 | JDBC + 连接池 | 订单、用户等核心数据 | 事务一致性、分页方言差异 |
| 缓存 | RedisTemplate / Jedis | 热点数据、会话 | 序列化方式、过期策略 |
| 搜索引擎 | RestHighLevelClient | 全文检索、聚合分析 | 索引映射、分词器 |
| 文件存储 | OSS SDK / FTP | 图片、日志、备份 | 分片上传、断点续传 |
来看一个统一查询接口的实现思路:
// 统一数据查询接口
public interface DataQueryService {
<T> T query(String dataSource, String queryKey, Map<String, Object> params);
}
// 适配器注册中心
@Component
public class DataSourceAdapterRegistry {
private final Map<String, DataSourceAdapter> adapters = new ConcurrentHashMap<>();
public void register(String type, DataSourceAdapter adapter) {
adapters.put(type, adapter);
}
public DataSourceAdapter getAdapter(String dataSource) {
DataSourceAdapter adapter = adapters.get(dataSource);
if (adapter == null) {
throw new AdapterNotFoundException("未找到数据源适配器: " + dataSource);
}
return adapter;
}
}
// MySQL适配器示例
@Component
public class MysqlAdapter implements DataSourceAdapter {
private final JdbcTemplate jdbcTemplate;
@Override
public Object execute(String queryKey, Map<String, Object> params) {
// 根据queryKey找到对应的SQL模板
String sql = SqlRegistry.getSql(queryKey);
// 参数绑定,防止SQL注入
return jdbcTemplate.queryForList(sql, params);
}
}
我的习惯:多数据源适配最容易出问题的是「事务边界」。比如你从MySQL查了数据,写到Redis,再写到ES——任何一个步骤失败,都需要回滚。我一般会用「本地消息表 + 补偿任务」来保证最终一致性,而不是强依赖分布式事务。
3.4 适配层架构总览
这三种适配场景,其实可以抽象成一个统一的架构模式。我画了一张图,你看完应该就明白了:
你看这个架构,从上到下分了三层。业务调用方只跟统一接口层打交道,根本不知道底层是MySQL还是老掉牙的COBOL系统。适配器层负责把「脏活累活」都干了——协议转换、异常处理、重试降级。
总结一下:接口适配的核心不是技术,是「边界意识」。你要清楚哪些是你能控制的,哪些是你控制不了的。能控制的,做好抽象和封装;控制不了的,做好隔离和降级。这样系统才能经得起折腾。