1. 首页
  2. >
  3. 架构设计

微服务架构下的API接口驱动开发,设计和集成

微服务架构下的API接口驱动开发,设计和集成

今天谈下在微服务架构下,接口设计和开发方面的思考。

对于微服务架构,SOA和Http Rest API接口设计,在我前面的文章中均有专门的说明,因此对于基础方面的解释在本文不再重复。对于今天要写的内容,先总结一句话再展开说明。

在SOA和微服务架构思想下,除了常说的面向对象,领域驱动,SOA等架构思想外。还需要增加基于API接口驱动进行的设计和开发工作。API接口的识别,定义,设计和开发过程贯彻了整个微服务开发的生命周期。

软件生命周期

微服务架构下的API接口驱动开发,设计和集成

对于软件开发过程或生命周期,常会说到瀑布模型,增量和迭代模型,敏捷方法论等。即使到了迭代开发,对于全新驱动仍然强调一个关键点,即:

软件开发中应以架构为核心,架构应确保高度的概念一致性和完整性。

也就是说对于全新系统研发,即使要拆分和并行,也需要在架构设计完成后再进行,否则很难真正保证架构一致性和整个领域模型的完整性等。

从模块拆分到前后端分离

微服务架构下的API接口驱动开发,设计和集成

对于传统业务系统的开发,架构做了一个重要工作,即将大的业务系统拆分为多个子系统或者模块,同时定义清楚各个模块之间的接口。在这个工作完成后,后续各个模块的设计开发工作才能够真正并行起来。也就是说:

只要各个模块是按照预先定义好的接口进行设计开发的,那么最终各个模块就一定能够集成起来。架构师不单是做了分而治之的分解工作,而是通过接口解决了分解后的集成问题。

即使到了现在微服务架构模式下,对于究竟拆分为多少个模块,每个模块应该暴露哪些API接口服务应该是自顶向下方式,由架构师统一进行规划和设计。

微服务架构开发实际上重点解决两个阶段的关键问题

  • 规划设计阶段:通过架构设计来确定微服务拆分,关键接口API定义
  • 实现阶段:选择具体的某个微服务开发框架进行功能的开发实现

其次,在微服务开发中,引入了另外一个关键即前后端分离

前后端分离实际和SOA思想里面的服务分层思路相对一致,至少SOA跨系统间的服务分层,服务组织思想进入到单个微服务内部的功能实现机制上。

前后端分离实际上当前是包括了技术和团队组织两个方面

  • 在技术上前端和后端完全拆分,都可以独立编译打包
  • 在团队上形成前端和后端角色,不再是一个人前后贯通实现

在分离后开发分工越来越细,各自都可以专注自己的内容做到更加高效,一个前端往往也可以对应多个后端开发。而前端和后端协同的关键就变成了Rest API接口服务调用。

接口先行和驱动的开发

微服务架构下的API接口驱动开发,设计和集成

那么当前微服务开发,有多少团队是真正将接口定义和设计清楚后,各自基于严格约定的接口契约进行并行开发的?

实际上很多团队并没有严格这样做。

前端说需要一个接口了,后端临时再做一个,或者后端因为另外一个功能实现影响将接口调整了也不通知前方等,这些都是实际经常出现的情况。

即接口本身严肃性不够,导致接口新增和变更都相对随意,也导致了接口很难处于一个稳定的状态。在接口无法稳定的情况下,导致团队各个角色的开发都受到影响。

接口驱动开发的核心思路就是在微服务开发过程中优先定义和设计接口,确定API接口模型和接口契约,然后后端,前端,基于同样的接口标准并行开展工作。

接口定义清楚后,可以生成相应的代码框架或开发框架,前端和后端基于同样的开发框架进行接口开发工作。对于后端人员应该优先实现接口,再实现非接口部分内容,后端人员实现的接口可以自己进行单元测试。而对于前端在进行开发的时候,由于有了接口契约,可以通过Mock的方式提供模拟接口数据,方便进行前端功能开发和测试。

只有这样,才能够真正做到全并行开发和开发后的集成工作。

为什么接口驱动很难?

实际在实践的过程中你会感觉到很难,就是前期定义接口往往在后期仍然大量变动和增加,但是这不是不用接口驱动的理由。就如我们不能因为需求会经常变更,而不先进行需求分析和开发一样的道理。

接口频繁变动实际本身包括两个方面的原因。

其一是需求本身就不清晰,往往是功能做到哪里想到哪里,这样自然会导致接口不断地增加或者变更,导致大量重复工作。其二是在进行接口定义设计的时候,没有考虑接口应该是提供领域服务能力,而不是提供大量的数据库表的CRUD能力,没有考虑很多逻辑应该是内聚在后端完成聚合和组装,而跑到前端进行组装。

以上两个就是接口频繁变动的关键原因。因此要推进接口驱动,首先要加强需求输出的严谨性,其次要加强领域设计能力,做好接口的设计和抽象。

接口驱动开发过程

微服务架构下的API接口驱动开发,设计和集成

在这里先看下关于面向接口编程的一些说明。

