微服务架构,这个在几年前还算比较前卫的技术在现在遍地开花。得益于开源社区的支持,咱们能够轻松地利用 Spring Cloud 以及 Docker 容器化快速搭建一个微服务架构的原型。不论是成熟的互联网公司、创业公司仍是我的开发者,对于微服务架构的接纳程度都至关高,微服务架构的普遍应用也天然促进了技术自己更好的发展以及更多的实践。本文将结合项目实践,剖析在微服务的背景下,如何经过先后端分离的方式开发移动应用。html
对于微服务自己,咱们能够参考 Martin Fowler 对 Microservice 的阐述。简单说来,微服务是一种架构风格。经过对特定业务领域的分析与建模,将复杂的应用分解成小而专注、耦合度低而且高度自治的一组服务。微服务中的每一个服务都是很小的应用,这些应用服务相互独立而且可部署。微服务经过对复杂应用的拆分,达到简化应用的目的,而这些耦合度较低的服务则经过 API 形式进行通讯,因此服务之间对外暴露的都是 API,不论是对资源的获取仍是修改。前端
微服务架构的这种理念,和先后端分离的理念不谋而合,前端应用控制本身全部的 UI 层面的逻辑,而数据层面则经过对微服务系统的 API 调用完成。以 JSP (Java Server Pages) 为表明的先后端交互方式也逐渐退出历史舞台。先后端分离的迅速发展也得益于前端 Web 框架 (Angular, React 等) 的不断涌现,单页面应用(Single Page Application)迅速成为了一种前端开发标准范式。加之移动互联网的发展,不论是 Mobile Native 开发方式,仍是 React Native / PhoneGap 之流表明的 Hybrid 应用开发方式,先后端分离让 Web 和移动应用成为了客户端。客户端只须要经过 API 进行资源的查询以及修改便可。web
BFF 概况及演进数据库
Backend for Frontends(如下简称BFF) 顾名思义,是为前端而存在的后端(服务)中间层。即传统的先后端分离应用中,前端应用直接调用后端服务,后端服务再根据相关的业务逻辑进行数据的增删查改等。那么引用了 BFF 以后,前端应用将直接和 BFF 通讯,BFF 再和后端进行 API 通讯,因此本质上来讲,BFF 更像是一种“中间层”服务。下图看到没有BFF以及加入BFF的先后端项目上的主要区别。express
1. 没有BFF 的先后端架构后端
在传统的先后端设计中,一般是 App 或者 Web 端直接访问后端服务,后台微服务之间相互调用,而后返回最终的结果给前端消费。对于客户端(特别是移动端)来讲,过多的 HTTP 请求是很昂贵的,因此开发过程当中,为了尽可能减小请求的次数,前端通常会倾向于把有关联的数据经过一个 API 获取。在微服务模式下,意味着有时为了迎合客户端的需求,服务器常会作一些与UI有关的逻辑处理。api
2. 加入了BFF 的先后端架构服务器
加入了BFF的先后端架构中,最大的区别就是前端(Mobile, Web) 再也不直接访问后端微服务,而是经过 BFF 层进行访问。而且每种客户端都会有一个BFF服务。从微服务的角度来看,有了 BFF 以后,微服务之间的相互调用更少了。这是由于一些UI的逻辑在 BFF 层进行了处理。网络
BFF 和 API Gateway数据结构
从上文对 BFF 的了解来看,BFF 既然是先后端访问的中间层服务,那么 BFF 和 API Gateway 有什么区别呢?咱们首先来看下 API Gateway 常见的实现方式。(API Gateway 的设计方式可能不少,这里只列举以下三种)
1. API Gateway 的第一种实现:一个 API Gateway 对全部客户端提供同一种 API
单个 API Gateway 实例,为多种客户端提供同一种API服务,这种状况下,API Gateway 不对客户端类型作区分。即全部 /api/users的处理都是一致的,API Gateway 不作任何的区分。以下图所示:
2. API Gateway 的第二种实现:一个 API Gateway 对每种客户端提供分别的 API
单个 API Gateway 实例,为多种客户端提供各自不一样的API。好比对于 users 列表资源的访问,web 端和 App 端分别经过 /services/mobile/api/users, /services/web/api/users服务。API Gateway 根据不一样的 API 断定来自于哪一个客户端,而后分别进行处理,返回不一样客户端所需的资源。
3. API Gateway 的第三种实现:多个 API Gateway 分别对每种客户端提供分别的 API
在这种实现下,针对每种类型的客户端,都会有一个单独的 API Gateway 响应其 API 请求。因此说 BFF 实际上是 API Gateway 的其中一种实现模式。
GraphQL 与 REST
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
GraphQL 做为一种 API 查询语句,于2015年被 Facebook 推出,主要是为了替代传统的 REST 模式,那么对于 GraphQL 和 REST 究竟有哪些异同点呢?咱们能够经过下面的例子进行理解。
按照 REST 的设计标准来看,全部的访问都是基于对资源的访问(增删查改)。若是对系统中 users资源的访问,REST 可能经过下面的方式访问:
Request:
Response:
对于一样的请求若是用 GraphQL 来访问,过程以下:
Request:
Body:
Response:
关于 GraphQL 更详细的用法,咱们能够经过查看文档以及其余文章更加详细的去了解。相比于 REST 风格,GraphQL 具备以下特性:
1. 定义数据模型:按需获取
GraphQL 在服务器实现端,须要定义不一样的数据模型。前端的全部访问,最终都是经过 GraphQL 后端定义的数据模型来进行映射和解析。而且这种基于模型的定义,可以作到按需索取。好比对上文 /users资源的获取,若是客户端只关心 user.id, user.name信息。那么在客户端调用的时候,query中只须要传入 users {id\n name}便可。后台定义模型,客户端只须要获取本身关心的数据便可。
2. 数据分层
查询一组users数据,可能须要获取 user.friends, user.friends.addr等信息,因此针对 users 的本次查询,实际上分别涉及到对 user, frind, addr三类数据。GraphQL 对分层数据的查询,大大减小了客户端请求次数。由于在 REST 模式下,可能意味着每次获取 user数据以后,须要再次发送 API 去请求 friends 接口。而 GraphQL 经过数据分层,可以让客户端经过一个 API获取全部须要的数据。这也就是 GraphQL(图查询语句 Graph Query Language)名称的由来。
3. 强类型
GraphQL 的类型系统定义了包括 Int, Float, String, Boolean, ID, Object, List, Non-Null 等数据类型。因此在开发过程当中,利用强大的强类型检查,可以大大节省开发的时间,同时也很方便先后端进行调试。
4. 协议而非存储
GraphQL 自己并不直接提供后端存储的能力,它不绑定任何的数据库或者存储引擎。它利用已有的代码和技术进行数据源的管理。好比做为在 BFF 层使用 GraphQL, 这一层的 BFF 并不须要任何的数据库或者存储媒介。GraphQL 只是解析客户端请求,知道客户端的“意图”以后,再经过对微服务API的访问获取到数据,对数据进行一系列的组装或者过滤。
5. 无须版本化
GraphQL 服务端可以经过添加 deprecationReason,自动将某个字段标注为弃用状态。而且基于 GraphQL 高度的可扩展性,若是不须要某个数据,那么只须要使用新的字段或者结构便可,老的弃用字段给老的客户端提供服务,全部新的客户端使用新的字段获取相关信息。而且考虑到全部的 graphql 请求,都是按照 POST /graphql发送请求,因此在 GraphQL 中是无须进行版本化的。
GraphQL 和 REST
对于 GraphQL 和 REST 之间的对比,主要有以下不一样:
1. 数据获取:REST 缺少可扩展性, GraphQL 可以按需获取。GraphQL API 调用时,payload 是能够扩展的;
2. API 调用:REST 针对每种资源的操做都是一个 endpoint, GraphQL 只须要一个 endpoint( /graphql), 只是 post body 不同;
3. 复杂数据请求:REST 对于嵌套的复杂数据须要屡次调用,GraphQL 一次调用, 减小网络开销;
4. 错误码处理:REST 可以精确返回HTTP错误码,GraphQL 统一返回200,对错误信息进行包装;
5. 版本号:REST经过 v1/v2 实现,GraphQL 经过 Schema 扩展实现;
微服务 + GraphQL + BFF 实践
在微服务下基于 GraphQL 构建 BFF,咱们在项目中已经开始了相关的实践。在咱们项目对应的业务场景下,微服务后台有近 10 个微服务,客户端包括针对不一样角色的4个 App 以及一个 Web 端。对于每种类型的 App,都有一个 BFF 与之对应。每种 BFF 只服务于这个 App。BFF 解析到客户端请求以后,会经过 BFF 端的服务发现,去对应的微服务后台经过 CQRS 的方式进行数据查询或修改。
1. BFF 端技术栈
咱们使用 GraphQL-express 框架构建项目的 BFF 端,而后经过 Docker 进行部署。BFF 和微服务后台之间,仍是经过 registrator 和 Consul 进行服务注册和发现。
在 BFF 的路由设置中,对于客户端的处理,主要有 /graphql和 /api/${serviceName}两部分。/graphql处理的是全部 GraphQL 查询请求,同时咱们在 BFF 端增长了/api/${serviceName}进行 API 透传,对于一些没有必要进行 GraphQL 封装的请求,能够直接经过透传访问到相关的微服务中。
2. 总体技术架构
总体来看,咱们的先后端架构图以下,三个 App 客户端分别使用 GraphQL 的形式请求对应的 BFF。BFF 层再经过 Consul 服务发现和后端通讯。
关于系统中的鉴权问题
用户登陆后,App 直接访问 KeyCloak 服务获取到 id_token,而后经过 id_token透传访问 auth-api服务获取到 access_token, access_token 以 JWT (Json Web Token) 的形式放置到后续 http 请求的头信息中。
在咱们这个系统中 BFF 层并不作鉴权服务,全部的鉴权过程所有由各自的微服务模块负责。BFF 只提供中转的功能。BFF 是否须要集成鉴权认证,主要看各系统本身的设计,并非一个标准的实践。
3. GraphQL + BFF 实践
经过以下几个方面,能够思考基于 GraphQL 的 BFF 的一些更好的特质:
GraphQL 和 BFF 对业务点的关注
从业务上来看,PM App(使用者:物业经理)关注的是property,物业经理管理着一批房屋,因此须要知道全部房屋概况,对于每一个房屋须要知道有没有对应的维修申请。因此 PM App BFF 在定义数据结构是,maintemamceRequests是property的子属性。
一样相似的数据,Supplier App(使用者:房屋维修供应商)关注的是 maintenanceRequest(维修工单),因此在 Supplier App 获取的数据里,咱们的主体是maintenanceRequest。维修供应商关注的是 workOrder.maintenanceRequest。
因此不一样的客户端,由于存在着不一样的使用场景,因此对于一样的数据却有着不一样的关注点。BFF is pary of Application。从这个角度来看,BFF 中定义的数据结构,就是客户端所真正关心的。BFF 就是为客户端而生,是客户端的一部分。须要说明的是,对于“业务的关注”并非说,BFF会处理全部的业务逻辑,业务逻辑仍是应该由微服务关心,BFF 关注的是客户端须要什么。
GraphQL 对版本化的支持
假设 BFF 端已经发布到生产环境,提供了 inspection相关的 tenants和 landlords的查询。如今须要将图一的结构变动为图二的结构,可是为了避免影响老用户的 API 访问,这时候咱们的 BFF API 必须进行兼容。若是在 REST 中,可能会增长api/v2/inspections进行 API 升级。可是在 BFF 中,为了向前兼容,咱们可使用图三的结构。这时候老的 APP 使用黄色区域的数据结构,而新的 APP 则使用蓝色区域定义的结构。
GraphQL Mutation 与 CQRS
若是你详细阅读了 GraphQL 的文档,能够发现 GraphQL 对 query和 mutation进行了分离。全部的查询应该使用 query { ...},相应的 mutaition 须要使用mutation { ... }。虽然看起来像是一个convention,可是 GraphQL 的这种设计和后端 API 的 读写职责分离(Command Query Responsibility Segregation)不谋而合。而实际上咱们使用的时候也听从这个规范。因此的 mutation 都会调用后台的 API,然后端的 API 对于资源的修改也是经过 SpringBoot EventListener 实现的 CQRS 模式。
如何作好测试
在引入了 BFF 的项目,咱们的测试仍然使用金字塔原理,只是在客户端和后台之间,须要添加对 BFF 的测试。
Client 的 integration-test 关心的是 App 访问 BFF 的连通性,App 中全部访问 BFF 的请求都须要进行测试;
BFF 的 integration-test 测试的是 BFF 到微服务 API 的连通性,BFF 中依赖的全部 API 都应该有集成测试的保障;
API 的 integration-test 关注的是这个服务对外暴露的全部 API,一般测试全部的 Controller 中的 API;
结语
微服务下基于 GraphQL 构建 BFF 并非银弹,也并不必定适合全部的项目,好比当你使用 GraphQL 以后,你可能得面临屡次查询性能问题等,但这不妨碍它成为一个不错的尝试。你也的确看到 Facebook 早已经使用 GraphQL,并且 Github 也开放了 GraphQL 的API。而 BFF, 其实不少团队也都已经在实践了,在微服务下等特殊场景下,GraphQL + BFF 也许能够给你的项目带来惊喜。
参考资料
【注】部分图片来自网络
https://martinfowler.com/articles/microservices.html
https://www.thoughtworks.com/insights/blog/bff-soundcloud
http://philcalcado.com/2015/09/18/thebackendforfrontendpattern_bff.html
http://samnewman.io/patterns/architectural/bff
https://medium.com/netflix-techblog/embracing-the-differences-inside-the-netflix-api-redesign-15fd8b3dc49d
文/ThoughtWorks龚铭
原文连接:https://insights.thoughtworks.cn/use-graphql-build-bff-in-microservices/