Atlas,移动开发周期的统一方法:网络层

作为开发人员,我们通常花费大量时间来编写,重写和重构现有代码。 对于我们中的某些人来说,它代表了通过干净代码的乌托邦进行的漫长的迭代过程。

许多开发人员没有抓住软件工程的要点之一:避免通过推广可重用代码来重新发明轮子。

向没有直接参与开发的项目经理或其他团队解释此过程可能经常引起争论: 时间就是金钱,您知道吗? 另一方面,知识的增长是公司的关键部分

至于日常生活中的许多其他内容,则是在这两部分中间的某个时刻。

什么是地图集

当今的大多数软件都与后端服务器对话,下载一些数据,然后将其他数据发送回给用户。

虽然网络层只是一部分,但它在每个应用程序中都起着关键作用:因此自然而然地,它是我们开始使用Atlas进行标准化的第一个应用程序。

Atlas的目标是为IQUII上的每个新客户提供一个共同的基础(框架):它将包括从网络层到持久性支持,推送通知和许多其他部分的所有共同功能。

这样的项目背后的好处很多:

  • 专注于真正重要的事情 :有了共同的成长基础,我们可以将重点放在项目的核心:UX,UI和功能。
  • 稳定性 :只需维护一个代码库,我们就可以着眼于更坚实和经过良好测试的代码。
  • 标准化开发周期 :这只是整个过程的一部分,但是有了一套通用的规则,我们可以更快,更紧密地开发:从一个项目切换到另一个项目更简单,更省力。

我们将其称为Atlas,因为Titan谴责永恒的天空:我们的框架将在IQUII及其iOS和Android平台上容纳整个移动生态系统。

在本文中,我们将介绍Atlas的关键部分之一:网络层。

建立网络层的“不要”部分

如果您从未创建过单例课程来举行网络通话,请举手。 这是我在过去几年中遇到很多时间的常见情况,实际上,这是创建网络类的最简单方法:应用程序的每个网络调用都存在的单个单例类。

不要误会我的意思; 单身并不是邪恶,但是这种方法自愿打破了单身责任原则。

不管我们认为什么是出色的代码,它始终需要一种简单的质量: 代码必须是可维护的 。 任何无法维护且不能相对轻松地适应不断变化的需求的代码,都是在等待变得难以管理,然后过时的代码。

在任何编写不佳的代码中,您总是可以找到一个承担多个责任的类

我们的单身人士了解有关连接的所有信息( 基本url,每个请求的路径 ),如何构建和执行每个请求,如何解析响应数据,最后还管理内部用户会话和某些用户的配置文件数据(噩梦!)。

与网络连接相关的所有内容都位于这个庞大的单例类中。

如果您需要进行依赖注入,这也很成问题,此外,出于测试目的,也不能轻易对其进行嘲笑。

正确的方式

Atlas的目标之一是充当我们称为Athena的后端服务的通用客户端框架。 Athena是使用NodeJS / MongoDB创建的现代后端应用程序,它公开了REST JSON服务。

像Atlas一样,Athena代表了IQUII上每个新项目的共同点:它提供了一组通用的结构和方法,可以针对特定项目进行扩展。

从这个角度来看,我们在网络层要实现的功能最少:

  • 用户持久性 :允许主机应用轻松登录/注销到系统中,同时保持会话活动(以及用户配置文件和设置)。 会话管理必须对主机应用程序透明,而配置文件和设置应由框架本身自动更新。
  • 网络操作的抽象层 :提供一个可扩展的点来扩展网络功能:从编写新的端点来管理超时,处理网络错误,连接不良以及将对象转换为请求(反之亦然)Atlas提供了一种标准且健壮的方法与后端通信。
  • Handle Server Autentication :在Athena中,凭据由JWT的访问/刷新令牌管理。 基本上,这意味着用户登录后,您的应用会收到两个令牌:访问令牌是一个短暂的代码,必须在每次后续调用中都发送给后端; 刷新令牌是安全保存在应用程序沙箱中的长期令牌,仅用于在新访问令牌请求过期后对其进行身份验证(如果您对此官方OAuth指南感兴趣,请说明此握手的详细信息)。

Atlas的目标是向主机应用隐藏这些任务背后的复杂性。

因此,我们的第一个挑战是选择正确的工具。

整个网络堆栈由以下工具支持:

  • Hydra:ES6 Promise / Future范例的Swift实现。 这是瑞士军刀,用于处理并发,重试,超时以及基本上是我们层中所有异步事务。
  • SwiftyJSON:即使使用Swift 4,我们很快就会迁移到Codable,该库提供了一种强大的方法来解析和生成JSON数据。
  • JWTDecode:用于管理JSON Web令牌。
  • SwiftyUserDefaults:用于以安全快速的方式管理首选项设置

建筑

下图代表了Atlas网络核心的体系结构:

服务配置

它充当应用程序网络配置的存储; Atlas可以从Info.plist自动实例化它; 或者,开发人员可以手动提供自己的实例。

为了使所有这些东西自动化,我们通常使用Info.plist并将每个Xcode的Build Configuration (通常为Testing-Debug-Production-Release)具有不同值的User Defined Setting变量设置为每个键的值。

通过此技巧,当我们从Xcode的“运行”菜单切换目标时,所有字段将自动在正确的环境下预编译到Info.plist中。

这里唯一缺少的密钥是APIKey ,但是显然不能将其存储在此处; 它会在运行时由应用通过代码提供。

服务

服务是网络层的核心。 通过传递ServiceConfig来创建此类的新实例,该实例表示服务器的网络访问点。 Atlas提供了一个联网点,但是开发人员可以根据需要创建多个实例。

服务接收Request实例,并在其配置上下文中对其进行评估。

