非典型程序媛

数据访问接口设计思考

这题目有点大,但我也没想到一个更好的题目,先这么用着。老规矩,先说背景。本周遇到了一个难题,如下:某entity有add和update两个接口,add和update的参数基本一致(add时无id,update时有id,其他基本一致)。该entity有个字段A是Date类型,非必填项。如果add时传了这个字段,那么update的时候,没有办法把字段A给修改为NULL。为什么没办法呢?往下看。

接口设计现状

我们主要着眼在控制层的逻辑

基础类APIController/** * 提供接口的基础服务如获取请求、鉴权、获取DTO */{……/*** 获取请求的DTO* @param <T>* @return object*/public static <T> T getDTO(Class<T> clazz) {JsonObject requestJsonData = (JsonObject) getDataOfJson();/*** 省略具体处理逻辑*/}public static JsonElement getDataOfJson() {……}}具体实体的Controller/** * 事件实体的控制类,提供add和update方法 */{……() {……// 获取参数,只是为了验证是否有传递该参数,该字段对请求结果不影响,仅为了安全,防猜密钥之用途。@SuppressWarnings(“unused”)EventUpdateRequestDTO eventRequest = getDTO(EventUpdateRequestDTO.class);updateEvent(eventRequest);}……(EventUpdateRequestDTO request) {……// new EventEvent event = EventService.getById(request.getEventId());if (event == null) {helper.returnError(“5000005”, “eventId does not exist.”);}……if (request.getPrvEventId() != null) {event.setPrvEventId(request.getPrvEventId());}/*** 省略具体字段的处理,对所有的字段都是在不为NULL的时候set*/if (request.getSource() != null) {event.setSource(request.getSource());}……}}

从以上两段代码中可以看出,具体entity的Controller是通过父类的getDTO方法来拿到请求类的,由于父类封装了这个过程,在具体entity的Controller里,是不知道原始请求信息中是没传这个字段呢,还是传了但传的是NULL。update时,对所有的字段都统一粗暴地进行NULL判断,不为NULL时才set。于是本文开头的问题就很好理解了,对于一开始在add时就有值的字段,无法将其update成NULL。

为什么要跟NULL过不去

是的,我们在思考解决方案之前,要先回答这个问题。为什么我们要跟NULL过不去,为什么非要在update时将一个已有值的字段设置为NULL。我给出的答案如下:

有业务场景需要,如本文例子中的event,该实体仅在特定状态下需要有endTime(Date类型)字段,其他状态下若保留endTime会造成干扰和误解。因此,有这样的业务需求在update时清空已有字段。

即使目前没有业务场景需要,但接口功能还是得齐备。我可以不用,但你不能没有。万一我哪天就想用了呢?

所以,还是得解决这个问题。

几种可能的解决方案特殊处理法

实在是有这样的字段,那就特殊处理好了。比如可以这样:

// 对endTime做特殊处理if (request.getEndTime() != null) {event.setEndTime(new Date(request.getEndTime() * 1000));} else {// 不传或传null的时候,将endTime设置为null(因为只有开测状态的大事件有结束时间,其他状态不需要结束时间)event.setEndTime(null);}

也可以这样:

// 对endTime做特殊处理if (request.getEndTime() != null) {// 传特殊时间表示需要置为NULLif (request.getEndTime().longValue() == 0L) {event.setEndTime(null);} else {event.setEndTime(new Date(request.getEndTime() * 1000));}}

and so on。总之,针对这个字段做一些特殊的约定和处理。这样解决的好处是改动小(未涉及接口框架的改动,也不牵连其他字段),坏处是埋坑。我们讨厌特殊处理,很大程度上是因为特殊处理需要人的特殊注意,容易出错。这段特殊处理的代码放在这里,其实就是埋了一个坑,指不定哪天就掉进去了。

修改接口协议

这里的接口协议指的是整体api的所有update接口协议。原本我们的协议是,update时仅传需要修改的字段。那我们改一下,改成和add类似的“全量”update,即update时需要传这个实体所有的字段值(即使有些字段没有修改)。这样的优点显而易见,不再存在无法清空某一字段的问题,想改成啥都行;缺点也一样显而易见,,update接口交互的内容增多,很多不需要修改的值也传来传去的,浪费网络流量。再者,针对目前遇到的这个问题,这个方法不具备可行性,因为接口已经大规模铺开应用,修改接口协议不现实。如果是完全从头设计新接口,倒是可以考虑下这种方案。

修改基础类APIController

【写在前面的话】这个解决方案纯粹是我异想天开。所以这一小节的标题是“几种可能的解决方案”,仅仅只是可能。看完如果觉得搞笑,哈哈一笑就好,不要当真。嗯,因为我自己都觉得挺搞笑,或者说不现实。

我的想法是,既然是APIController的getDTO方法屏蔽了请求的细节(如参数是传了还是没传),那为什么不改写getDTO这个方法呢?哪里跌倒哪里爬起来,这个方法里就搞清楚请求原内容是什么,返回的DTO也要做相应修改。异想天开的地方来了:

{……endTimeFlag;// endTime的内容private Long endTime;……}

getDTO的职责就是解析请求,根据实际情况设置每个字段的两个相关属性(传了没,如果传了值是啥)。

这种方法(虽然getDTO的逻辑我没有贴,还没时间写)可以解决本文开头提出的问题。优点就是,接口协议不用修改,不影响关联方,即使接口已经铺开使用了也没事。缺点是,如果一个entity有N个字段的话,那requestDTO里就会有2*N个属性,代码量大增,开发复杂度上来了。另外就是我觉得这个方案有点搞笑,冗余、不优雅。

总结看着书里九万五千公里的绚丽。又或是和我一样,

非典型程序媛

相关文章:

你感兴趣的文章:

标签云: