概述

API 是一个服务的门面,就像衣装是人的形象一样。

优雅的 API 设计,能让业务方使用起来倍儿爽,提升开发效率,降低维护成本;糟糕的 API 设计,则让业务方遭心,陷入混沌。

本文将展示一个扩展搜索 API 的优化过程,从中也可以学到一些东西。

现状

找一个上游工程的扩展搜索代码如下:

extendKeywords.add((EsCondition) ConditionFactory.in("order_tags", Arrays.asList("IS_XXX_ORDER")));
extendKeywords.add(new EsCondition("goods_title", Op.match, new Match(goodsTitle, "100%")));

啊啊,真是丑死了 ! 为什么呢 ?

  • 强制类型转换。 让业务方写强制类型转换,简直是让业务方来遭罪的 ! 这 API 设计简直了 !
  • 暴漏底层细节。 让业务方 new EsCondition ,不仅是暴漏底层细节,还很难看!
  • 不方便的传值。 为了传一个 in 的数值,需要写个 Arrays.asList(e) !
  • 遍布的 EsCondition 。 由于 API 设计的很裸,导致上游工程到处弥漫着 EsCondition 的烟雾。

emmm... 其实是我设计的 API 造的孽 ! 解铃还须系铃人。

优化过程

工厂方法

这种硬的 new EsConditon ,完全可以通过工厂方法和方法重载来消除。此外,为了收拢扩展搜索条件的构建,可以构建一个专门的 ExtendSearchParam 。

public class ExtendSearchParam implements Serializable {
private static final long serialVersionUID = 2824767430430079287L; private List<EsCondition> extendConditions = new ArrayList<>(); public ExtendSearchParam addEq(String field, Object value) {
extendConditions.add(new EsCondition(field, Op.eq, value));
return this;
} public ExtendSearchParam addIn(String field, Object... list) {
extendConditions.add(new EsCondition(field, Op.in, list));
return this;
} public ExtendSearchParam addRange(String field, long gte, long lte) {
extendConditions.add(new EsCondition(field, Op.range, new Range(gte, lte)));
return this;
} public ExtendSearchParam addMatch(String field, String query) {
extendConditions.add(new EsCondition(field, Op.match, new Match(query, "100%")));
return this;
} public ExtendSearchParam addMatch(String field, String query, String match) {
extendConditions.add(new EsCondition(field, Op.match, new Match(query, match)));
return this;
}
}

这里借鉴了 Builder 模式,能够链式地构建扩展搜索条件。这样,业务方就可以舒心地写上:

extendSearchParam.addIn(ORDER_TAGS, "IS_XXX_ORDER").addMatch("goods_title", goodsTitle);

没有了类型强制转换,没有了暴漏底层细节,没有了不方便的传值,还可以一直 add 下去, 世界多美好 !

拦路虎

很快,就遇到了拦路虎:

private List<EsCondition> dealOrderSourceCondition(String orderSource) {
List<EsCondition> result = new ArrayList();
result.add(new EsCondition(TYPE_ENTRANCE, Op.eq, "wsc"));
result.add(new EsCondition(TYPE_PLATFORM, Op.eq, "wx"));
return result;
}

emmm , 写这个方法的小伙伴也是好意,封装一个方法来构建订单来源扩展条件也是好意。不过这给API 重构带来了一点点小小的障碍。

怎么重写这一段呢 ? 第一想到的是,在 dealOrderSourceCondition 的方法里额外增加一个参数 ExtendSearchParam ,传进去,修改它。也能达到目地。但是,—— 破坏了“不可变”原则。 不可变原则要求,尽可能避免修改入参。修改入参这种行为,很容易导致不起眼的 BUG ,如果在关键流程中做这个事情,有可能导致故障。有线上教训的。

怎么办呢 ? 又不能修改 dealOrderSourceCondition 的入参,又要把这个方法的扩展搜索条件合并到已有的扩展搜索对象中。

有一种办法 ! 合并扩展搜索对象 ExtendSearchParam 。 这样,ExtendSearchParam 需要支持一个合并操作:

public ExtendSearchParam merge(ExtendSearchParam extendSearchParam) {
if (extendSearchParam != null && extendSearchParam.has()) {
extendConditions.addAll(extendSearchParam.getUnmodifiedExtendSearch());
}
return extendSearchParam;
}

这样,将 dealOrderSourceCondition 的返回值改为 ExtendSearchParam 对象,就能使用 merge 方法来合并扩展搜索条件了。

Yeap ! 想一想,除了 合并操作,还需要支持哪些操作呢 ?API 设计需要考虑周全,可不能遇到一个问题加一个支持啊 !

辅助方法

为了与原来的 OrderSearchParam 联合使用, 需要加一些辅助方法,比如:

public boolean has() {
return CollectionUtils.isNotEmpty(extendConditions);
} public List<EsCondition> getUnmodifiedExtendSearch() {
return Collections.unmodifiableList(extendConditions);
}

小结

本文讲解了一个扩展搜索 API 的优化过程。好的 API 设计能提升业务方的使用体验,降低维护成本。设计优雅的 API ,需要掌握一些技巧:工厂方法、重载方法、常用操作等。

从工作中不断发现需要优化的地方,掌握方法和技巧去解决, 也是一种提升技能的方式。

