微服务架构。这项在几年前还比较前卫的技术,如今却遍地开花。得益于开源社区的支持,我们可以很方便地利用SpringCloud和Docker容器化快速搭建微服务架构原型。无论是成熟的互联网公司、初创公司还是个人开发者,对微服务架构的接受度都相当高。微服务架构的广泛应用自然促进了技术本身更好的发展和更多的实践。本文将结合项目实践,分析在微服务的背景下,如何通过前后端分离的方式开发移动应用。对于微服务本身,我们可以参考MartinFowler对微服务的阐述。简单来说,微服务是一种架构风格。通过对特定业务领域的分析和建模,将复杂的应用分解为一组小型的、专用的、低耦合的、高度自治的服务。微服务中的每一个服务都是一个小的应用,这些应用服务是独立的,可以部署的。微服务通过拆分复杂的应用来达到简化应用的目的,而这些低耦合的服务通过API进行通信,所以服务之间暴露的是API,无论是资源的获取还是修改。这种微服务架构的理念与前后端分离的理念不谋而合。前端应用控制其所有的UI层逻辑,而数据层则通过微服务系统的API调用来完成。以JSP(JavaServerPages)为代表的前后端交互方式逐渐退出了历史舞台。前后端分离的快速发展也得益于前端Web框架(Angular、React等)的不断涌现,单页面应用(SinglePageApplication)迅速成为前端开发标准范式。再加上移动互联网的发展,无论是MobileNative开发方式,还是以ReactNative/PhoneGap等为代表的Hybrid应用开发方式,前后端分离使得Web和移动应用成为客户端。客户端只需要通过API查询和修改资源即可。一、BFF概述及演进BackendforFrontends(以下简称BFF),顾名思义,就是为前端而存在的后端(服务)中间层。也就是说,在传统的前后端分离应用中,前端应用直接调用后端服务,后端服务再根据相关数据进行增删查改等操作。商业逻辑。那么在引用了BFF之后,前端应用会直接和BFF通信,BFF通过API和后端通信,所以本质上,BFF更像是一个“中间层”的服务。下图展示了前端项目与没有BFF和BFF的前端项目的主要区别。1.无BFF的前后端架构在传统的前后端设计中,通常是app或者web直接访问后端服务,后台微服务相互调用,然后返回最终结果交给前端消费。对于客户端(尤其是移动端)来说,过多的HTTP请求是非常昂贵的,所以在开发过程中,为了尽量减少请求的数量,前端一般倾向于通过API来获取关联的数据。在微服务模式下,就是说有时候服务端为了满足客户端的需求,往往会做一些与UI相关的逻辑处理。2、BFF的前后端架构增加了BFF的前后端架构。最大的区别是前端(Mobile、Web)不再直接访问后端微服务,而是通过BFF层访问。每个客户端都会有一个BFF服务。从微服务的角度来看,有了BFF,微服务之间的相互调用就少了。这是因为一些UI逻辑是在BFF层处理的。2.BFF和APIGateway从上面对BFF的理解,既然BFF是前后端访问的中间层服务,那BFF和APIGateway有什么区别呢?我们先来看看APIGateway的常见实现方式。(APIGateway的设计方式可能有很多种,这里仅举以下三种)1.APIGateway的第一种实现方式:一个APIGateway为所有客户端提供相同的API。单个API网关实例为多个客户端提供相同的API。API服务,在这种情况下,API网关不区分客户端类型。即所有/api/users的处理是一致的,API网关不做任何区分。如下图所示:2.APIGateway的第二种实现方式:APIGateway为每种类型的客户端提供单独的API。APIGateway的单个实例为不同的客户端提供不同的API。比如访问用户列表资源,web端和App端分别通过/services/mobile/api/users和/services/web/api/users服务。APIGateway根据不同的API判断来自哪个客户端,然后分别处理返回不同客户端需要的资源。3.APIGateway的第三种实现方式:多个APIGateways分别为每一类客户端提供单独的API。在此实现中,对于每种类型的客户端,将有一个单独的API网关响应其API请求。所以BFF其实是APIGateway的一种实现方式。3.GraphQL和RESTGraphQL是一种用于API的查询语言,也是使用现有数据完成这些查询的运行时。GraphQL为您的API中的数据提供了完整且易于理解的描述,使客户能够准确地询问他们需要什么,仅此而已,随着时间的推移更容易发展API,并启用强大的开发人员工具。GraphQL作为一种API查询语句,由Facebook于2015年推出,主要是为了取代传统的REST模型。那么GraphQL和REST有什么区别呢?有什么相同点和不同点?我们可以通过下面的例子来理解。按照REST设计标准,所有的访问都是基于对资源的访问(增、删、查、改)。如果访问系统中的users资源,REST可以通过以下方式访问:Request:GEThttp://localhost/api/usersResponse:[{"id":1,"name":"abc","avatar":"http://cdn.image.com/image_avatar1"},...]如果使用GraphQL访问同一个请求,流程如下:Request:POSThttp://localhost/graphqlBody:query{users{id,name,avatar}}响应:{"data":{"users":[{"id":1,"name":"abc","avatar":"http://cdn.image.com/image_avatar1"},...]}}关于GraphQL更详细的用法,我们可以通过查看文档和其他文章来深入了解。与REST风格相比,GraphQL有以下特点:1.定义数据模型:按需获取GraphQL在服务端实现端,需要定义不同的数据模型。所有的前端访问最终都是通过GraphQL后端定义的数据模型进行映射和解析的。并且这种基于模型的定义可以按需获得。比如上面的/users资源的获取,如果客户端只关心user.id,user.name信息。那么客户端调用的时候,只需要在query中传入users{id\nname}即可。后台定义模型,客户端只需要获取自己关心的数据即可。2、分层查询一组用户数据,可能需要获取user.friends、user.friends.addr等信息,所以这次查询users实际上涉及到user、frind、addr三类数据。GraphQL对分层数据的查询大大减少了客户端的请求次数。因为在REST模式下,可能意味着每次获取到`user`数据,都需要重新发送API去请求好友接口。GraphQL使客户端能够通过数据分层通过API获取所有需要的数据。这就是GraphQL(图查询语言)这个名字的由来。{user(id:1001){//第一层名称,friends{//第二层名称,addr{//第三层国家,城市}}}}3.强类型constMeeting=newGraphQLObjectType({name:'Meeting',fields:()=>({meetingId:{type:newGraphQLNonNull(GraphQLString)},meetingStatus:{type:newGraphQLNonNull(GraphQLString),defaultValue:'}})})GraphQL的类型系统定义了Int、Float、String、Boolean、ID、Object、List、Non-Null等数据类型。因此在开发过程中,使用强大的强类型检查可以大大节省开发时间,同时也非常方便前后端调试。4.协议而非存储GraphQL本身不直接提供后端存储能力,也不绑定任何数据库或存储引擎。它使用现有的代码和技术来管理数据源。比如在BFF层使用GraphQL,这一层的BFF不需要任何数据库和存储介质。GraphQL只是解析客户端的请求,在知道客户端的“意图”后,通过访问微服务API获取数据,对数据进行一系列的组装或过滤。5.不需要版本控制.',resolve:(parent)=>{returnparent.file}},fileId:{type:newGraphQLNonNull(GraphQLID)}})})GraphQL服务器可以通过添加deprecationReason自动将字段标记为弃用。并且基于GraphQL的高扩展性,如果你不需要某个数据,只需要使用一个新的字段或者结构。旧的弃用字段为老客户提供服务,所有新客户使用新字段获取相关信息。并且考虑到所有的graphql请求都是按照POST/graphql发送的,所以在GraphQL中不需要进行版本控制。4、GraphQL与RESTGraphQL与REST的主要区别如下:数据获取:REST缺乏可扩展性,GraphQL可以按需获取。调用GraphQLAPI时,可以扩展payload;API调用:REST是每个资源操作的端点,GraphQL只需要一个端点(/graphql),只是postbody不同;复杂数据请求:REST对嵌套的复杂数据需要多次调用,GraphQL调用一次,减少网络开销;错误码处理:REST可以准确返回HTTP错误码,GraphQL统一返回200封装错误信息;版本号:REST通过v1/v2实现,GraphQL通过Schema扩展实现;5、微服务+GraphQL+BFF实践微服务下基于GraphQL构建BFF,我们已经在项目中开始了相关实践。在我们项目对应的业务场景中,微服务后台有近10个微服务,客户端包括4个不同角色的app和一个web端。对于每一类App,都有一个BFF与之对应。每个BFF仅服务于此App。BFF解析客户端请求后,会通过BFF端发现服务,通过CQRS去对应的微服务后台查询或修改数据。1.BFF端技术栈我们使用GraphQL-express框架搭建项目的BFF端,然后通过Docker进行部署。BFF与微服务后台之间,仍然通过registrator和Consul进行服务注册和发现。addRoutes(){this.express.use('/graphql',this.resolveFromRequestScopeAndHandle('GraphqlHandler'))this.serviceNames.forEach(serviceName=>{this.express.use(`/api/${serviceName}`,this.routers.apiProxy.createRouter(serviceName))})}在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放在后续的http请求在header信息中以JWT(JsonWebToken)的形式出现。在我们的系统中,BFF层不提供鉴权服务,所有的鉴权过程都由各自的微服务模块负责。BFF只提供中继功能。BFF是否需要集成鉴权,主要取决于各个系统本身的设计,并不是一个标准的做法。3.GraphQL+BFF实践通过以下几个方面,可以思考基于GraphQL的BFF的一些更好的特点:(1)GraphQL和BFF侧重于业务点从业务角度来看,PMApp(用户:物业经理)的重点是财产。物业经理管理着许多房屋,因此他们需要了解所有房屋的概况。对于每间房子,他们需要知道是否有相应的维修申请。所以PMAppBFF定义数据结构时,maintemamceRequests是property的子属性。类似的数据,SupplierApp(用户:房屋维修供应商)关注的是maintenanceRequest(维修工单),所以在SupplierApp获取的数据中,我们的主体就是maintenanceRequest。维护提供者关注workOrder.maintenanceRequest。因此,不同的客户端因为不同的使用场景,对同一份数据的关注点也不同。BFF是应用程序的伙伴。从这个角度来说,BFF中定义的数据结构才是客户端真正关心的。BFF为客户而生,是客户的一部分。需要注意的是,“关注业务”并不意味着BFF会处理所有的业务逻辑。业务逻辑仍应由微服务处理。BFF专注于客户的需求。(2)GraphQL对版本控制的支持假设BFF端已经发布到生产环境,提供检查相关的租户和房东查询。现在我们需要把图1的结构改成图2的结构,但是为了不影响老用户的API访问,这时候我们的BFFAPI必须兼容。如果在REST中,可以添加api/v2/inspections用于API升级。但是在BFF中,为了向前兼容,我们可以使用图3的结构。此时,旧APP使用黄色区域的数据结构,而新APP使用蓝色区域定义的结构。(3)GraphQLMutation和CQRSmutation{area{create(input:{areaId:"111",name:"test",})}}如果你详细阅读GraphQL文档,你会发现GraphQL分离了query和mutation。所有的query都应该使用query{...},相应的mutations需要使用mutation{...}。虽然看起来像是约定俗成,但GraphQL的这种设计恰好与后端API的读写职责分离(CommandQueryResponsibilitySegregation)不谋而合。其实我们在使用的时候也是遵循这个规范的。所有的mutation都会调用后台API,后台API对资源的修改也是SpringBootEventListener实现的一种CQRS模式。6、如何做好测试在引入BFF的项目中,我们的测试仍然采用金字塔原理,但是在客户端和后台之间,我们需要增加一个对BFF的测试。客户端集成测试关注的是App访问BFF的连通性。App中所有访问BFF的请求都需要测试;BFF的integration-test测试BFF对微服务API的连通性,所有BFF依赖的API都应该通过集成测试来保证;APIintegration-test关注服务暴露的所有API,通常测试Controller中的所有API。7.结语在微服务下基于GraphQL构建BFF并不是灵丹妙药,也不一定适用于所有项目。比如你在使用GraphQL之后,可能要面临多个查询性能问题等等,但这并不妨碍它成为一个好东西。尝试。你确实看到了,Facebook已经使用了GraphQL,Github也开放了GraphQLAPI。至于BFF,其实很多团队都已经实践过了。在微服务等特殊场景下,GraphQL+BFF可能会给你的项目带来惊喜。【本文为专栏作者“ThoughtWorks”原创稿件,微信公众号:Thinkworker,转载请联系原作者】点此查看该作者更多好文
