我们的iOS模块化故事


几年前…作为荷兰几十年来著名的零售商,wehkamp已成功地从目录公司过渡到100%电子零售商。 现在是时候在wehkamp探索应用程序世界了。

我们从一个SCRUM团队开始,这个团队有1位Android开发人员,1位iOS开发人员,1位测试人员,一些后退者,Scrum Master和产品所有者。 我们填补了积压的订单,并开始从头开始构建应用程序。 这些应用程序由运行在本地IIS服务器上的.NET API提供支持。 .NET环境已经运行了很多年,并且已经发展成为一个庞大的整体。

微服务和微站点

在随后的几年中,我们作为一个团队主要致力于通过添加功能,改进现有功能以及尽可能为我们的用户优化应用程序来扩展应用程序。 同时,后端.NET应用程序在可维护性,可伸缩性,可靠性等方面已达到极限,我们周围的团队正在努力摆脱这些问题,并转向微服务和微站点。 这是正确的解决方案,效果很好。

初期

在应用程序的早期,收入部分可以忽略不计,因此组织对应用程序团队的压力不大。 我们只是做了自己的事情,就有很多实验的可能性。 我真的不想发动一场(本机)应用程序与网络大战,但是过了一会儿,这些数字告诉人们,大量用户更倾向于使用应用程序。 转化率更高,订单价值更高,参与度更高。 因此,在组织内部,我们受到越来越多的关注,并且增长的需求正在上升。

目前…我们的团队有3个iOS开发人员和2个Android开发人员,我们仍在寻找更多。

历史重演

但是……历史在重复。 几年前,网络已经发展到了同一个方向。 一个大屁股巨石。

在一个人的团队中工作,不必过多地考虑可维护性,可伸缩性和可读性。 当然,我知道团队会成长的一天,所以我试图牢记这一点来实现应用程序的每个部分。 但是有时压力很大,您需要捷径。 避免这种情况是不可能的,但是我试图尽量减少这些捷径。

受其他创建微服务和微站点的团队以及这些准则的启发,我们决定重构我们的应用程序并进行模块化! 这是我们的故事……

我们的目标是从此出发:

对此:

但是,让我们先说说我们的出发点。 当然,这会影响向模块化方向迈进的步骤,因此更容易说……

依赖注入FTW

正如我已经提到的,在我刚开始的头几年里,我始终牢记有一天,我们应该成长,并尝试制定尽可能防止将来发生灾难的决策。

确实对我有帮助的一个原则是依赖注入。 我不会对此进行详细介绍,有很多可用的资源。 但是,想法是将实例化注入到类中,而不是在类本身中创建实例化。 这样,您就可以将责任分开,而不是紧缩责任,而松散耦合还是紧密耦合。 我将依赖注入与有意义的面向协议编程结合使用。 这种方法迫使我避免耦合根本不应该耦合的代码部分。

这与依赖注入结合在一起,产生了相当独立的代码部分。 每个功能都有其自己的情节提要板(或有时是多个情节提要,以防止情节提要,团队合并,冲突和地狱)以及与此功能相关的所有其他内容。 如您在图片中看到的,例如有“购物”和“收藏夹”功能。 “收藏夹”功能向我们显示了产品列表,您可以想象用户点击该列表中的产品时希望看到产品详细信息。 产品详细信息视图控制器位于“购物”功能中。 为此,我在“收藏夹”情节提要中添加了对“购物”情节提要的情节提要参考。 而且……我们的分离已经消失了!

第一步; Core和CoreUI与独立性

微型功能实现的重要部分是这些功能应能够独立运行。 有时这可能与DRY(请勿重复自己)原则冲突。 在某些情况下,您必须将代码从一种功能复制粘贴到另一种功能。 我过去经常重复使用尽可能多的代码。 例如; 想象一个特定类别的产品清单,例如“女装”。 这向我们显示了这样的产品列表:

这是一个包含单元格的collectionview,其中包含一个imageview和一些标签。

现在,假设我们要向我们的应用程序添加新的功能! 愿望清单! 这些设计向我们表明,它与“正常”产品列表中的设计基本相同。 所以我的第一个想法; 让我们重用它! 为此,我们创建一个类ProductListCollectionView。 我们必须定义一个数据,所以让我们创建一个协议ProductListCollectionViewDatasource 。 在现有产品列表上,我们有一个心形图标,可将产品添加到愿望清单,但是如果您在愿望清单中,我们不希望这样。 因此,我们使用方法ShouldShowHeartIcon添加协议ProductListCollectionViewDelegate

哇! 这就像一种魅力。 稍后,我们希望在某些冲刺中添加一个图标,以将产品添加到购物篮中。 这是一项A / B测试,因此我们暂时不希望在产品概述中使用它。 我们应该添加一个额外的委托方法shouldShowAddToCart 。 您可以想象,它将演变成一个难以维护的,丑陋的,如果被其他污染的巨人。 最后,您没有很多通用的UI。 我们应该开始将其创建为一个独立的元素而不共享任何东西。 有时候从一个功能复制粘贴到另一个功能并不难。

