边缘服务的一致性、耦合和复杂性

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"技术公司采用微服务架构已经十多年了,结果好坏参半。微服务之间的依赖关系导致在修改一个服务时也需要修改其他服务,微服务的优势因此打了折扣。这就是所谓的紧密耦合。但组件之间的依赖关系是不可避免的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果微服务不能满足用户的需求,那它还有什么用?本文重点讨论如何维护有用的微服务,并能够从微服务的迁移流程中得到好处。关键的是要在各个层保持清晰的关注点分离,并遵循最适合每一个层的设计原则。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"RESTful API设计"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2000年,Apache HTTP服务器联合创始人发表了一篇题为“网络软件架构的架构风格和设计”的博士"},{"type":"link","attrs":{"href":"https:\/\/www.ics.uci.edu\/~fielding\/pubs\/dissertation\/fielding_dissertation.pdf","title":null,"type":null},"content":[{"type":"text","text":"论文"}]},{"type":"text","text":"。其中的第5章介绍了REST (Representational State Transfer),作为分布式超媒体系统的一种架构风格。这篇有关Richardson成熟度模型的"},{"type":"link","attrs":{"href":"https:\/\/martinfowler.com\/articles\/richardsonMaturityModel.html","title":null,"type":null},"content":[{"type":"text","text":"博文"}]},{"type":"text","text":"是了解REST在API设计中所起作用的一个很好的资源。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在API设计和HTTP标准之间存在着紧密的一致性。在RESTful API设计中,URI的路径部分用于标识特定实体(也称为资源)。HTTP谓词用于标识要对实体执行的操作类型。实体可以通过其他实体的URI路径部分链接到其他实体。对于HTTP状态代码含义的解释也存在一致性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提供在线应用程序的公司将API设计成平台,他们之所以这样做有很多原因。或许,他们希望从第三方那里获得额外的收入来源,或者向高级用户追加销售。或许,他们希望让不同的团队更容易调用彼此的API。或许,他们希望以这样一种方式来组织他们的API,让它们可以很容易被相同产品族中的类似或相关的产品所重用。最后,他们希望设计出易于进行自动化测试的API。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"就像提供在线应用程序的公司开发便于用户理解的GUI应用程序一样,平台公司也应该开发便于开发人员理解的API。RESTful API非常适合这一需求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在RESTful API出现之前,API是基于所谓的远程过程调用(Remote Procedure Call,RPC)而设计的。这些API的设计不存在一致性,以致于难以看出它们是干什么用的。REST在API设计中引入了一致性。当你将REST与OpenAPI结合在一起时,开发人员很容易就知道如何使用你的API。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/e8\/e8c10d9c9dc78d8e5f35f4b9b2cf167e.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"一个基本的新闻源RESTful API Swagger规范。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"后端服务可以很容易地调用RESTful API,但对于前端应用程序来说就不那么容易了。这是因为好的用户体验不那么RESTful。用户不想要背后满是碎片化实体的GUI。他们希望一下子看到所有的东西,除非在设计上确实需要渐进显示。例如,我不想在规划旅行行程时打开多个页面,我希望在下订单之前能够在一个页面上看到所有的摘要信息(包括航班、汽车租赁和酒店预订)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当用户导航到一个Web页面或链接到单页应用程序(SPA)或移动应用程序的某个视图时,前端应用程序需要调用后端服务来获取渲染视图所需的数据。如果使用的是RESTful API,单个调用不太可能获得所有的数据。通常是先执行一个调用,然后前端代码遍历该调用的结果,并对每个结果项进行更多的API调用,以获得所需的所有数据。这不仅使前端开发复杂化,而且还增加了应用程序的页面加载时间。稍后再详细介绍。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里还有另一个问题,即RESTful API与前端GUI关注点不是很契合。RESTful API本身不支持推送通知,但支持回调(通过WebHook实现)。WebHook对推送通知的支持程度不如WebSocket。WebHook和WebSocket的不同之处在于,Web浏览器不支持WebHook,但支持WebSocket。这是因为WebSocket是由前端发起的,并与后端保持连接,后端会向前端发送更新。WebHook是由后端发起的,但浏览器没有一个固定的IP地址来接收这些请求。因为路径在RESTful API中被用于标识一个特定的实体,所以请求和响应的格式不应该发生明显的变化。为了节省连接资源,SPA可能会为所有类型的推送通知打开单个WebSocket,允许每一条消息的格式之间存在巨大差异。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新的用户需求(如增加额外的数据字段)可能需要前端和后端都作出修改,这是导致紧密耦合的根本原因。团队之间的紧密耦合降低了开发速度,这个可以用康威定律来解释。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"跨团队的沟通成本要高於单个团队内部的沟通成本。同时拥有前端和后端开发人员的团队也可能缺乏效率。虽然从理论上讲,前后端开发人员处在同一个团队中,但在前端和后端开发人员之间仍然存在分界线。这种“隐式”的子团队隐藏了软件开发的一些复杂性,可以说是康威定律的一个不成文的补充。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当后端服务和前端应用程序发生紧密耦合时,发布管理也会变得复杂。微服务的最大优势之一是你不必一次性发布所有的内容,但紧密耦合的组件通常需要在同一时间发布,如果一个组件需要回滚,其他组件也都需要回滚。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"GraphQL API设计"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2015年,Facebook采用了一种不一样的API设计方法,即图查询语言(GQL)或GraphQL。它是一种包含层次结构类型的模式,该模式包含三种特殊类型:查询、变异和订阅。调用者发送一个命令,该命令提供查询条件,并指定在响应中期望得到的数据格式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最为流行、功能齐全且成熟的GraphQL服务器端框架实现是由旧金山的一家名为"},{"type":"link","attrs":{"href":"https:\/\/www.apollographql.com\/","title":null,"type":null},"content":[{"type":"text","text":"Apollo"}]},{"type":"text","text":"的小型初创公司开发的。有了他们的框架,在客户端增加新功能就变得非常容易,且无需对服务器作出大量修改。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"后端开发人员必须编写schema和解析器。框架调用在请求中指定的解析器,然后将每个解析器的响应拼接在一起。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/51\/5174b6832cb459ca9f50a30c5d0f9282.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"类似的基本新闻提要的GraphQL schema。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于解析器位于属性级别,而且获取底层数据的机制可能一次性获取多个属性,因此存在重复获取相同数据的可能性,造成了浪费。这就是所谓的N+1问题。后端代码应该用某种类型的请求缓存来缓解这个问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基于生存时间值(TTL)、最近最少使用原则(LRU)的缓存在GQL中的作用是有限的。因为有效载荷是可以灵活指定的,所以很难实现高命中率和低脏读率的高效缓存。因此,GQL缓存往往要比RESTful缓存大得多。Apollo GraphQL框架支持在schema中使用缓存提示注解或在解析器中动态设置,这可以通过浏览器端缓存或内存缓存或外部缓存(如Memcached或Redis)来实现。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"变异部分与RPC相似,对于它们的处理方式并没有定义好的标准,所以它们都不好理解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在撰写本文时,GraphQL的应用程序性能监控(APM)还没有RESTful API那么成熟。GraphQL没有提供内建支持,但有一些插件或解决方案可用于"},{"type":"link","attrs":{"href":"https:\/\/newrelic.com\/blog\/nerdlog\/apollo-server-plugin","title":null,"type":null},"content":[{"type":"text","text":"New Relic"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https:\/\/docs.datadoghq.com\/integrations\/apollo\/","title":null,"type":null},"content":[{"type":"text","text":"Data Dog"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https:\/\/grafana.com\/docs\/grafana-cloud\/reference\/integrations\/integration-apolloserver\/","title":null,"type":null},"content":[{"type":"text","text":"Prometheus"}]},{"type":"text","text":"和"},{"type":"link","attrs":{"href":"https:\/\/docs.appdynamics.com\/21.7\/en\/application-monitoring\/configure-instrumentation\/transaction-detection-rules\/custom-match-rules\/node-js-business-transaction-detection","title":null,"type":null},"content":[{"type":"text","text":"App Dynamics"}]},{"type":"text","text":"。我相信,随着时间的推移,Apollo风格的GraphQL APM监控将变得更加主流。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在RESTful API中,客户端指定路径,可能是查询字符串参数,可能是身份验证,仅此而已。而在GQL中,客户端必须指定有效载荷是什么样子的。这种小程序增加了调用GQL服务的复杂性,从而增加了出现错误的可能性。这也提高了自动化测试的成本。通常的方法是在测试自动化中查询所有的内容,这样做应该足够了,除非解析器需要通过上下文对象交换带外数据。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"关注点分离"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"API设计该基于REST还是GQL,关键在于你要理解一个计算机科学概念,也就是关注点分离(SoC)。一个设计良好的软件通常由多个层组成,每个层又分为多个模块。如果每个层和每个模块都有清晰严格的关注点分离,那么软件就容易理解,复杂度也更低。为什么会这样?如果你知道在哪里可以找到某个功能的实现代码,你很快就会知道该如何去阅读它的代码(很可能会跨多个代码库)。就像REST和GQL在API设计方面所提供的一致性一样,清晰的SoC提供了一种一致性的方式用于找到每个功能的实现。开发人员很少会在他们了解得很透彻的软件中引入bug。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SoC的标准由软件架构师来设定。以下是不同层的分类以及每个层应该关注什么。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常来说,在现代商业软件中,最主要的层是前端和后端。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前端软件的直接交互对象是用户,通常运行在移动设备或笔记本电脑上。前端包括移动应用和Web应用,主要是关于渲染、绑定、交互和用户体验。其内部结构类似模型视图控制器(MVC)的变种。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"后端软件的交互对象是前端软件。在生产环境中,后端软件通常运行在数据中心(如公有云)的服务器上。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"后端又被进一步分为数据、边缘和集成服务。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"数据服务为数据库提供保护、执行业务规则、维护一致性,并专注于可伸缩性、性能和潜在的弹性问题。其内部结构包括资源控制器、服务、模型和数据访问对象(DAO)。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"边缘服务负责处理推送通知、跨端点聚合和安全问题。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"集成服务应该作为第三方应用的反应式抗腐蚀层,如电商网站(后端集成)和电子表格(前端集成)。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"还有其他一些类型的服务这里没有提及。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个功能全面的服务网格可用于处理弹性、发现、内部认证、加密和可观察性问题。要实现可观察性,需要与其他类型的服务发生交互,获得监控、告警、日志聚合,甚至是分布式追踪能力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在架构师看来,当开发人员决定模糊这些SoC(通常是图一时的方便),就是系统开始陷入麻烦的开始。例如,因为DevOps的不完善,你决定让数据服务来处理边缘服务或集成服务应该处理的问题,或者让前端应用做一些本该由后端完成的事情。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"现代技术栈"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大约在六年前,我发现了一种所谓的BFF边缘服务。客户端应用程序不会直接调用数据服务,而是通过中间服务来调用,中间服务专门用来满足客户端应用程序的需求。如果设计得当,这种方式可以将数据服务与不断变化需求的GUI解耦开来。虽然数据服务仍然与数据库紧密耦合,但数据库schema变化速度相对较慢。至少对于相对成熟的项目来说是这样的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那么BFF涉及哪些有用的技术?它们作为GraphQL暴露出来,需要基于RESTful数据服务提供一个聚合编排层,需要提供WebSocket或利用GQL的订阅能力,应该由前端团队负责开发维护,并采用前端开发人员比较熟悉的技术栈。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可能倾向于认为BFF会增加延迟,因为多了额外的服务器跳转,但事实却相反。加重延迟的不是服务器跳转,而是数据包的传输距离。为了方便演示,请想象一下以下这种情况。假设一个网页调用了一个API,这个API平均每次返回10个数据项,而每个数据项需要调用另外三个API,这样才能获得渲染页面所需的数据。一位旧金山的用户从亚马逊us-east-1区域的服务器加载页面,每个请求来回需要传输5600公里。因为总共有31个请求,所以数据需要传输173600公里,这个距离可以绕地球7圈。如果你把这31个请求放在一个BFF里,并且这个BFF与服务器位于同一个数据中心里,那么你总共需要32个调用,但数据只需要传输5600公里,是不使用BFF传输距离的3%。Web浏览器可以通过并行的方式调用API,但相比后端服务,在连接方面具有更强的约束。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一种边缘服务叫作API网关,用于认证、授权、速率限定、单点登录和访问权限管理。如果有必要,它也可以将查询请求路由给GQL BFF,或者直接将请求路由给RESTful服务。对于不同类型的客户端应用程序,需要使用不同的BFF,但你只需要一个API网关就可以满足各种类型的客户端。API网关有时候也作为第三方的调用代理,让它们可以访问防火墙背后的数据服务。API网关是一种通用服务,可以由后端开发人员负责开发("},{"type":"link","attrs":{"href":"https:\/\/openresty.org\/","title":null,"type":null},"content":[{"type":"text","text":"OpenResty"}]},{"type":"text","text":"就是一种比较流行的方案),也可以是第三方提供的产品(比如"},{"type":"link","attrs":{"href":"https:\/\/konghq.com\/","title":null,"type":null},"content":[{"type":"text","text":"Kong"}]},{"type":"text","text":"),或者是由公有云供应商提供的PaaS服务。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/6e\/6e1680de108562f950812cdf9695f388.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现代Web应用程序几乎都是SPA(单页应用),而在以前,用户用Web浏览器加载HTML页面,这些页面可能是由服务器端的Web应用程序生成的。当用户单击页面上的一个链接,浏览器会渲染一个全新的HTML页面,这个页面也是由服务器端的Web应用程序生成的。在使用SPA时,用户用Web浏览器加载一个Web页面,这个页面只包含最基本的HTML元素,同时也会下载很多JavaScript和CSS文件。API调用是通过执行JavaScript代码来完成的,然后生成很多DOM元素,浏览器再用这些DOM来渲染GUI。当用户单击一个链接,页面上的JavaScript会销毁旧的DOM元素,并生成一些新的DOM元素。页面看起来发生了变化,但浏览器并没有加载全新的页面。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现如今,大多数前端开发人员使用TypeScript(具备类型检查特性的JavaScript变种)编程,以及Angular或React等框架。TypeScript被转译为JavaScript和CSS,这个过程成为项目构建的一部分。在进行本地开发时,开发人员将Node.js作为JavaScript和CSS文件的服务器,也用它将请求路由给目标API。但如果不是在本地开发,我建议使用Nginx。这样可以将配置了同源策略的文件与应用程序代码放在一起。构建出来的Docker镜像包含应用程序编译后的文件以及与CORS或缓存控制问题相关的配置文件。如果你采用了这种方式,可能需要调整CDN的配置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现如今大多数移动应用程序都是运行在iOS或安卓系统上。这些操作系统都有各自的技术要求,这里就不赘述了。你可以选择为不同的操作系统单独开发应用程序,也可以使用Ionic或React Native框架来开发同一套应用程序,然后为不同的操作系统分别生成各自的二进制包。你可以为分别为iOS和安卓开发单独的BFF,也可以简单一点,开发一个移动BFF来满足两个平台。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"结论"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你不必纠结于是选择REST还是GraphQL。REST更适合面向平台的数据服务,GraphQL更适合面向GUI的边缘服务。如果你的数据服务和边缘服务位于不同的层,那么完全可以同时保留REST和GQL,把二者的好处尽收囊中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"理清不同类型组件之间的关注点分离,有助于降低意外复杂性。有时候,你会为了达成短期目标而模糊了这些关注点。架构师需要努力满足各方的需求。为了满足紧急需求,考虑应用一些短期的解决方案,允许暂时模糊关注点边界,提高意外复杂性。但后续需要马上跟进(需要各方的参与),进行长期必要的重构工作,让系统重新具备清晰的关注点分离,并从整体上降低意外复杂性。这就是技术负债给我们带来的教训。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果可能的话,降低跨组件的耦合性。紧密耦合的组件应该由相同的团队负责开发维护,并采用相似或互补的开发技术。你不需要为此大幅改变团队的结构。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"作者简介:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Glenn Engstrand"},{"type":"text","text":"是Rally Health的软件架构师。他的工作重点是与工程师合作,交付符合12 Factor标准的可扩展服务器端应用架构。在2017年和2018年的Adobe内部广告云开发者大会以及2012年在波士顿举行的Lucene Revolution大会上,Glenn进行了突破性的演讲。他擅长将单片应用分解为微服务,并与实时通信基础设施进行深度集成。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"原文链接"},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.infoq.com\/articles\/consistency-coupling-complexity\/","title":null,"type":null},"content":[{"type":"text","text":"Consistency, Coupling, and Complexity at the Edge"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章