为什么我们放弃传统的iOS网络

在开始讨论之前,我将先声明免责声明:这不是您的典型的I-a-a-fant-new-thing科技博客,具有非典型,晦涩的图案。 不,这是一篇有关采用常见的基本范式并将其应用于我们以前可能从未想到过的地方的文章,或者更准确地说,是在我们忘记了它们与之相关的地方。

软件产品往往会经历总结能力成熟度模型的相同阶段,转化为软件开发。 我们通常会坚持不懈地进行尝试并进行创新。 这是因为我们不想浪费时间和金钱来为尚未得到的答案过度设计解决方案。 我们希望将精力集中在发明新事物上,因此我们会避免为重新发明轮子做任何努力。 在我开发和制造的所有移动产品中,其中一个轮子始终存在: 网络。

在MINDBODY,我们已经维护和扩展了许多不同的移动产品超过四年。 同样,大多数软件产品具有相同的根源,我们的移动应用程序也不例外。 根据年龄的不同,每种产品在某种程度上都很大程度上依赖于许多人认为是事实上的开源网络库:

  • AF网络
  • Alamofire
  • MKNetworkKit

这些功能强大且受社区支持的第三方可解决任何Cocoa开发人员的大部分联网问题-Cocoa HTTP库的全地形轮胎。 但是,我们是一家企业,以此类推完整的圆圈,不能在Peterbilt上打一堆全地形轮胎并且期望行驶很远。

多年来,我们一直在不断改进这些常见要求:

  1. 将具有不同范例和传输格式的多个API整合到一个网络会话中的能力。
  2. 在单个会话中应用不同级别的授权和其他一次性装饰的能力。
  3. 具有满足以上两个要求的具有不同主机/客户端配置的多个会话的能力。

最重要的是,我们渴望拥有SOLID基础。

建立一个全新的网络层听起来很昂贵,冒险且彻头彻尾,这在很大程度上缩小了故障间隔。 最终,这意味着我们需要从以下两个问题的明确答案入手: 我们如何设计新车轮,以及如何将东西砸在行驶的汽车上?

设计车轮

曾经有人告诉我,“只有在第三次失败之后,新架构才能成功。”作为坚定地支持这一说法的人,我将转变话题,并略谈一下Node.js。

为什么选择Node.js?

不,我不会开始赞扬JavaScript及其动态的,松散类型的,嵌套回调的荣耀。 Node.js如此流行的原因是容易和简单,通常要付出代价。 但是在某些情况下,语言和平台为出色的模式提供了基础。 在我们的案例中,我们决定招待Express.js及其中间件模式的实现。

没有花哨的全局请求管理器到处都是路由规则,序列化模式,全局请求标头,过于简化的嵌入式授权机制,缓存策略等; 没有请求操作会承担太多责任,无法尝试使摆锤向模块化方向摆动太远; 无需花三个小时阅读有关所有工作原理的文档。

网络管道架构

当简化为体系结构时,我们发现针对任何给定请求有四个主要参与者:

  • 管道
  • 中间件的集合
  • 请求
  • 响应

在我们的Swift实现中, 会话请求管道同义 管道中显示的任何更改或全局行为将影响该会话内的所有请求。 会话客户端使用提供的URLSessionConfiguration管理单个管道,并且它直接接受URLRequests作为输入。 这意味着,网络架构是Apple网络库之上非常薄的一层 。 客户端只是简单地通过串行队列来收集请求,通过提供的中间件链来处理它们(每个请求可以不同),最后将它们传输到URLSession。 这满足了我们的第一个要求。

中间件范式

中间件组件具有基于具体实现的职责范围,但是它们都可以做三件事:

  1. 修改请求
  2. 冻结/清空输出管道
  3. 用错误使响应短路

中间件被定义为一个简单的协议,并且可以从任何地方定义和注入自定义中间件。 为了对该系统进行测试,我们需要证明中间件不仅仅是简单的过滤处理;还可以证明中间件的功能。 它需要支持整个子系统

为了证明这一点,我们完全在网络库的外部构建并注入了第一套中间件:一个完全实现OAuth2授权协议(RFC 6749和RFC 6750)的系统。 这满足了我们的第二个要求。

回顾一下: 会话 == 网络管道

我们可以有多个并行运行的网络管道。 另外,我们可以在本地(即每个请求)或全局(每个会话)注入中间件。 在我们的案例中,这意味着可以为给定会话中的每个单独请求完全创建,调整或省略授权中间件。 这满足了我们的最后一个要求。

因此,我们已经设计了车轮,并且在这一点上,我们已经对其进行了战斗测试。 最后一个站立的问题是如何将吸盘拍打到行驶的汽车上。

如何注入依赖注入(什么?)

您没看错,依赖注入注入(初始?)。 我们已经从谈论如何重建网络层正式转变为如何完全替代已有四年历史的网络层。 提示汉斯·齐默尔。

事实证明,这并不像替换依赖项层那么容易。 考虑到这通常是您在任何与网络相关的应用程序中构建的第一部分,并且还考虑到您不想在早期阶段浪费时间进行过度架构,因此该层在我们的大多数应用程序中几乎都是不可分割的。 根据这些事实,我提出了一个两阶段的方法:

  1. 止血
  2. 依赖注入

停止流血

第一阶段的目标:允许新旧网络代码共存。 实现此目标将使开发人员可以完全在新的网络管道上构建以用于将来的开发,而不会负面影响针对传统网络层实现的现有API调用。

从这个角度来看,我们看到最初计划的工作在天文上正在减少。 在三个不同的移动应用程序的三个不同的网络层中,事实证明,在每个不同的实现中,只有一个问题要解决,那就是令牌管理。

依赖注入连接

好吧,我将以细腻的名字停下来。 下一个也是最终的目标是与旧的依赖关系完全断开联系。 最终从Podfile中删除AFNetworking是任何企业开发人员的梦想。

当然,在大多数涉及移动开发的情况下,我们在谈论时间和金钱。 为了使迁移切实可行,第二阶段在成本和质量之间走了一条路。 我们没有尝试重建整个传统的传输层以指向新的体系结构,而是采用了更为实际的方法。

首先,我们首先准确地确定了应用程序与传输层中给定依赖关系之间的接触点-“使用的接口”。我发现提取使用的接口的最快方法是从适配器开始,例如:

清除所有必需的签名后,我们回到制图板上并回答以下问题:

  • 我们如何调整所需的传统方法以将其引入新架构?
  • 我们将如何在传统网络层中展示所有相同的必需行为?

对于在完全不同的网络堆栈上运行的三个大型iOS应用程序,这些问题的答案最终变得可行。 注入适配器使我们可以通过单个依赖项交换来交换网络层。

就是这样-显然,在沿高速公路收费时,可以卸下那些磨损的全地形轮胎。 即使在快节奏的企业世界中,只要将它们分解成小块,也完全有可能解决这些问题。

开源的

这个框架是公开的! 在Github上查看导管:https://github.com/mindbody/Conduit