我们可以使用Apple内置的NSURLSessionAlamofire之类的第三方库作为Service的基础 :实际上,这并不重要,范围是将所有这些内容与上面的层隔离开。

服务仅需要实现一个名为execute()乐趣:它必须返回带有响应的Promise 。 下面的实现显示了它如何与Alamofire一起工作:

要求与回应

请求封装有关请求的所有信息:

  • 端点组件路径(即auth_login
  • http方法(POST,PUT,GET…)
  • 超时间隔
  • 输入参数及其提供方式(JSON / XML到正文,GraphQL样式,编码的URL参数…)。
  • 预期的输出(原始数据,JSON解码或XML)

单个请求完全独立于服务。 在哪里执行:这是体系结构的关键点。

这是请求的示例:

您可能会想到,响应对象包含执行请求的结果:

  • 原始请求(用于调试目的)
  • HTTP响应(标题/代码)
  • 响应数据(原始,JSON或XML)
  • 指标数据(用于对与服务器端的通信进行基准测试:包括等待时间,开始/结束时间,通话持续时间)

它还提供了两个函数: toJSON()toString() ,它们可以解析原始数据并输出输出的全面版本。

响应是通过我们服务实现的执行功能自动创建的。

JSONOperation和DataOperation

Service,Request和Response之上我们还有另外两个对象: DataOperationJSONOperation

这两个类都是Operation协议的具体实现:我们可以将Operation视为网络层的更高抽象层:它封装了API调用的业务逻辑并将其保持在一个独立的结构中。

操作的一个重要角色是提供从服务器到应用程序业务模型的原始/已解析(JSON)响应的即用型转换。

通常,当IQUII的iOS开发人员需要为其应用程序创建新的终结点调用时,他将成为JSONOperation的子类。

JSONOperation提供了以下几种功能:

  • 开箱即用的数据转换 :原始数据会自动转换为主机应用程序的特定模型:开发人员可以创建诸如GetNewArticles类的操作,该操作需要一些参数并获取“ Articles”对象的列表。 无需解析,无需特别注意异步请求 。 所有的复杂性都是隐藏的但可调试的。
  • 自动应用程序的错误管理: JSONOperation负责预先解析从服务器接收的输出并查找任何接收到的错误; 所有后端公共错误都将自动处理,而无需开发人员采取任何特殊措施
  • 封装请求的逻辑 :一个操作包含要执行的请求,该请求还包括请求的所有参数。 单点调试使代码更简单且可调试

正如我们所说,业务逻辑的错误由Operation类自动管理。 雅典娜的典型回应如下:

Atlas的目标是预先解析此结果,对收到的任何可能的错误进行评估,然后将可理解的错误返回给主机应用程序(通常是Swift的Error消息,开发人员可以根据特定的应用程序限制扩展该消息)。

我们从雅典娜的后端发现了几种错误。 特别要提到的是MaintenanceModeObsoleteVersion

第一个告诉Atlas框架后端服务暂时不可用; 第二个通知主机应用程序有关AppStore上可用的应用程序的新的强制性更新。

主机应用程序可以订阅这两个事件,以提供开箱即用的响应。

一个简单的登录请求

Atlas提供了一组Operation子类,这些子类用于涵盖最小客户端的所有基本功能。

其中之一是LoginOp ; 它以电子邮件和密码作为输入参数,并在成功的情况下返回UserModelProtocol

此操作的结果保存在Atlas提供的名为Application的单例对象中: Application是与所有Atlas服务进行交互的中心点。

在启动时,应用程序可以安全地查询此对象以检索任何特定于会话的数据(如登录的用户)。

UserModelProtocol是一个开放协议,其中包含用户的所有属性。 开发人员可以使用特定后端应用程序的一些额外属性来扩展它(即,体育应用程序可能包含favourite_club属性)。

Atlas将注意保存并从其持久层中检索它以及其他属性。

操作显示如下:

此类的一个有趣部分是onGenerateOutputModel函数:它充当入口点,允许开发人员将JSON原始数据转换(有效时手动转换为有效的应用程序模型)。

在这种情况下,此函数是Atlas框架的一部分,并从原始json数据创建指定开发人员的User类的模型。

开发人员还可以通过将自定义验证器附加到onValidateJSONResponseonValidateJSONResponse Atlas提供的输出的验证。

AtlasOperation类的另一个有趣的特性tokenRefreshAllowed ; 当设置为false它允许禁用JWT令牌验证; 如果为true Atlas会自动刷新与用户关联的JWT令牌并重试开发人员请求的调用(在这种情况下,登录不需要刷新令牌,因为此时我们没有任何令牌)。

准备好Service实例和LoginOperation之后,我们可以通过添加我们的登录方法来扩展Service类:

此函数是在我们现有Service实例中执行LoginOp的简单快捷方式。

有了Atlas,我们的开发人员只需要编写_ 一行代码 _:

只需一行代码:内置的异步支持,自动错误处理,自动数据到模型转换。 就像魔术!

正如我们所说的,Atlas的目标不只是网络层。

Atlas公开的Application对象包括自动联网配置,会话处理和基本移动应用程序的所有基本要求。

结论

正如我们从快速进入Atlas所看到的那样,为每个应用程序创建一个共同点代表了向前迈出重要一步 ,尤其是考虑到我们正在开发的应用程序数量和正在成长的团队。

在成熟的环境(如移动设备)中,需要一种标准的方法来应对开发周期,这是必不可少的,因此,如果不希望将来遇到相关的问题,就不会碰巧遇到这种情况。

在每个新项目中,我们都有机会使Atlas更加强大,拥有更多功能,使其成为每个应用程序的坚实基础。

通过以下文章,我们将有机会深入了解Atlas的其他部分。