关于接口设计:关于接口可维护性的一些建议-京东云技术团队

21次阅读

共计 5150 个字符,预计需要花费 13 分钟才能阅读完成。

作者:D 瓜哥

在做新需要开发或者相干零碎的保护更新时,尤其是波及到不同零碎的接口调用时,在可维护性方面,总感觉有很多中央差强人意。一些零星思考,抛砖引玉,心愿引发更多的思考和探讨。总结了大略有如下几条倡议:

  1. 在接口正文中退出接口文档链接
  2. 将调用接口处写上被调用接口文档链接
  3. 将接口源代码公布到私服仓库
  4. 对于状态值常量,优先在接口参数类或者返回值类中定义
  5. 如果应用 Map 对象作为传输载体,要提供 Key 值定义常量
  6. 针对 Map 返回值,能够思考应用将 Map 转化成对象
  7. 尽可能简化接口依赖
  8. 只传递必要字段,尽量避免大而全的接口
  9. 将接口的参数和返回值原始数据打印到日志中
  10. 将 RPC 接口的类名及办法打印到日志中
  11. 核心思想:以人为本,就近准则,触手可及

上面,D 瓜哥对每一条倡议做一个具体阐明。

1. 在接口正文中退出接口文档链接

在做接口开发时,无论是对自有接口的降级革新,还是针对内部接口的从头接入,都波及到接口文档。不同之处是,前者的工作重点是书写或者更新接口文档;而后者是依据接口文档开发适合的接入代码。然而,常常遇到的一个麻烦是,找不到接口文档。在组内须要找老同事询问;如果是跨部门,还须要两层甚至三层的进行转接,十分麻烦。

D 瓜哥认为,在这种状况下,为了不便大家保护,最好的方法就是将接口文档链接间接放在代码正文中,这样后续保护的人员,间接就能够点击链接中转接口文档,简略不便高效。如果是新建的接口,就能够先创立一个空文档,把链接放在正文中,后续再书写文档内容。如果是保护已有接口,能够在保护时,将缺失的链接退出到正文中,本人不便,也不便其他人进行后续的保护更新。这样,在循序渐进的过程中,逐渐就能够把文档链接补充到代码中,不便保护代码,也同步更新文档。

2. 将调用接口处写上被调用接口文档链接

在调用其余零碎的接口时,没有接口文档,简直举步维艰。在第一次接入接口时,绝大多数状况下,都是参考着接口文档做接入工作。然而,目前的状况时,接入时参考文档,参考完就顺手把文档给“扔了”。后续如果还须要做进一步降级保护,还须要到处找接口文档;另外,交互的零碎不免有一些 Bug,在和其余系统维护人员对接解决 Bug 时,只有接口没有文档,对方可能也须要去找文档链接。无形中,很多工夫都节约在了找文档的过程中。

D 瓜哥最近尝试了一个实际,就是在接口调用的中央,把接口文档链接当做正文退出到代码中。这样,无论是后续保护降级,还是沟通协调解决问题,都十分不便。他人问接口是什么,连贯口 + 文档都能够一把复制就搞定。

通过最近一段时间的实际状况来看,这个解决十分不便,是一个十分值得推广的实际。再插一句,也能够像一条倡议一样,能够在保护代码时,一直把已接入的接口文档退出到调用接口的中央,循序渐进,不便后续人保护降级。

3. 将接口源代码公布到私服仓库

接口文档链接在正文中,在构建后果中就不复存在了。所以,为了不便接口应用方能够在接口中查问到对应的接口文档,就须要把源码也公布到私服仓库中。

这里只阐明一下 Java 的相干解决方法。如果应用 Maven 作为构建工具的话,默认是不会将源代码公布到私服仓库中的。对于如何将源代码公布到,在 降级 Maven 插件:将源码公布到私服仓库 中曾经做过相干介绍,这里就不再赘述。

除了将源码公布到私服仓库,另外,还倡议编译构建时,放弃办法的原始参数命名。这个也能够通过配置 Maven 插件来实现,具体配置见:降级 Maven 插件:字节码文件蕴含原始参数名称。

4. 对于状态值常量,优先在接口参数类或者返回值类中定义

在做接口开发时,很多数据都有一个状态值,比方订单状态,再比方接口状态等等。目前的一个状况时,这些状态值大部分书写在文档中,在接入接口时,须要接入方自定义这些状态值。这就有些繁琐了,而且状态定义也不明确,甚至有可能脱漏一些重要的状态值。有些懒省事,间接在代码中硬编码一个魔法值,后续保护的跟还须要依据上下文反推这个值的含意,十分不利于保护。

D 瓜哥集体感觉,有两个解决方法:

  1. 如果状态值不是很多,优先在接口参数类或者返回值类中定义。
  2. 如果状态值很多,能够思考独自抽取成一个常量类或者枚举类。

这样应用的时候,触手可及。不须要到处去找。

5. 如果应用 Map 对象作为传输载体,要提供 Key 值定义常量

有些零碎可能思考不便减少字段,抉择应用 Map 作为数据载体。本人开发的时候很爽,然而给接口接入却十分不敌对。接入方从 Map 中获取数据时,要么本人定义 Key 值;要么间接应用魔法值硬编码在代码中。应用前者计划,就须要在各个接入方都须要自定义一套;应用后者,初期是省事了,起初保护的人员就懵逼了。这都无形中减少了很多保护老本。

D 瓜哥感觉一个计划更优,那就是间接由接口提供方来定义这些能够取值的 Key 值常量。这样,任何接入方都能够间接应用这些常量。

6. 针对 Map 返回值,能够思考应用将 Map 转化成对象

针对 Map 的解决,即便依照 如果应用 Map 对象作为传输载体,要提供 Key 值定义常量 举荐的做法,定义了相干的 Key,在取值时,也略有麻烦,须要一直的 map.get(KEY)。一个更简略的办法是自定义一个类型,应用工具将 Map 对象转化成自定义类型的对象。这样就能够间接应用办法调用来取值。

在 Java 中,能够间接应用 Jackson 来实现这个转换工作。工具类代码如下:

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;

import java.util.*;java

/**
 * Map 工具类
 *
 * @author D 瓜哥 · https://www.diguage.com
 */
@Slf4j
public class MapUtils {private static final ObjectMapper MAPPER = new ObjectMapper();

    static {MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * 将 Map 转换成指定类型的对象
     *
     * @author D 瓜哥 · https://www.diguage.com
     */
    public static <T> T convertToObject(Map<String, Object> data, Class<T> clazz) {
        try {T result = MAPPER.convertValue(data, MAPPER.getTypeFactory().constructType(clazz));
            if (log.isInfoEnabled()) {log.info("converted {} to a {} object: {}",
                        JsonUtils.toJson(data), clazz.getSimpleName(), JsonUtils.toJson(result));
            }
            return result;
        } catch (Exception e) {log.error("converting failed! data: {}, class: {}",
                    JsonUtils.toJson(data), clazz.getSimpleName(), e);
        }
        return null;
    }

    /**
     * 将 Map 转换成指定类型的对象
     *
     * @author D 瓜哥 · https://www.diguage.com
     */
    public static <T> List<T> convertToObjects(List<Map<String, Object>> datas, Class<T> clazz) {if (CollectionUtils.isEmpty(datas) || Objects.isNull(clazz)) {return Collections.emptyList();
        }
        List<T> result = new ArrayList<>(datas.size());
        if (CollectionUtils.isNotEmpty(datas)) {for (Map<String, Object> data : datas) {T t = convertToObject(data, clazz);
                result.add(t);
            }
        }
        return result;
    }
}



7. 尽可能简化接口依赖

当初,很多对外裸露接口的定义是,接口定义放在一个模块中;模型定义在一个模块中;有些工具类又定义在一个模块中。接口依赖模型模块;模型模块又依赖工具类模块;而工具类依赖了一大堆内部依赖。集体感觉这是一个十分不好的实际。会导致很多不必要的依赖被间接引入到了接口应用方的零碎中,无形中减少很多保护老本。

D 瓜哥举荐的一个实际是:将接口和模型定义放在一个模块中,对外裸露也只须要这一个模块即可。接口应用方只须要引入这一个依赖。防止引入很多无用的其余内部依赖。如果模型须要依赖一些公共的父类,能够思考将这些独自定义在一个模块中,这个模块只保留多个零碎依赖的公共类,并且剔除掉一些工具类的定义,这样就能够保障接口依赖的污浊性。如果其余零碎须要工具类,让其明确去引入,而不是被动依赖。

对于后面 对于状态值常量,优先在接口参数类或者返回值类中定义 中提到了“如果状态值很多,能够思考独自抽取成一个常量类或者枚举类。”这里存在一种状况须要特地阐明,状态值的定义须要在本零碎的业务模块的代码中应用,能够将接口的依赖退出到改业务模块的依赖中,而不是反过来。为什么会这样的操作?一个核心思想是放弃对外裸露接口的污浊性。这样既能够缩小状态定义的重复性,又能够缩小接口的内部依赖。

8. 只传递必要字段,尽量避免大而全的接口

察看很多零碎,尤其是一些以业务为外围的零碎的对外裸露接口,很多接口是大而全的接口,一个接口就能够把指定数据的所有信息全副返回进来。这样,很多字段须要去辨认,也要在泛滥字段中区筛选进去合乎本人要求的数据,无形中节约了很多心智,不利于保护。

D 瓜哥认为,在做接口开发时,肯定要做一个“悭吝的守财奴”。把数据当做财产一样守护,对外只提供必要的数据,做到“够用就行”。

这一点不仅仅是保护上的思考,还有数据传输效率的点。在其余条件雷同的状况下,更小的数据,无论是机器解决效率,还是传输效率,都会更快更高。

对于传输效率上的一些思考,联合 Hessian、Msgpack 和 JSON 实例比照 以及“Hessian 协定解释与实战”等文章来看,有几个准则值得器重的:

