前言
本文非常长,阅读需要勇气。
作者尝试在移动端总结出一套面向页面的架构设计,暂定命名为POA(page-oriented architecture),因为核心的关注点在于page,阅读本文更多的是了解移动端架构的方式方法。
另外,本文主要是方法论层面的阐述,具体案例因为每一种编程语言的不一样实现会有所不同,所以文中代码均为伪代码。作者已经部分实现这套方案,但具体的实现并不重要,重要的是希望这套方法论能给读者带来一些收获。
定义
面向页面的架构的定义:
以页面为自治单元,定义页面的唯一标识,对页面进行模块化,解决页面的注册、页面栈的治理、页面间通信、页面的分层架构、页面的组件化等问题。
这里要先给出模块和组件的相关的定义,本文中所说的这些概念均基于以下的定义:
- 模块化,根据业务领域的划分进行页面的划分的过程。
- 模块,模块化的产物,模块尽量保持自治;
- 页面,页面的构成基本上依赖于产品的设计,不会像模块化或者组件化一样存在划分的灵活性,页面由组件构成,可以且必须保持自治;
- 组件化,根据业务上的通用程度对页面布局进行划分的过程。
- 组件,组件化的产物,组件不强调自治,但能自治当然更好,组件之间需要进行组合,最终由多个组件组合成一个页面,组件依赖于页面进行部署到App上。
以页面为自治单元
为什么要以页面为自治单元呢?
首先理解下自治单元,在 SOA 的场景下,一个 service 是一个自治单元,但是 service 的划分是无法标准化的,这增加了 SOA 架构设计的难度,直到出现 FAAS(function as a service)出现,function 是一个很好标准化的自治单元,极大降低了 SOA 架构设计的不确定性。
那么以页面为自治单元有以下一些原因:
- 页面是天然可标准化的自治单元,模块和组件的划分都存在较多不确定性;
- 如果以模块为自治单元,还要徒增模块间通信的复杂性,且模块划分不合理可能导致模块间的通信过于复杂;
- 如果以组件为自治单元,不只是组件的划分的不确定性,组件间的依赖如何解耦也是另外一个难题了;
- 页面的路由是必不可少的工作,页面间传参可以认为是页面路由的必备功能,对页面间传参的能力进行扩展,就可以实现页面间的通信;
- 页面可路由的一个前提是页面具有唯一标识(下面会讲到),而页面间通信刚好可以服用这个唯一标识,无序增加更多复杂性;
- 移动端App天然是页面的集合,以页面为自治单元可以极大的增强页面的可复用性,当然这不是目的,仅仅是一个副作用。
下面附上为POA设计的模块、页面、组件的物理架构图模板,物理架构是树形结构的,从模块到页面,从页面到组件,都是如此。
以页面为自治单元是本文的 POA 架构设计的基石。
页面的唯一标识
在前端领域,一个页面必然对应一个 URL,但是在移动端领域,这个标准不存在的,虽然 iOS 和 Android 系统自身会提供一些 URL 来调用系统页面的能力,但更多时候我们需要自己写一个 UIViewController或者Activity,然后直接通过依赖其类型来打开页面,这就造成了页面间更多的依赖性。
为了解耦页面间的依赖,直接采纳前端的方案,引入 URL 作为页面的唯一标识。通过这个唯一标识,我们可以打开、关闭页面,也可以跟页面进行通信。
需要注意的是,这个唯一标识只是在当前系统上下文内是唯一的,跨系统的情形不在本文的套路范畴之内,或者你可以认为我们主要讨论的是 内链,跨系统的页面 URL 是 外链,内链默认都是可信的,外链才需要关注安全性。外链可以通过转发内链来打开页面。
对 URL 的具体定义可以根据页面在树上的路径来,类似于如下的规则:
- /[module]/[page]/
- /[module]/[submodule]/[page]
当页面更复杂的时候,可能需要增加一个 feature 路径
- /[module]/[feature]/[page]/
- /[module]/[submodule]/[feature]/[page]
页面的模块化
页面的模块化,我们需要决定哪些页面应该分在哪个模块下面,前面也提到,这是一个存在不确定性的过程。
如何进行模块化
从业务的角度,我们很多时候需要通过面向对象的思想来分析,抽象名词,选出合适的名词作为关键实体,关键实体可能就是我们需要划分出来的模块。再往深一点看,我们需要对业务进行建模,这里不展开,我了解也比较肤浅。
模块划分之后,有一个办法可以用来验证模块划分的合理性。首先模块是由页面组成的,当我们抛弃模块之间臆想出来的依赖之后,剩下的就是这些实实在在物理层面的页面依赖,根据下面的方式来验证:
- 如果两个模块之间存在较多的页面依赖,那么这两个模块的页面划分可能是存在问题的;
- 如果一个模块内部存在两组以上基本不依赖的页面,那这个模块可能需要继续进行拆分。
模块化应遵循的原则
模块的划分,在物理层面应该是可以独立发布的包,所以同样适用于以下设计原则,wiki:
- 重用发布等价原则(REP)
- 共同封闭原则(CRP)
- 共同重用原则(CCP)
- 无环依赖原则(ADP)
- 稳定依赖原则(SDP)
- 稳定抽象原则(SAP)
前三个原则,较简单,不做阐述,后三个原则在后续章节会有更详细的说明。
模块的模块化编程
模块化编程的具体含义可以参考 wiki。
从我们对页面的定义看,一个页面本身就可以是一个最小的模块,页面座位自治单元,完全可以遵循模块化编程的规范,那么模块自然就很好遵循模块化编程的规范。模块的接口即是所有模块包含的页面。最小的模块可以只包含一个页面。遵循模块化编程可以让模块具有以下优点:
- 易设计,模块经过划分之后,使得模块本身的设计变得更为简单,因为需要关注的范围变小了,需要解决的问题变得简单了;
- 易实现,模块化适合团队开发,实现模块的成员不需要了解整个系统的全貌,人员的更替也不会带来很高的成本;
- 易测试,模块可以独立开发,也可以独立测试,到集成测试阶段才需要对整个系统进行测试;
- 易扩展,增加新功能只需要增加新模块或子模块,偶尔对现有模块的重新划分(就是对模块进行重构),在我们的场景下面,页面本身已经是自治单元