6.1 KiB
6.1 KiB
PositionUpdateEvent API重构文档
创建日期: 2025-01-15
版本: 0.1.11
重构类型: API设计原则的严格实施
问题背景
用户提出的问题
用户询问:"PositionUpdateEvent中为什么返回车辆ID用了string"
原有设计问题
- 类型不一致:getVehicleId()返回String类型,违背了"索引性质使用vehicleId"的设计原则
- 混合逻辑:为了兼容两种不同的payload类型,强制使用String统一返回
- 语义不清:没有明确区分索引标识符和业务标识符的使用场景
设计原则回顾
用户在前面确立的API设计原则:
索引性质的返回值:使用vehicleId(唯一、稳定的标识符)
详细信息的返回值:包含vehicleId + licensePlate(完整信息)
重构方案
核心思路
区分不同数据类型和使用场景,提供专门的方法:
- 索引性质:getVehicleId() → 返回Long类型
- 业务标识符:getBusinessId() → 返回String类型
- 完整信息:getVehicleIdentifier() → 返回完整对象
数据类型区分
无人车数据(VehicleLocation)
- vehicleId:Long类型,数据库主键
- businessId:String类型,车牌号(licensePlate)
- 特点:有数据库记录,支持索引查询
航空器/机场车辆数据(PositionUpdatePayload)
- vehicleId:null(没有数据库记录)
- businessId:String类型,航班号/车牌号(objectId)
- 特点:仅实时处理,不持久化存储
重构实施
1. getVehicleId()方法重构
修改前:
public String getVehicleId() {
if (payload instanceof VehicleLocation) {
return String.valueOf(((VehicleLocation) payload).getVehicleId());
} else if (payload instanceof PositionUpdatePayload) {
return ((PositionUpdatePayload) payload).getObjectId();
}
return null;
}
修改后:
public Long getVehicleId() {
if (payload instanceof VehicleLocation) {
return ((VehicleLocation) payload).getVehicleId();
}
// 航空器和机场车辆没有数据库记录,返回null
return null;
}
2. 新增getBusinessId()方法
public String getBusinessId() {
if (payload instanceof VehicleLocation) {
return ((VehicleLocation) payload).getLicensePlate();
} else if (payload instanceof PositionUpdatePayload) {
return ((PositionUpdatePayload) payload).getObjectId();
}
return null;
}
3. 新增getVehicleIdentifier()方法
public VehicleIdentifier getVehicleIdentifier() {
if (payload instanceof VehicleLocation) {
VehicleLocation location = (VehicleLocation) payload;
return VehicleIdentifier.builder()
.vehicleId(location.getVehicleId())
.businessId(location.getLicensePlate())
.vehicleType(location.getVehicleType().name())
.build();
} else if (payload instanceof PositionUpdatePayload) {
PositionUpdatePayload positionPayload = (PositionUpdatePayload) payload;
return VehicleIdentifier.builder()
.vehicleId(null) // 航空器没有数据库记录
.businessId(positionPayload.getObjectId())
.vehicleType(positionPayload.getObjectType())
.build();
}
return null;
}
4. VehicleIdentifier内部类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class VehicleIdentifier {
private Long vehicleId; // 数据库主键(索引性质)
private String businessId; // 业务标识符(车牌号、航班号等)
private String vehicleType; // 车辆类型
}
使用场景说明
索引场景
PositionUpdateEvent event = new PositionUpdateEvent(vehicleLocation);
// 用于Map索引(仅无人车有效)
Long vehicleId = event.getVehicleId();
if (vehicleId != null) {
vehicleLocationMap.put(String.valueOf(vehicleId), location);
}
业务展示场景
// 用于前端显示
String displayId = event.getBusinessId(); // 车牌号或航班号
String displayText = "车辆: " + displayId;
完整信息场景
// 获取完整标识符信息
VehicleIdentifier identifier = event.getVehicleIdentifier();
if (identifier.getVehicleId() != null) {
// 有数据库记录的无人车
processUnmannedVehicle(identifier);
} else {
// 仅实时处理的航空器/机场车辆
processRealtimeObject(identifier);
}
测试用例修正
修改前
assertEquals("TEST_AIRCRAFT_001", event.getVehicleId());
修改后
// 航空器没有数据库记录
assertNull(event.getVehicleId());
assertEquals("TEST_AIRCRAFT_001", event.getBusinessId());
// 验证完整标识符信息
VehicleIdentifier identifier = event.getVehicleIdentifier();
assertNull(identifier.getVehicleId());
assertEquals("TEST_AIRCRAFT_001", identifier.getBusinessId());
assertEquals("AIRCRAFT", identifier.getVehicleType());
技术优势
1. 类型安全
- 明确的Long/String类型区分
- 避免不必要的类型转换
- 编译时类型检查
2. 语义清晰
- 索引和业务标识符明确分离
- 方法名称直接表达用途
- 减少使用时的歧义
3. 扩展性
- VehicleIdentifier类支持未来扩展
- 不同数据源的统一处理
- 保持向后兼容性
4. 性能优化
- 减少不必要的字符串转换
- Map操作使用数字索引更高效
- 内存使用更节省
验证结果
- ✅ 编译测试通过
- ✅ 单元测试通过(WebSocketEventTest)
- ✅ API设计原则完全落实
- ✅ 保持向后兼容性
- ✅ 文档和测试用例更新完成
设计原则强化
通过这次重构,进一步强化了API设计原则的应用:
- 索引性质 → 明确的数据类型(Long)
- 详细信息 → 完整的信息对象(VehicleIdentifier)
- 业务语义 → 专门的业务标识符方法(getBusinessId)
- 类型安全 → 编译时类型检查和null处理
这个重构为项目中所有类似的API设计树立了标准,确保了设计的一致性和可维护性。