作维护型工做,最大的收获也许就是知道什么叫作丑陋了。本文针对我遇到的一些接口设计问题,总结了以下一些经验分享给你们,但愿咱们可以吸收经验,对外提供最美的一面,即便咱们的实现可能很丑,可是用户不关心也看不到,这就是封装的好处,哈哈。java
接口的粒度其实很大程度上是接口的职责问题。通常来讲越细粒度的接口职责越内聚(偏向于Service),越粗粒度的接口职责越宽泛(偏向于Facade)。web
笔者认为下面的作法是比较合理的:spring
笔者认为接口必定要职责清晰,常常看到这样的代码,在一个for循环中"顺带"作了一些其余事情,缘由是在这里做比较方便,不然须要从新遍历一次,影响性能。可是因为这顺带做的事情,每每会致使接口职责不单一,污染了接口,致使接口的复用性变差。全部应该尽可能抵制诱惑,接口与类都应该是职责单一的。数据库
再说接口的粒度,提供细粒度的接口有利于重用,就像积木块同样,应用能够根据须要自行组装。若是一开始就提供太粗粒度的接口,每每会有这样的状况,有些应用场景下我只须要作其中的几个步骤,这时候就容易致使重复相似的代码出现了。这种状况下,应该将粗粒度的接口进行分解,将通用的步骤封装成细粒度的接口(应该是应用无关),将粗粒度的接口暴露给上层应用(应该是应用相关的)。编程
使用粗粒度的接口可能带来的一个问题是返回情偏多,具体处理详见下面的关于返回值的讨论。api
接口名称应该尽可能面向场景,这不只是接口友好性的表现,另外一方面也是避免内部逻辑外泄的重要措施。好比:如今是否经过AV认证,是否提交AV认证都没有相应的接口,而是提供了一个对AV_INFO_NEW表的查询操做接口,这样用户若是要检查是否已经提交AV认证信息,就必须这么作:框架
public static boolean hasUserSubmitAV(Integer companyId) { AvInfoNewDO avInfoNewDO = avInfoService.findAvInfoNewByCompanyId(companyId); if (avInfoNewDO == null || AvInfoStatusHelper.isAvinfoTransient(avInfoNewDO.getStatus())) { return false; } else { return true; } }
这就是内部业务逻辑的外泄,若是之后是否提交AV认证不是这么一个判断逻辑,就会致使大量的应用须要修改。另外一方面,也是致使客户端不少重复代码。函数
目前为了方便使用spring的声明式事务处理,咱们的service在配置上都是继承自intl-biz-datasource二方库中的transactionDefinition。性能
这原本是一件好事,可是带来的一个问题是Spring是根据方面签名进行AOP的,而父类定义的是CUD数据库操做类型的接口才拦截,这也致使了咱们的接口看起来就象是一个CRUD接口。AOP不该该成为咱们的一个限制,若是默认的AOP模式不能知足咱们的需求,能够重载父类定义。这实际上是颇有必要的,在多service接口协做的过程当中,可能须要不一样的service接口有不用的事务传播类型。单元测试
另外,也能够考虑使用Anotation进行事务标注。
如今不少接口都是这样动不动就是一个DO或者DTO对象做为参数,可是到了实现一看,发现其实就是用到DO/DTO的两三个字段而已。这种大对象做为参数其实是一种很是很差的做法,特别是若是你只用到了大数据对象的一小部分字段而已。举个例子你们就比较容易理解我为何对它如此深恶痛绝了:在com.alibaba.intl.biz.product.service.interfaces.ProductService获取产品独立detail页面URL的接口定义以下:
com.alibaba.intl.biz.product.service.interfaces.ProductService String getProductDetailUrl(URIBroker uriBroker, ProductSearchDTO product);
而在com.alibaba.intl.biz.product.service.impl.ProductServiceImpl中其实现只是用到了ProductSearchDTO的三个字段:getProductId(),getSubject(),getServiceType()。可是ProductSearchDTO这个类呢有十几个属性,而且还关联了一些DO对象。你说这样的一个接口给用户,它怎么知道应该填充这个DTO的那些字段呢?!这对内存空间也是一种浪费。 直接定义成这样的接口多简单: com.alibaba.intl.biz.product.service.interfaces.ProductService
String getProductDetailUrl(URIBroker uriBroker, Integer productId, String subject, ServiceType serviceType);
其实传递URIBroker给下层也是一个不合理的作法,但这不在咱们这一次讨论范围内
什么状况下传递整个DO/DTO对象是合理有用的呢?笔者认为如下三种状况能够考虑:
接口的处理结果可能有多个返回状况,特别是接口粒度越粗,返回状况就越多。如何将处理结果返回给客户,是一个须要好好考虑的问题。好比发布产品,若是将全部逻辑放在Service接口,那么接口必须支持多个返回结果,由于页面须要根据不一样的结果进行不一样的提示。好比:若是用户未提交AV认证,引导其先填写AV信息。若是用户发布的产品数超过了限度,提示之。若是用户类目失效,提示之。若是。。。
通常来讲有如下两种方式:
一种定义一个ResultCode,即接口不仅是返回true或者false,而是返回具体错误信息。页面层根据调用结果代码作相应的处理。但这会致使接口变得复杂,很差理解。
另外一种方式是不经过返回值,而是定义本身的业务异常,经过抛出异常来告诉调用者结果。这会致使客户端好多try catch,不过咱们的声明式事务控制也是要求Service接口抛出异常的。
建议是二者的结合。可是返回结果码是须要事先订下的,后来再加上至关于改变了接口签名。
好的接口不只仅是对用户友好,还应该是对本身友好。其中很大方面表如今可测性上。简单来讲,POJO对象可测性最高,因此尽可能提供POJO参数接口。
在咱们如今不少web层的util类中,web层对象处处走(甚至是web层框架特有对象),这致使可移植性,可测性和可复用性大大变差。好比WebUser虽然放在了ThreadLocal中,可是在biz和dal中获取一个webUser作相应的检查显然是有问题的,由于他的定义就是web层使用的。还有在web层的不少util和helper类中,大量传递webx特有对象——rundata和context,直接从rundata中过去参数,验证或者处理完又直接将结果塞入context中。特别是后者,看起来是方便,可是可测性就差好多了。由于你必须启动整个容器,才能测试。 这里举的例子主要都是针对web层的,其实biz层也是同样的道理。
面向对象设计最大的原则就是针对接口设计。可见接口的重要性,若是接口可以定义好,不只便于自身维护,并且也致使上层应用不须要太多变更。想一想Unix/Linux这么大的内核,也就200多个系统调用,很是稳定,没有太多变化,人家是面向过程的编程思想,能作到这样,确实有值得咱们思考和学习的地方。但愿咱们之后在定义新接口的时候可以多思考一下。另外,咱们的intl-biz-product实在太单薄了,考虑咱们像会员线同样迁移到新的二方库中,新增的接口一概放在新二方库中,而且新增接口务必保证单元测试的完整性和可幂性。目前构想新的二方库结构应该是api和impl分开(为服务化做准备),dal和biz分开(常常看到不少forBOPS,forMoree的SQL...),biz层还要细分通用逻辑层(细粒度的,应用无关的接口)和业务门面层(粗粒度的,应用相关的),每一层可能都有相应的common/share包。固然。只是一个我我的的一些粗步的构想(跟海滔讨论过),欢迎你们提供建议。文档地址以下:http://b2b-doc.alibaba-inc.com/pages/viewpage.action?pageId=45513924
关于UT的幂等性
所谓UT的幂等性,即只要被测函数没有变化,跑N次这个UT应该都是同样的结果。要保证这一点,须要作到以下几点:
首先数据不能依赖于老数据,这意味着须要针对这个UT做相应的数据准备,可使用DBUnit从数据库中导出数据,根据须要简单编辑一下。
其次还要保证数据的独立性,几我的同时操做一个数据库或者操做一个存在大量未知数据的数据库,将不能保证数据操做的正确性。要保证数据的独立性和隔离性,可使用先清空,执行测试,最后再回滚的方式。另外一种方式是每一个人跑本身的测试数据库(可使用内存数据库)。
第三个是减小外部函数的依赖。若是你的被测函数的结果依赖于另外一个函数,那么你很难保证被测函数的幂等性。解决这个问题的一个方式是使用Mock对象。
有了上面这三点保证,咱们就能够保证这个UT在以下的输入下必然有以下的输出,这就是幂等性。