一个扩展搜索API的优化过程的更多相关文章

  1. 如何设计一个优秀的API(转载)

    最近在整理框架的一些 API,觉得很有必要总结一下 API 兼容性的设计.下图是我自己当下的一些总结,慢慢维护: 网上搜索了一下,一个多月前,“标点符”已经发布了下面这篇文章,觉得写得非常不错,转载于 ...

  2. ASP.NET Web API 控制器创建过程(一)

    ASP.NET Web API 控制器创建过程(一) 前言 在前面对管道.路由有了基础的了解过后,本篇将带大家一起学习一下在ASP.NET Web API中控制器的创建过程,这过程分为几个部分下面的内 ...

  3. 如何设计一个优秀的API(转)

    到目前为止,已经负责API接近两年了,这两年中发现现有的API存在的问题越来越多,但很多API一旦发布后就不再能修改了,即时升级和维护是必须的.一旦API发生变化,就可能对相关的调用者带来巨大的代价, ...

  4. 如何设计一个优秀的API

    如何设计一个优秀的API - 文章 - 伯乐在线 http://blog.jobbole.com/42317/ 如何设计一个优秀的API - 标点符 https://www.biaodianfu.co ...

  5. Dubbo源码解析之SPI(一):扩展类的加载过程

    Dubbo是一款开源的.高性能且轻量级的Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用.智能容错和负载均衡,以及服务自动注册和发现. Dubbo最早是阿里公司内部的RPC框架,于 ...

  6. ASP.NET Web API 控制器执行过程(一)

    ASP.NET Web API 控制器执行过程(一) 前言 前面两篇讲解了控制器的创建过程,只是从框架源码的角度去简单的了解,在控制器创建过后所执行的过程也是尤为重要的,本篇就来简单的说明一下控制器在 ...

  7. ASP.NET Web API 控制器创建过程(二)

    ASP.NET Web API 控制器创建过程(二) 前言 本来这篇随笔应该是在上周就该写出来发布的,由于身体跟不上节奏感冒发烧有心无力,这种天气感冒发烧生不如死,也真正的体会到了什么叫病来如山倒,病 ...

  8. ext3是对ext2文件系统的一个扩展高性能日志文件系统

    嵌入式开发者所做的最重要的决定之一就是部署哪种文件系统.有些文件系统性能比较高有些文件系统空间利用率比较高,还有一些文件系统设备故障或者意外断电后恢复数据比较方便. linux文件系统概念 分区 分区 ...

  9. Redis数据导入工具优化过程总结

    Redis数据导入工具优化过程总结 背景 使用C++开发了一个Redis数据导入工具 从oracle中将所有表数据导入到redis中: 不是单纯的数据导入,每条oracle中的原有记录,需要经过业务逻 ...

随机推荐

  1. scrapy 当当网 爬虫

    前言 好久没有写实战博客了,因为前几个月在公司实习,博客更新就耽搁了下来,现在又受疫情影响无法返校,但是技能还是不能丢的,今天就写一篇使用scrapy爬取当当网的实战练习吧. 创建scrapy项目 目 ...

  2. Docker基础(1) 原理篇

    Docker是什么 Docker的构成 Docker的分层和写时拷贝策略 Docker与主流虚拟机的区别 Docker镜像与容器的关系 镜像的变更管理 Docker是什么 Docker是一个开源的应用 ...

  3. opencv —— moments 矩的计算(空间矩/几何矩、中心距、归一化中心距、Hu矩)

    计算矩的目的 从一幅图像计算出来的矩集,不仅可以描述图像形状的全局特征,而且可以提供大量关于该图像不同的几何特征信息,如大小,位置.方向和形状等.这种描述能力广泛应用于各种图像处理.计算机视觉和机器人 ...

  4. Play! 1.x 访问远程web

    本文参考 Play Framework 控制层发起HTTP请求 (Send Http Request In Controller) 参考连接地址:http://blog.csdn.net/fhzait ...

  5. 8.python内置模块之random模块简介

    Python中的random模块用于生成随机数. 常用的7个函数: 1.random.random():返回一个[0,1)之间的随机浮点值(双精度) 2.random.uniform(a,b):返回[ ...

  6. Django单元测试中Fixtures用法

    在使用单元测试时,有时候需要测试数据库中有数据,这时我们可以使用Django的Fixtures来生成测试数据. 基础配置 在settings.py 中配置如下内容: FIXTURE_DIRS = (' ...

  7. \n不换行

    \n在js中表示换行,<br/>在html中表示换行,所以如果在设置innerHtml值时使用  \n  ,那么在页面上并不会显示换行,而在设置innerText值时使用  \n  就会显 ...

  8. 折腾vue--使用vscode创建vue项目(二)

    1.安装webpack npm install -g webpack 2.安装sass npm install --save-dev sass-loader npm install --save-de ...

  9. Python 实现选择排序

    选择排序算法步骤: 找到数组中最小的那个元素中, 将它和数组的第一个元素交换位置, 在剩下的元素中找到最小的元素,将它和数组的第二个元素交换位置, 如此往复,知道将整个数组排序. 逐步分析: 假设一个 ...

  10. STL中的Set和Map——入门新手篇

    STL中的Set和Map 先来看一段网络上的文字描述: 上图是一段关于STL中Set集合的描述,同样的,也近似适合Map的描述.上述文字中,描述了最为重要的特征: Set和Map,底层调用了红黑树的结 ...