细说API – 文档和前后端协作

在上一篇文章——《细说API – 重新认识RESTful》中介绍了如何理解和设计RESTful风格的API,现在我们来聊聊如何有效的呈现API文档,以及前后端协作的方式。

我经历过一些没有文档的项目,前后端开发者坐到一起口口相传,或者有些团队用 word、pdf 来编写 API 文档。API 文档的缺乏给前后端协作带来困难,在缺乏专门工具的情况下,编写和维护文档是一件工作量巨大的事,人工处理也非常容易出错。

本文将会介绍三种方案来解决前后端协作的问题:

  1. 基于注释的 API 文档:这是一种通过代码中注释生成 API 文档的轻量级方案,它的好处是简单易用,基本与编程语言无关。因为基于注释,非常适合动态语言的文档输出,例如 Nodejs、PHP、Python。由于NPM包容易安装和使用,这里推荐 nodejs 平台下的 apidocjs。
  2. 基于反射的 API 文档:使用 swagger 这类通过反射来解析代码,只需要定义好 Model,可以实现自动输出 API 文档。这种方案适合强类型语言例如 Java、.Net,尤其是生成一份稳定、能在团队外使用的 API 文档。
  3. 使用契约进行前后端协作:在团队内部,前后端协作本质上需要的不是一份 API 文档,而是一个可以供前后端共同遵守的契约。前后端可以一起制定一份契约,使用这份契约共同开发,前端使用这份契约 mock API,后端则可以通过它简单的验证API是否正确输出。

基于注释的 API 文档

apidocjs 是生成文档最轻量的一种方式,apidocjs 作为 npm 包发布,运行在 nodejs 平台上。原理为解析方法前面的注释,使用方法非常类似 javadoc 等程序接口文档生成工具,配置和使用都非常简单。因为只是解析代码注释部分,理论上和编程语言无关。

安装:

npm install apidoc -g

在需要输出文档的源代码中添加一个一个注释示例:

最小化运行:

apidoc -i myapp/ -o apidoc

即可在 apidoc 中输出静态的 html 文档目录。如果指定配置文件 apidoc.json 可以定义更多的操作方式,也可以自定义一套 HTML 模板用于个性化显示你的 API 文档,另外在输出的 HTML 文档中附带有API请求的测试工具,可以在我们生成的文档中尝试调用 API。

使用 apidocjs 只需要添加几个例如 @api、@apiname、@apiParam 等几个必要的注释即可,值得一提是 @apiDefine 可以定义变量避免重复书写,@apiGroup 用来对 API 分组,@apiVersion 可以再生成不同版本的文档。

基于反射的 API 文档

apidoc 的缺点是需要维护一些注释,当修改源代码时需要注意注释是否同时被更新。不过如果你使用的是 Java、.Net 等强类型语言,就可以利用强类型语言的优势。

在这个领域最好用的文档工具当属 swagger,swagger 实际上是一整套关于 API 文档、代码生成、测试、文档共享的工具包,包括 :

  1. Swagger Editor 使用 swagger editor 编写文档定义 yml 文件,并生成 swagger 的 json 文件
  2. Swagger UI 解析 swagger 的 json 并生成 html 静态文档
  3. Swagger Codegen 可以通过 json 文档生成 Java 等语言里面的模板文件(模型文件)
  4. Swagger Inspector API 自动化测试
  5. Swagger Hub 共享 swagger 文档

通常我们提到 swagger 时,往往指的是 swagger ui。而在 Java 环境下,可以通过 Springfox 来完成对代码的解析,再利用 swagger 生成文档,下面我们给一个简单的例子看怎么给一个 Spring boot 项目生成文档。

首选加入依赖(gradle 同理):

全局配置:

我们的 controller,需要定义一些必要的注解来描述这个 API 的标题和解释,我们返回的 user 对象是一个简单 value object,swagger-annotations 包下面提供了很多注解可以满足更多的定制需求。

然后访问你的 context 下的 /{context}/swagger-ui.html 页面,你会看到一个漂亮的 API 在线文档。swagger 的文档上能看到具体的字段定义和 Model,如果修改了 Model,再次编译后则可以自动反应到文档上,这也是反应了强类型编程语言的优势之一。

基于契约的前后端协作

在过去的开发中,往往是后端开发者占主导,像上面的两种方案中,直接注释、反射通过生成 API 文档。

但前后端分离后让合作方式发生了变化。传统的方式往往是服务器开发者完成了 API 开发之后,前端开发者再开始工作,在项目管理中这样产生时间线的依赖。理想的情况下,在需求明确后,架构师设计,前后端应该能各自独立工作,并在最后进行集成测试即可。

后端开发者可以根据文档实现接口,最后按照文档联合调试即可,甚至通过契约生成 API 调用和数据承载的 VO (Value Object),减少工作量。如果 API 的提供者想做的更为完善一些,可以使用契约文件来验证实际 API 输出输出是否合理。

契约测试

当我们使用契约文件来提高前后端协作开发的体验,其中很重要的一部分就是契约测试,关于契约测试,我们一般指的是 Martin Fowler 提出的概念 —— “消费者驱动的契约”

简单来说,就是前后端开发者协定好后,由消费者驱动,通过编写 API 调用层相关的代码,可以直接生成契约文件。由于一个 API 可以被多处消费,所以消费者驱动可以更好的管理契约的变化(如果 API 验证契约时不能通过,说明契约被破坏了,可以在 CI 上马上反应出来)。

(Pact 契约测试模型)

写契约测试的博客非常多,就不展开赘述了。我把契约测试放到了前后端协作这个部分,是因为契约测试的前提是建立在前后端良好的协作下实现的。“契约测试”关注的是契约,而不是测试。

实际工作中,退一步说,制定好契约就可以完成基本的开发工作,对契约测试、验证可以让前后端协作变得更为可靠。如果你现在还没准备好使用契约测试的话,也不必焦虑,手动定义契约可以让前后端协作先运行起来。

而如果你恰好使用了 Spring boot 全家桶的话,不妨看看 Spring cloud contract。

使用 Swagger Yaml 契约

前面在讲 swagger 的时候,提到了Swagger Editor,使用这个工具可以通过编写 API 定义文件(Yaml格式),它提供线上版本,也可以本地使用。

后端通过生成 API 定义文件,就可以进一步完成生成 HTML 静态文档、模拟 API 数据等操作。

前端开发者可以通过 swagger 的 node 版本 swagger-node 自带的 mock 模式启动一个 Mock server,然后根据约定模拟自己想要的数据。 关于在前端使用的 mock server,实在太多,而且各有优劣,在附录中有一个清单以供参考,不再赘述。

使用 RAML 契约

使用 Swagger Yaml 契约或者 Pact 契约都能在一定程度上完成契约测试、生成文档、mock 等工作,但是我们在实际工作中发现这些工具和平台的契约规则并不相同。

Swagger 在生成文档上非常优秀,然而在契约测试上不及 Pact,反之亦然。

随着引入微服务和开放的互联网项目越来越多,前后端协作的问题越来越明显,而解决上述问题的工具和技术并不通用。好在业界早已认识到这个问题,于是一些组织提出了 RestFul API 统一建模语言 (RESTful API Modeling Language),也就是 RAML。

围绕着 RAML 这一标准,构建出 API 协作的工具链,设计、构建、测试、文档、共享。

其他前后端协作实践

中心文档服务器

在一个大型的团队中,可能会有几十个以上的项目同时提供了 API,这种情况下如果每个应用都各自提供API文档就会变得很难管理,如果将 API 文档绑定到应用服务上会带来一些无意义的损耗。可以使用一个集中地服务来存放这些文档,类似于 github 的私有仓库,swagger 同样也提供了类似的服务 – swaggerhub.com

即使不使用 swagger ,我们可以构建出 HTML 文档然后每一次输出部署到一台静态服务器,也是非常容易的事情。

  1. 如果是开源或者对外的 API,可以借用 GitHub Page 来创建我们的文档服务
  2. 针对团队内部,诸多云服务商均提供了静态服务器,例如 AWS 的 S3

管理契约文件

既然是契约文件,就不应该是API提供者或者消费者单独拥有的,即使只有一个调用方,至少是前端、后端共同拥有的。

那么契约文件应该怎么放呢?

我们之前一直放到API的代码仓库中,然后给所有的人添加了权限。后来发现这样做非常不方便,于是单独增加了一个管理契约文件的 git仓库,并使用 git 的submodule 来引用到各个涉及到了的代码仓库中。

将契约文件单独放置还有一个额外的好处,在构建契约测试时,可以方便的发送到一台中间服务器。一旦 API 契约发生变化,可以触发 API提供的契约验证测试。

附录:API 文档工具清单

  • 使用或调研过的,API 文档/契约生成工具
    1. apidoc
    2. swagger
    3. blue sprint
    4. RAML
  • 使用或调研过得 mock 工具清单
    1. wiremock
    2. json-server
    3. node-mock-server
    4. node-mocks-http
  • HTTP 请求拦截器
    1. axios-mock-adapter
    2. jquery-mockjax
  • 契约/API 测试工具
    1. Spring Cloud Contract
    2. Pact
    3. Rest-Assured

更多精彩洞见,请关注微信公众号:ThoughtWorks洞见

Share

细说API – 重新认识RESTful

如果你是一个客户端、前端开发者,你可能会在某个时间吐槽过后端工程师的API设计,原因可能是文档不完善、返回数据丢字段、错误码不清晰等。如果你是一个后端API开发者,你一定在某些时候感到困惑,怎么让接口URL设计的合理,数据格式怎么定,错误码怎么处理,然后怎么才能合适的描述我的API,API怎么认证用户的请求。

在前后端分离和微服务成为现代软件开发的大趋势下,API设计也应该变得越来越规范和高效。本篇希望把API相关的概念最朴素的方式梳理,对API设计有一个更全面和细致的认识,构建出更规范、设计清晰和文档完善的API。

重新认识API

广义的API(Application Programming Interface)是指应用程序编程接口,包括在操作系统中的动态链接库文件例如dll\so,或者基于TCP层的socket连接,用来提供预定义的方法和函数,调用者无需访问源码和理解内部原理便可实现相应功能。而当前通常指通过HTTP协议传输的web service技术。

API在概念上和语言无关,理论上具有网络操作能力的所有编程语言都可以提供API服务。Java、PHP、Node甚至C都可以实现web API,都是通过响应HTTP请求并构造HTTP包来完成的,但是内部实现原理不同。例如QQ邮箱就是通过使用了C构建CGI服务器实现的。

API在概念上和JSON和XML等媒体类型无关,JSON和XML只是一种传输或媒体格式,便于计算机解析和读取数据,因此都有一个共同特点就是具有几个基本数据类型,同时提供了嵌套和列表的数据表达方式。JSON因为更加轻量、容易解析、和JavaScript天生集成,因此成为现在主流传输格式。在特殊的场景下可以构造自己的传输格式,例如JSONP传输的实际上是一段JavaScript代码来实现跨域。

基于以上,API设计的目的是为了让程序可读,应当遵从简单、易用、无状态等特性,这也是为什么Restful风格流行的原因。

RESTful

REST(英文:Representational State Transfer,简称REST),RESTful是一种对基于HTTP的应用设计风格,只是提供了一组设计原则和约束条件,而不是一种标准。网络上有大量对RESTful风格的解读,简单来说Restful定义URI和HTTP状态码,让你的API设计变得更简洁、清晰和富有层次,对缓存等实现更有帮助。RESTful不是灵丹妙药,也不是银弹。

RESTful第一次被提出是在2000Roy Fielding的博士论文中,他也是HTTP协议标准制定者之一。从本质上理解RESTful,它其实是尽可能复用HTTP特性来规范软件设计,甚至提高传输效率。HTTP包处于网络应用层,因此HTTP包为平台无关的字符串表示,如果尽可能的使用HTTP的包特征而不是大量在body定义自己的规则,可以用更简洁、清晰、高效的方式实现同样的需求。

用我几年前一个真实的例子,我们为了提供一个订单信息API,为了更方便传递信息全部使用了POST请求,使用了定义了method表明调用方法:

返回定义了自己的状态:

大家现在来看例子会觉得设计上很糟糕,但是在当时大量的API是这样设计的。操作资源的动作全部在数据体里面重新定义了一遍,URL上不能体现出任何有价值的信息,为缓存机制带来麻烦。对前端来说,在组装请求的时候显得麻烦不说,另外返回到数据的时候需要检查HTTP的状态是不是200,还需要检查status字段。

那么使用RESTful的例子是什么样呢:

例子中使用路径参数构建URL和HTTP动词来区分我们需要对服务所做出的操作,而不是使用URL上的接口名称,例如 getProducts等;使用HTTP状态码,而不是在body中自定义一个状态码字段;URL需要有层次的设计,例如/catetory/{category_id}/products 便于获取path参数,在以后例如负载均衡和缓存的路由非常有好处。

RESTful的本质是基于HTTP协议对资源的增删改查操作做出定义。理解HTTP协议非常简单,HTTP是通过网络socket发送一段字符串,这个字符串由键值对组成的header部分和纯文本的body部分组成。Url、Cookie、Method都在header中。

几个典型的RESTful API场景:

虽然HTTP协议定义了其他的Method,但是就普通场景来说,用好上面的几项已经足够了

RESTful的几个注意点:

  • URL只是表达被操作的资源位置,因此不应该使用动词,且注意单复数区分
  • 除了POST和DELETE之外,其他的操作需要冥等的,例如对数据多次更新应该返回同样的内容
  • 设计风格没有对错之分,RESTful一种设计风格,与此对应的还有RPC甚至自定义的风格
  • RESTful和语言、传输格式无关
  • 无状态,HTTP设计本来就是没有状态的,之所以看起来有状态因为我们浏览器使用了Cookies,每次请求都会把Session ID(可以看做身份标识)传递到headers中。关于RESTful风格下怎么做用户身份认证我们会在后面讲到。
  • RESTful没有定义body中内容传输的格式,有另外的规范来描述怎么设计body的数据结构,网络上有些文章对RESTful的范围理解有差异

JSON API

因为RESTful风格仅仅规定了URL和HTTP Method的使用,并没有定义body中数据格式的。我们怎么定义请求或者返回对象的结构,以及该如何针对不同的情况返回不同的HTTP 状态码?

同样的,这个世界上已经有人注意到这个问题,有一份叫做JSON API开源规范文档描述了如何传递数据的格式,JSON API最早来源于Ember Data(Ember是一个JavaScript前端框架,在框架中定义了一个通用的数据格式,后来被广泛认可)。

JSON已经是最主流的网络传输格式,因此本文默认JSON作为传输格式来讨论后面的话题。JSONAPI尝试去提供一个非常通用的描述数据资源的格式,关于记录的创建、更新和删除,因此要求在前后端均容易实现,并包含了基本的关系类型。个人理解,它的设计非常接近数据库ORM输出的数据类型,和一些Nosql(例如MongoDB)的数据结构也很像,从而对前端开发者来说拥有操作数据库或数据集合的体验。另外一个使用这个规范的好处是,已经有大量的库和框架做了相关实现,例如,backbone-jsonapi ,json-patch。

没有必要把JSON API文档全部搬过来,这里重点介绍常用部分内容。

MIME 类型

JSON API数据格式已经被IANA机构接受了注册,因此必须使用application/vnd.api+json类型。客户端请求头中Content-Type应该为application/vnd.api+json,并且在Accept中也必须包含application/vnd.api+json。如果指定错误服务器应该返回415或406状态码。

JSON文档结构

在顶级节点使用data、errors、meta,来描述数据、错误信息、元信息,注意data和errors应该互斥,不能再一个文档中同时存在,meta在项目实际上用的很少,只有特别情况才需要用到,比如返回服务器的一些信息。

data属性

一个典型的data的对象格式,我们的有效信息一般都放在attributes中。

  • id显而易见为唯一标识,可以为数字也可以为hash字符串,取决于后端实现
  • type 描述数据的类型,可以对应为数据模型的类名
  • attributes 代表资源的具体数据
  • relationships、links为可选属性,用来放置关联数据和资源地址等数据

errors属性

这里的errors和data有一点不同,一般来说返回值中errors作为列表存在,因为针对每个资源可能出现多个错误信息。最典型的例子为,我们请求的对象中某些字段不符合验证要求,这里需要返回验证信息,但是HTTP状态码会使用一个通用的401,然后把具体的验证信息在errors给出来。

在title字段中给出错误信息,如果我们在本地或者开发环境想打出更多的调试堆栈信息,我们可以增加一个detail字段让调试更加方便。需要注意的一点是,我们应该在生产环境屏蔽部分敏感信息,detail字段最好在生产环境不可见。

常用的返回码

返回码这部分是我开始设计API最感到迷惑的地方,如果你去查看HTTP协议文档,文档上有几十个状态码让你无从下手。实际上我们能在真实环境中用到的并不多,这里会介绍几个典型的场景。

200 OK

200是一个最常用的状态码用来表示请求成功,例如GET请求到某一个资源,或者更新、删除某资源。 需要注意的是使用POST创建资源应该返回201表示数据被创建。

201 Created

如果客户端发起一个POST请求,在RESTful部分我们提到,POST为创建资源,如果服务器处理成功应该返回一个创建成功的标志,在HTTP协议中,201为新建成功的状态。文档规定,服务器必须在data中返回id和type。 下面是一个HTTP的返回例子:

在HTTP协议中,2XX的状态码都表示成功,还有202、204等用的较少,就不做过多介绍了,4XX返回客户端错误,会重点介绍。

401 Unauthorized

如果服务器在检查用户输入的时候,需要传入的参数不能满足条件,服务器可以给出401错误,标记客户端错误,需要客户端自查。

415 Unsupported Media Type

当服务器媒体类型Content-Type和Accept指定错误的时候,应该返回415。

403 Forbidden

