清单一:
public interface RefundExportService { BaseResult<String> export(RefundExportParam refundExportParam); } @Data public class RefundExportParam { /** 调用方 */ private String source; }通常,退款需要先搜索出所需要的记录,然后再根据这些记录额外获取其他信息,再写入和上传报表。现在,需要加点东西了。 假设需要根据 退款编号和 退款时间来搜索。 那么,需要在 RefundExportParam 里增加三个参数。
清单二:
@Data class RefundExportParamV2 { /** 调用方 */ private String source; /** 退款编号 */ private String refundNo; /** 退款起始时间 */ private Long startTime; /** 退款结束时间 */ private Long endTime; }到目前为止,似乎一切都很自然。 假设退款还有更多搜索参数,那么是不是全部都写在 RefundExportParam ?实际上还有一种方案,将搜索参数语义分离出来,建立类 RefundSearchParam 。
清单三:
@Data public class RefundExportParamV3 { /** 调用方 */ private String source; /** 退款搜索参数 */ private RefundSearchParam search; } @Data class RefundSearchParam { /** 退款编号 */ private String refundNo; /** 退款起始时间 */ private Long startTime; /** 退款结束时间 */ private Long endTime; }现在,需要做出一个选择。 究竟是清单二的方式好,还是清单三的方式好呢?
从清晰简洁来看,无疑清单二是非常简单符合标准的;而清单三增加了嵌套,增加了复杂性。 仔细分析 RefundExportParamV2 ,会发现这个参数含有两层语义: 1. 用于搜索记录的语义; 2. 调用相关语义。 当这两层语义都比较多时,就会导致这个类比较混杂。 此时,有两种选择: 1. 如果按照清单二,那么需要把这两种语义的参数用空行显式分割开; 2. 如果按照清单三,则需要提供一些遍历方法,让调用方更加友好地设置 search 及 search 里的参数,提供更好的使用体验。此外,RefundSearchParam 还可以在退款搜索中复用。
我个人倾向于使用清单三的方式。语义分离,是创造清晰性的一种方式。但有时,清晰性与简洁性并不是一个概念。清单三做到了清晰性,但并非足够简洁;清单二,做到了简洁,却不足够清晰。
扩展参数通常,需要一些扩展参数,来做一些外围的事情。
联调。为了更好地联调,通常会设计一个必传的 requestId 。
运维。如果导出因为偶然因素而失败怎么办? 可以设计一个 exportId ,针对该导出ID进行导出和修复。
监控与统计。 调用源 source 实际上是用来统计的,并非必要参数。监控与统计,尽量依赖内部的状态,而不是API参数。
清单四:
@Data public class RefundExportParamV4 { /** 调用方,必传 */ private String source; /** 请求ID,必传 */ private String requestId; /** 导出ID, 运维使用 */ private Long exportId; /** 退款搜索参数 */ private RefundSearchParam search; }如清单四所示。 要特别注意的是,这些外围、扩展参数是否会淹没了必要参数。有一种办法是,将这些非必要参数,都放到一个 Map 里,然后提供一些工具方法来设置。 孰优孰劣,读者来评判。
总结下: 通过三个类的组合 (RefundExportService, RefundExportParam, RefundSearchParam) ,建立了退款导出API 的基本骨架。退款导出的API,其实是很多导出API的典型表达。
假设,该应用现在要接入一个电子卡券导出。 电子卡券导出与退款导出的流程和实现基本类似,但搜索参数不同。
我不希望再加个 VirtualTicketExportService, 而是希望做成一个通用导出服务,这个导出可以容纳退款导出和电子卡券导出,以及后续的各种导出。现在,清单三的方式显然胜出了。 因为如果按照清单二,需要把电子卡券搜索入参也写到 RefundExportParamV2 中,清晰性立即骤降,且导致了参数混杂(退款导出调用方也能看到电子卡券的搜索参数)。
现在,在退款导出的基础上,重新设计一个 ( ExportService, ExportParam, SearchParam ) 。 在 ExportParam 里肯定要加个导出业务类型参数 bizType。重写如下:
清单五:
public interface ExportService { BaseResult<String> export(ExportParam exportParam); } @Data public class ExportParam { /** 调用方,必传 */ private String source; /** 导出业务类型,必传 */ private String bizType; /** 搜索参数,必传 */ private SearchParam search; /** 请求ID,必传 */ private String requestId; /** 导出ID, 运维使用 */ private Long exportId; } class SearchParam { // How to design ? }