但是需要权衡。 如果您发现自己将代码的特定部分复制到所有功能中,则可能表明创建一些共享此代码的代码是有效的。 这就是模块“​​ core”和“ coreUI”发挥作用的地方。

这些是放置所有功能使用的代码的模块。 有时,很难确定某个特定的代码段应该驻留在“核心”中还是需要自己的模块。 我决定将这些东西放在核心:

  • 联网
  • 安全
  • AB测试
  • 分析工具
  • 组态
  • 实用程序
  • 基础课程扩展
  • 等等

UI元素的计数相同,您将在这里找到:

  • UI元素
  • Collectionview布局
  • 基本ViewController
  • 输入验证
  • 状态完整的ViewController
  • UIKit类的扩展
  • 等等

我自己的经验法则是,我们应该在这些模块中放置尽可能少的代码,时不时复制/粘贴并不算太糟糕……

特征

下一步是仅使用该特定功能的代码创建一个新模块。 我将在另一篇文章中逐步说明如何在Xcode中为您的iOS项目执行此操作。 (编辑;由于时间问题,我现在创建了一个示例应用程序,以后可能会再发表另一篇文章。来源:https://github.com/martijnschoemaker/modularizesampleapp)。 目前,这仅与概念有关。 在我们的情况下,当您打开要素项目时,您将看到以下组:

  • UI ; 与所有与UI相关的文件; ViewController,视图,情节提要和NIB。
  • AB测试 ; 在这里,我们定义了用于此功能的AB测试。
  • 处理者 ; 这是我们可以定义的处理程序的实现。 例如,可以通过这种方式为ApplicationContinueActivity委托注册一个处理程序,这样,模块可以自行决定是否应处理该事件以及该如何处理。
  • 模型 ; 对于所有模型类别
  • 网络运作 ; 我们将网络请求定义为HttpOperations。 HttpOperation是一个协议,对于特定的请求,我们实现HttpOperation ,在这里您可以找到这些操作。

换句话说,所有与功能相关的东西都应该在功能模块中,而不是更多或更少。

礼节

还记得我们想要在点击愿望清单中的产品时显示产品详细信息屏幕的示例吗? 过去,我们只是在一个项目中将要素分为不同的组。 因此,这使我们可以使用其他功能的代码。

我们在单独的模块中实现了这些功能,最后得到了多个彼此都不了解的模块。 但是,我们仍然希望能够在点击愿望清单中的产品时打开该产品详细信息屏幕。

但是……如何? 所有这些依赖魔术都是通过添加“中间人”模块来完成的。 依赖性。 该模块将处理功能之间的依赖关系。 功能彼此不引用,而仅引用该依赖模块。 依赖模块不应包含任何实现类。 这只是功能之间的粘合。

如果要在点击产品愿望清单时打开产品详细信息视图,则需要诸如ProductViewControllerProvider之类的东西

因此,我们可以做的是在依赖项中引入一个ProductViewControllerProvider类,该类可以为wishlist模块提供正确的viewcontroller以便对其进行显示。 但是通过这种方式,我们为依赖模块提供了太多有关实现的知识。 它不应该知道如何创建一个视图控制器来显示产品。 唯一知道这一点的是ViewController实际使用的Shopping功能。

所以我们在这里需要一些抽象…

仅协议

从一开始,我们就可以从依赖注入和面向协议的方法中受益。 协议就是胶水,而胶水存在依赖模块。 因此,依赖项应包含协议,而不是实现。

对于我们的愿望清单示例中的产品详细信息,结果如下所示:

  1. 在“愿望清单”功能中,我们实现了愿望清单中的产品列表。
  2. 在依赖关系中,我们有一个协议ProductViewControllerProvider ,其方法为getProductViewController()-> UIViewController。
  3. 在购物功能中,我们具有ProductViewControllerProvider的实现,该实现知道如何创建视图控制器并返回它。
  4. 通过依赖注入,我们在购物模块中定义了对于协议ProductViewControllerProvider应当在购物模块中使用的实现。

现在怎么办?

从我们通过引入core,coreUI和依赖关系创建基本实现的那一刻起,我们决定对每个功能进行模块化。 每个Sprint可以选择一项或两项功能。 目前,我们已经完成了约80%的现有功能。 对于新功能,我们将创建一个新模块。

快乐模块化!

示例应用

我创建了带有一些模块的示例应用程序。 它没有太多功能,但是模块化和去污注入的思想应该很明确。

https://github.com/martijnschoemaker/modularizesampleapp


感谢您抽出宝贵的时间阅读这个故事! 随时留下您的意见并提出问题! 并分享您对模块化应用程序的体验。