我们在一般实现一个系统的时候,通常是将定义与实现合为一体,不加分离的,我认为最为理想的系统设计规范应是所有的定义与实现分离,尽管这可能对系统中的某些情况有点麻烦。

在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。

面向接口编程就是指按照这种思想来编程。

对接口的理解

接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。接口的本身反映了系统设计人员对系统的抽象理解。

在设计模式里面经常会提到面向接口而设计,同时强调尽量要少用继承而多用组合。面向接口设计一方面是更加方便的应对和适配底层变化,比如底层实现的变化;另外一个方面就是接口定义清楚后对于接口依赖端可以并行开展工作。

同时还可以看到通过面向接口编程方式,可以最大限度地对外部屏蔽接口内部实现细节,一个模块对外开放能力只需要开放接口定义格式和描述,而不用开放具体的实现细节。

定义和实现分离,达到了进一步的安全方面要求。

接口在架构设计中起到关键作用

在前面已经谈到接口定义和设计在架构设计里面起到关键作用,这种接口的定义除了上述的作用外,更加重要的是实现了系统分解后,各个子系统和模块只要遵守共同的接口定义契约,就可以开始并行开发和实现。

这也是我们在实施大型SOA集成项目经常谈到,接口定义和设计先行,通过统一标准的接口契约来实现接口开发和实现的并行。如下图:

微服务架构下的API接口驱动开发,设计和集成

接口先行的目的就是大家遵循同样的标准,那么后续各个组件就能够无缝地集成到一起。否则接口实现不一致,那么后续就无法进行集成,导致功能和接口变更。

基于接口驱动来实现完整的产品开发和集成

在微服务开发过程中,整个微服务划分和微服务间的接口设计仍然需要保持高度的架构完整性和概念一致性。即首先通过架构人员进行微服务拆分,关键接口设计,其次才是进行各个微服务模块的开发,在开发完成后进行集成工作。

微服务架构下的API接口驱动开发,设计和集成

如上图,大家遵循同样的接口契约,那么后端开发,前端开发和测试人员可以并行开始各自的工作。对于前端优先进行接口开发和实现,前端则通过接口契约产生Mock模拟,通过接口模拟实现来进行前端功能的开发。在前后端开发过程中,测试人员也可以根据接口定义进行测试设计工作,同时进行相关的测试脚本设计或录制工作。

接口开发完成后,前端和后端首先各自进行单元测试,在单元测试完成后进行前后端的集成测试和验证。同时测试人员可以启动相应的接口自动化测试工作。

前后端分离后的问题

当我今天写这篇文章的时候,进一步思考了下前后端分离开发的一些问题。

比如在传统开发模式下,一个功能实现都由张三负责,那么前端和后端都是张三来做,张三对整个功能业务和逻辑都了解,因此可以既完成接口层的单元测试,也可以完成前端页面黑盒驱动的功能自测试工作。

当功能进行前后端分离开发后,张三仅负责后端,前端由小敏负责。

而这个时候张三往往对整个前端功能实现和页面都不关心,仅仅关注自己逻辑实现和接口提供,关注自己的单元测试验证。而小敏负责前端,实际上小敏对整个功能的业务逻辑和场景往往也并不清楚,仅仅只能够测试数据的录入,查询装载等正常。

即这个时候张三和小敏都无法完整地进行前端功能页面的测试,这个时候导致很多测试工作都转到测试阶段后由测试人员才能够测试和发现。

这本身也就导致了功能实现问题的进一步朝后面泄露。

如何解决这个问题?

第一个方法就是负责后端的张三必须做完整的单元测试,而这个工作量极大,大部分开发人员实际都无法做完整的单元测试。第二个方法就是该工作还是由小敏负责,那么小敏就必须参与前期需求和接口评审,熟悉需求和业务场景,才能够展开。

规模不大的项目没必要前后端分离

这个也是我在前面强调过的要给观点,尽管前后端分离可以进一步实现微服务间的解耦,但是也增加了单个功能实现多个角色之间沟通的协同量。

项目规模再小,你还得找到要给后端角色或前端开发两个角色,或者你需要找到一个很厉害的都懂的全栈人员。而实际上前后端分离本身带来大量的集成工作量,同时后端分离的API接口本身并没有体现出应有的粗粒度和复用价值。

本来一个简单项目,前端用easyui就能搞定,结果用微服务和前后端分离后却越搞越复杂。

Http Rest API接口设计

对于HTTP Rest接口的设计,网上已经有很多文章都有详细的阐述,今天再重新整理下这里面的一些重点,大家都清楚Rest接口是面向资源的接口设计方法,而且基于原生的Http协议,因此里面就有两个最关键的点,一个就是对资源的理解,一个就是对操作的理解。

微服务架构下的API接口驱动开发,设计和集成

图片来源网络

什么是RESTful架构,重要的几点如下:

  1. 每一个URI代表一种资源;
  2. 客户端和服务器之间,传递这种资源的某种表现层;
  3. 客户端通过四个HTTP动词(GET/POST/PUT/DELETE),对资源进行操作

对资源的理解