  1. 优先应用 boolean 型;
  2. boolean 型满足不了,次优抉择 int 整型数据;再次能够思考 long 型;
  3. 日期优先应用内置的日期类型(含 Java Time API 类型),而不是格式化成字符串。
  4. 对于以上类型不满足,则抉择应用字符串。
  5. 汇合类型,链表优先应用 ArrayList,也能够思考应用 Iterator;哈希优先应用 HashMap;
  6. 以上状况都不符合要求才抉择自定义对象。

9. 将接口的参数和返回值原始数据打印到日志中

据察看,一些开发人员没有将接口,尤其是 RPC 接口的参数及返回值打印到日志中。这对定位问题十分不利。说的更直白一点,十分不利于甩锅。当出了问题,不能第一工夫就凭借参数及返回值顺利甩锅。可能导致本人花很多工夫去排查问题,最初发现是本人依赖的其余零碎的问题。

所以,肯定要谨记,将接口的参数和返回值原始数据打印到日志中。D 瓜哥凭借这个实际,在一些客诉及反馈中,顺利脱身,实现完满甩锅。

10. 将 RPC 接口的类名及办法打印到日志中

D 瓜哥也在尝试一个实际:将 RPC 接口的类名和办法,再加上参数或者返回后果,同时打印到日志中。

这里为什么和下面的 将接口的参数和返回值原始数据打印到日志中 独自列出来?因为,在这个实际中,强调的是“RPC 接口”。相对来说,RPC 接口存在更多容易出错的问题,常常须要脱离零碎去独自测试 RPC 接口的可用性。把类名就办法名能够更不便在呈现问题时,就能够及时依据日志中的信息,去独自测试 RPC 的可用性。

11. 核心思想:以人为本,就近准则,触手可及

洋洋洒洒总结了这么几条倡议。这里做一个总结。

对于可维护性倡议的一个核心思想就是: 以人为本,就近准则,触手可及 。通常来说,人都是有肯定的惰性的。如果把饭端到眼前,置信任何正常人无奈抗拒美食的引诱。而这里提到的一些可维护性的点,就是尽可能关照人“懒”的个性,在第一次时,就把该做的工作做到位,缩小后续人员不必要的麻烦,让人能够“非法偷懒”。

加油!争取让更多人能够更好地偷懒。💪🏻💪🏻💪🏻

正文完
 0