当客户端访问未授权的资源时,服务器应该返回403要求用户授权信息。

404 Not Found

这个太常见了,当指定资源找不到时服务器应当返回404。

500 Internal Server Error

当服务器发生任何内部错误时,应当返回500,并给出errors字段,必要的时候需要返回错误的code,便于查错。一般来说,500错误是为了区分4XX错误,包括任何服务器内部技术或者业务异常都应该返回500。

HATEOAS

这个时候有些了解过HATEOAS同学会觉得上面的links和HATEOAS思想很像,那么HATEOAS是个什么呢,为什么又有一个陌生的名词要学。 实际上HATEOAS算作被JSON API定义了的一部分,HATEOAS思想是既然Restful是利用HTTP协议来进行增删改查,那我们怎么在没有文档的情况下找到这些资源的地址呢,一种可行的办法就是在API的返回体里面加入导航信息,也就是links。这样就像HTML中的A标签实现了超文本文档一样,实现了超链接JSON文档。

超链接JSON文档是我造的一个词,它的真是名字是Hypermedia As The Engine Of Application State,中文叫做超媒体应用程序状态的引擎,网上很多讲它。但是它并不是一个很高大上的概念,在RESTful和JSONAPI部分我们都贯穿了HATEOAS思想。下面给出一个典型的例子进一步说明:

如果在某个系统中产品和订单是一对多的关系,那我们给产品的返回值可以定义为:

从返回中我们能得到links中product的的资源地址,同时也能得到orders的地址,这样我们不需要客户端自己拼装地址,就能够得到请求orders的地址。如果我们严格按照HATEOAS开发,客户端只需要在配置文件中定义一个入口地址就能够完成所有操作,在资源地址发生变化的时候也能自动适配。

当然,在实际项目中要使用HATEOAS也要付出额外的工作量(包括开发和前后端联调),HATEOAS只是一种思想,怎么在项目中使用也需要灵活应对了。

参考链接

在文档中还定义了分页、过滤、包含等更多内容,请移步文档:

英文版:http://jsonapi.org/format/

中文版:http://jsonapi.org.cn/format/ (PS:中文版更新不及时,请以英文文档为准)


更多精彩洞见,请关注微信公众号:ThoughtWorks洞见

Share

数字化企业的API架构治理

前文中我们说到,传统企业在逐步建设自己的数字平台过程中,需要抓住交付基础设施、API和架构治理、数据自服务、创新实验基础设施和监控体系、用户触点技术这五个支柱。今天我们就来谈一谈API、架构治理这些听起来非常技术性的概念与企业的数字化战略之间有何关系。

企业资源服务化

从1990年代起,企业资源计划(ERP)一直是企业信息化的核心议题。植根于供应链管理,ERP通过对企业内部财务会计、制造、进销存等信息流的整合,提升企业的计划能力与控制能力。然而近年来,在互联网的冲击下,传统企业开始面临全新的挑战。尤其是在互联网的去中介化效应影响下,原本在供应链上下游各安其位的企业突然间都被压缩到了“生产-流通-消费”这个极度精简的价值链中。药品购销两票制就是这个极简价值模型的直观呈现。在这个模型中,掌握技术优势和消费者入口的互联网企业有可能形成一家独大的超级垄断,挤死传统的流通企业,把生产企业变成自己的OEM厂商,这是传统企业对来自互联网的竞争者恐惧的根源。

为了对抗互联网企业的竞争,传统企业最好的办法不是硬拼互联网上的技术和流量,而是在自己擅长的领域开战:把自己多年积累的线下资源变成线上服务,构建起本行业的线上生态系统,不仅支撑本企业的线上经营,而且为上下游周边企业提供线上经营的平台,从而把线下优势转化为线上优势,以资源优势对抗技术优势。

为了支撑企业资源的服务化,在设计在线服务的API和架构时需要考虑以下问题:

  • 平台架构和API的设计应该注重开发者体验。
  • 在API的背后,应该从业务功能的角度出发划分合理的限界上下文和服务边界,对外提供高内聚低耦合的服务。
  • 在服务边界之间,应该考虑使用异步的事件机制实现服务之间的通信,来解耦领域模型,客观地描述运行时间比较长、甚至本质上不可能立即完成的操作。
  • 为了方便使用,应该提供API网关作为所有服务使用者的单一入口,在API网关背后去处理众多内部IT系统的复杂性。
  • 整个API架构应该以微服务的风格呈现,避免典型SOA架构中普遍存在的过于复杂的ESB编排逻辑。

ERP之后是什么?