就是我们平常上网访问的一张图片、一个文档、一个视频等。这些资源我们通过URI来定位,也就是一个URI表示一个资源。资源是做一个具体的实体信息,它可以有多种的展现方式。而把实体展现出来就是表现层,例如一个txt文本信息,它可以输出成html、json、xml等格式,一个图片他可以jpg、png等方式展现,这个就是表现层的意思。

URI确定一个资源,但是如何确定它的具体表现形式呢?应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对”表现层”的描述。

对操作的理解

客户端能通知服务器端的手段,只能是HTTP协议。

具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。

对于资源的任何操作,都应该映射到HTTP的几个有限的方法(常用的有GET/POST/PUT/DELETE 四个方法,还有不常用的PATCH/HEAD/OPTIONS方法)上面。所以RESTful API建模的过程,可以看作是具有统一接口约束的面向对象建模过程。

按照HTTP协议的规定,GET方法是安全且幂等的,POST方法是既不安全也不幂等的(可以用来作为所有写操作的通配方法),PUT、 DELETE方法都是不安全但幂等的。将对资源的操作合理映射到这四个方法上面,既不过度使用某个方法(例如过度使用GET方法或POST方法),也不添 加过多的操作以至于HTTP的四个方法不够用。

是否全用POST方法实现接口?

在接口设计里面经常会遇到一个问题,即是否全部用POST方法来实现接口,如果图简单省事,那么全部用POST方法是最省事的方式。

对于Get方法相对Post方法来说究竟有哪些优势?网上一段话可以参考如下: GET 的URL可以人肉手工在地址栏输入啊。。。你在地址栏打个POST给我看看。本质上面, GET 的所有信息都在URL, 所以可以很方便的记录下来重复使用。

也就是说通过Get方法可以更好地方便测试,验证,缓存等。

如果不考虑这点,确实可以完全用Post方法来实现所有Rest API接口。但是我们仍然建议对于简单的对象获取,根据Key值获取等仍然可以实现为Get方法。

其次对于比较复杂的模糊查询,还涉及到排序,多条件过滤的,虽然网上也有Rest API规范来强调用Get方法如何来定义,但是我们仍然推荐还是采用POST方法通过Body传递更合适。

反之,涉及到数据新增修改等操作,再简单也不建议通过Get方法进行。

API接口的版本问题

对于API接口的版本定义,仍然推荐在IP地址或域名后先定义版本,再定义详细的对象集合和ID信息。具体如下:

GET https://{serviceRoot}/{collection}/{id}

GET https://www.aa.com/v1.0/user/123/

当然还有一种方法是在后面添加版本,比如:

https://api.contoso.com/user/123?version=v1

这种方式为何不好?

即我们很难通过前面的接口定义和URL信息明确地知道究竟是消费哪个版本,而具体接口的版本信息必须动态在调用的时候参数化传递。

那么我们就很难将显性化的细粒度控制到具体的版本。特别是在接口启用大小版本的情况下,实际上大小版本已经是两个输入输出有明显差异的服务,必须单独控制。

包括用这种动态方式,API接口不同版本要注册接入到API网关本身也相当麻烦难以实现。当然如果你全部用POST方式实现接口,那么版本信息定义在路径末尾也是可以的,比如:

https://api.contoso.com/GetPeopleByID/V1

具体更加详细的说明可以参考微软的Rest API接口定义规范。

方法和工具选择

主流的Swagger设计工具

微服务架构下的API接口驱动开发,设计和集成

对于设计工具,这里只谈下Swagger设计工具,这个工具最大的好处就是只需要通过编辑器进行Rest接口设计文件的定义,然后可以自动生成测试框架,自动生成客户端和服务端多种语言下的开发框架,生成API接口设计文档,这个工具本身设计完成内容还可以导出为YAML文件或Json文件,也支撑文件导入。

在采用Swagger工具的时候,我们希望的步骤为:

首先是通过Swagger Editor进行接口文件的定义,对于接口文件本身的定义建议仍然进行分域而不是全部都定义到一个文件里面。

其次基于接口定义,通过Swagger提供的CodeGen功能来生成RestClient,或者整个SpingBoot项目。这个生成既需要包括客户端消费框架,也需要包括服务提供端框架。类似原来基于WSDL文件,用CXF框架来生成代码框架一样。

注意网上也有Spingcloud框架来集成Swagger的文章,更多的则是对已经定义好的接口来生成API接口文档,这并不是完整的接口驱动开发。

再次,通过接口定义来自动生成Mock数据,可以通过Swagger Json文件定义格式在前端通过JS来产生mock数据,也可以搭建一个完整的Mock Server产生数据,在这个步骤完成后,前端开发人员可以开始并行开发工作。

最后,API接口文档生成,Swagger在完成了接口定义后本身就已经形成了完整的接口定义,这个定义本身可以导出为完整的Html文档,也可以自己写代码进一步自动转化为Word文档。

即基于Swagger这类工具,再加上少量的定制,基本就可以实现一个完整的接口驱动开发中所需要的所有文档,代码开发框架等。