进入2010年代以来,“后ERP时代”这个说法不断被提出。在谈到ERP的发展方向时,通常都会涉及业务与技术两个角度。例如一种观点认为,ERP需要从以流程为中心转变为以客户为中心,并且需要用好云计算、社交网络、大数据和移动化等新技术。

ThoughtWorks认为,ERP在互联网时代的发展方向将是企业资源服务化(Enterprise Resource Servicification,ERS),通过数字平台的技术能力,把一家企业的资源融入一个行业的互联网生态,为企业铺下明确的数字化道路。

API和架构治理解读

下面我们来近距离看看,在“API和架构治理”这顶帽子下面,有哪些具体的问题需要被考虑到。

开发者体验

当企业资源以服务的形式对外提供,也就意味着不可能——像传统的IT系统建设那样——强迫别人使用这些服务。尤其是要把这些服务提供给第三方开发者、希望他们开发出形形色色的应用程序,那么服务的API是否易用就会很大程度上影响它能吸引到多少第三方开发者。ThoughtWorks第16期技术雷达还专门把开发者体验作为一个重要的技术主题。

在讨论开发者体验时,可以从开发工具和开发环境的安装、配置、管理、使用、维护等角度来考量。具体而言,开发环境和测试环境是否能弹性地随需获得,开发/测试基础设施和持续交付流水线是否以源代码的形式提供并完全自动化,是否提供对主流开源软件的支持,是否提供可编程的、命令行友好的(而不仅仅是图形化的)工具界面,安全、数据访问权限等企业规章是否严重影响开发者的效率和感受,这些都是影响开发者体验的要素。

服务边界

和所有的面向对象设计一样,服务的设计应该是高内聚低耦合的:与一个业务相关的修改只在一个服务内部进行,并且一个服务的修改/部署不需要影响其他服务。和一个代码库内部的对象设计不同,每个服务通常有专属的代码库,并且由专人负责维护(而不是所有人拥有所有代码),因此服务边界的改变会带来更大的变更成本。所以,服务边界的划分需要投入精力认真对待。

从设计原则上来说,服务的边界应该体现业务的边界,而不是单纯从技术角度出发划分服务边界。从业务功能的角度出发划分合理的限界上下文,以领域模型和领域事件的聚合为出发点来划分服务,更可能得出与业务边界一致的服务边界。随后再以业务目标驱动建设全功能一体化团队,就能做到业务、技术、团队三者对齐(康威定律再次起作用)。四色建模事件风暴等方法都能有效地实现领域驱动设计,从而建立起良好的领域模型及服务边界。

事件驱动架构

使用异步的事件机制实现服务之间的通信。对于运行时间比较长、甚至本质上不可能立即完成的操作(例如涉及人工操作),使用异步通信是合理的选择。即便不考虑响应的实时性,事件驱动的架构还表达了领域模型之间的松散耦合关系:跨领域的协作以事件而非方法调用的形式来表达,系统追求最终一致性而非强一致性。这一结构准确地映射了真实世界中多支相关但独立的团队之间的协作关系,避免了过度依赖其他服务的响应速度或可靠性等服务质量指标,使服务真正具有技术上的独立性。

在设计系统时,借助事件风暴方法,可以通过领域事件识别出聚合根,进而划分微服务的限界上下文。当出现跨多个聚合根的事件时,可以很自然地将其实现为异步的领域事件,从而获得与领域设计高度吻合的实现。关于如何设计和实现领域事件,可以参阅ThoughtWorks咨询师滕云的文章:《在微服务中使用领域事件》

在实现事件驱动的架构时,当然可以沿用传统的SOA架构中的消息中间件。但由于微服务架构中,业务逻辑都存在于各个服务内部,没有庞大臃肿的ESB(稍后我们还会详谈这个问题),因此消息机制也不需要强大的服务编排(orchestration)能力。RabbitMQ这样标准的消息代理当然很好,也有很多系统(例如Bahmni)采用更简单的做法:领域事件发生时,以ATOM格式发布;关心特定领域事件的其他领域模型则订阅特定的ATOM feed主题。这种基于HTTP的事件传播方式最大的好处就是简单,几乎不需要增加新的软件就可以实现。不过这个方案在处理低延迟的场景时表现不佳。

公共网关

微服务提供的API粒度与客户端的需求不同,所以客户端一个请求经常需要多个服务;服务端和客户端之间可能需要通信协议转换;不同的客户端对数据的需求不同,例如浏览器客户端需要的信息可能多于移动客户端;服务的终端信息(主机+端口)可能变化;不同数据片可能由不同的服务终端来提供——以上这些因素都指出:有必要对服务做一层门面封装,提供API网关作为所有服务使用者的单一入口点。

API网关处理请求的方式有两种:一种是直接代理/路由给合适的服务;另一种是由一个请求扇出/分发给多个服务。API网关可能针对不同客户端提供不同的API,可能包含针对客户端的适配代码。横切需求(例如安全)也可能在API网关实现。

当服务数量变多、API网关变大以后,维护一个通用的API网关会增加API网关层的复杂度,导致一个独立的“API团队”出现,协调和沟通的工作量加大。这时可以考虑引入公共网关的一个变体:为特定前端设计的后端(Backend For Frontend,BFF),即为每个前端应用提供一个单独的API网关,使对齐业务的一体化团队能够拉通前后端开发、而不必等待“API团队”完成他们的backlog。

API网关可以实现为一个独立的服务端应用,其代价则是增加一层复杂度(和出错的可能性)。为了降低这一代价,可以考虑用Zuul等工具来实现API网关。

微服务SOA拓扑

与传统的SOA架构相比,所谓“微服务”最大的特点可能就在于没有一个重量级的ESB。重量级的ESB有其历史原因。在2000年代业界刚开始采用SOA时,很多企业尽管把业务系统包装成了web服务,但IT团队的组织结构并没有发生改变,仍然是由一组人集中式地掌管整个业务流程——只不过系统集成的方式不再是直接的方法调用,而是服务编排(orchestration)。原本存在于集成代码中的复杂逻辑,现在被转移到了ESB中。而这个“ESB团队”成了IT交付的瓶颈:不论发布事件的服务还是消费事件的服务、或是编排逻辑本身的改变,与事件相关的变更都需要通过ESB团队。这个团队的backlog堆积起来,使得每个服务、每个应用都无法提供快速响应。

微服务架构更重视服务与业务的对齐。贝索斯所说的“两个pizza的团队”不仅负责一个IT系统的交付,而且要负责用这个IT系统来支撑一个业务的成功。为了做到单个服务能够独立开发、独立部署、独立运行,这支团队应该能够在很大程度上掌控自己的进度,而不依赖于一个集中式技术团队的进度。因此微服务应该通过服务注册与发现机制获得自己需要的依赖服务、自己判断是否要直接调用或订阅依赖服务的事件,每个服务包含与其业务对应的复杂度,而不是把整个系统的复杂度集中在ESB和编排逻辑上。整个系统的架构(以及团队的架构)应该呈现为若干个端到端拉通的、与业务对齐的纵切服务,而不是一个横切的大块(ESB)覆盖所有业务。

小结

为了激活企业线下资源、打造行业线上生态,IT需要一套有效的服务API和架构治理方法。首先从领域驱动设计入手,划分出合理的限界上下文和服务边界,然后用异步消息机制来描述领域事件。设计好的服务通过API网关或BFF暴露给前端应用,把依赖关系和集成逻辑约束在与业务对齐的一体化团队内部。在整个服务架构的设计中,需要保持对开发者体验的关注。顺畅地将企业资源服务化,这是企业数字化旅程的第二步。

了解更多关于数字平台战略的信息,可下载《数字平台战略》白皮书。


更多精彩商业洞见,请关注微信公众号:ThoughtWorks商业洞见

Share

信息爆炸?云上的机器人连接一切

前段时间,一条科技新闻惊爆整个互联网:微软举债262亿美元现金收购LinkedIn,创下科技史上收购价第三高的纪录。

为何要收购?微软CEO Satya Nadella在公开信中指出,“This deal brings together the world’s leading professional cloud with the world’s leading professional network.”依托世界上最大的专业化服务云与最大的专业化关系网,微软与LinkedIn走到一起,应用、数据与人连接在一起,将有可能构筑一个前所未有的智能、专业化生态。

无独有偶,在同一天的苹果WWDC大会上,苹果一口气发布了MacOS、iOS、WatchOS、tvOS四大操作系统。其中,最受关注的却是Siri、iMessage等软件的开放API。

1-api

从iPhone 4S时代走来的Siri第一个扛起“AI个人智能助理”的大旗,却接连被Microsoft Cortana、Amazon Echo、Google Allo等甩在身后。于是,苹果正式发布了Siri API,不久之后便可用它叫车、外卖、搜图、支付等。这一次,苹果希望以Siri为中心,将应用、数据连接在一起,重新构建真正的智能生活。

人类的历史就是一部信息传递与处理的历史。长城上的烽火把帝国的边疆阡陌连在一起,丝绸之路上的商人旅客让欧亚大陆连在一起,电话让登月的人类第一次从太空与地面进行沟通,互联网更是让人足不出户了然天下事……信息的载体从光、人、物、电磁波进化成了现代0和1的bit数字。与此同时,信息的范畴外延无比放大,从军情公文、家书鱼雁、各种软件数据到量化物体(物联网)、量化人类自己(可穿戴设备)……信息外延到了宏观的宇宙世界,又触及了微观的纳米世界。两波浪潮碰撞在一起,人类面对的是一个海量、碎片、实时、数字化的信息爆炸时代。

“弱水三千,取一瓢饮”,信息爆炸时代,人类要如何完成信息的高效处理?曾经的人类通过层级化的组织结构来小心地控制信息的输入与输出,现在的人类通过资源池化的信息引擎来完成信息的屏蔽过滤。然而,当万物连接的信息洪潮遮天蔽日地过来,曾经的组织和个体仿佛风雨中飘摇的枯叶一般弱不禁风,人类需要如何应对?在电影《环太平洋》中,面对宇宙中的洪荒巨兽,人类的机甲猎人被摧枯拉朽。这是否是人类的最终命运?

%e5%b1%8f%e5%b9%95%e5%bf%ab%e7%85%a7-2016-09-13-%e4%b8%8a%e5%8d%8811-47-45

问题太宏大,人类仍然需要回顾过去的经验,从历史的幽光中挖掘可能的解决办法。刘慈欣在科幻作品《三体》中讲述了一个科幻的故事:秦始皇的千万大军,通过黑白小旗和士兵矩阵,组建了与、或、非门,设计了一款人体计算机对微积分方程进行求解。这让我们不由沉思——低效的人类组织和协作,只要通过一些简单的规则,就可以对复杂的问题进行计算;同样,基于这些简单的规则,简单的元组件组合在一起,就能代替低效的人类组织和协作。

我们再来看看传统信息传递和处理的典型案例“长城烽火”。当敌人侵犯,守卫发现敌情,点燃烽火;附近的烽火台守卫看到烽火,点燃第二级烽火……以此类推,直到军队支援,消灭入侵敌人,消灭险情,烽火熄灭。整个过程主要涉及了如下的要素:

对象 职责 角色
烽火 烽火即警报,点燃烽火即为告警 信息协议,包括接口和格式
守卫 向烽火台网络里面输入敌情的告警信息 信息输入者
军队 处理烽火台网络输出的敌情告警信息 信息处理者
烽火台网络 确保敌情告警信息的正确传递和处理 信息传递、升级和协作体系

因此,我们可以抽象出信息处理的四个基本要素:协议、监控器、处理器,以及基于协议、由监听器和处理器组成的网络。无论是烽火狼烟,抑或是模电数电,都是基于下面的范式进行信息的采集、传递和处理,区别只在于效率和规模。面对“海量、碎片、实时、数字化”的信息洪潮,人类的终极信仰跃然纸上:云、机器人、API。

1-cloud

KK在《失控》中指出,“人造物越来越接近生命体,生命体越来越程序化。”连接和智能成为时代的主旋律,微软、苹果、腾讯、谷歌、阿里、百度……这些科技界巨头们无不提前进行结构性布局。多边平台的参与者越多,边际成本越低,网络效益最高。这也是为什么我们看到,微软不惜举债收购LinkedIn和Skype、苹果将Siri作为下一代个人智能助理的核心、腾讯将微信定位为“连接一切”……

外面的Interface很简单,但背后不显山不露水的恰恰是里子——微软的Azure和Office365云、苹果的iCloud、腾讯的腾讯云……微软、苹果、腾讯背靠各自的云和服务,坐拥无数的用户,或许正如唐太宗看见新晋进士鱼贯而出,得意洋洋地说道,“天下英雄,入吾彀中矣”。

尾声

在行文结束之前,我们再回顾一下本文的主要观点:

  • 人类需要处理的信息源将来自于自然万物:机器、软件应用、人体自己等等;
  • 海量、碎片、实时、数字化信息处理的核心在于云、程序性机器人和API接口;
  • 领先的科技公司都在这一战略逻辑之下进行结构性布局:建设云平台、将各种服务固化为程序性机器人、扩展与人类的信息输入和输出接口。

如果说微软通过Azure、Office365与LinkedIn连接是帮助职场专业人士更高效地完成办公信息的传递和处理,苹果的Siri API连接是帮助普罗消费者更高效地完成个人生活信息的传递和处理,软件研发运维的信息传递和处理如何实现呢?我们能否让每一位ThoughtWorker,从TechOps、到PS、再到TOC人员,都生出三头六臂?

 

Share