移动端工程架构与后端工程架构的思想摩擦之旅(1)

此文已由作者黎星授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验


记资源投放后端工程的架构调整与优化 


架构思考


一直以来对软件工程架构有着极大的兴趣,无论是之前负责的移动端Android工程,亦或是现在转到后端开发后维护的资源投放工程。可以说一个团队中并非每个开发都能够深入掌握架构知识,但需要每个人能够拥有软件架构的意识。架构是对工程整体结构与组件的抽象描述,是软件工程的基础骨架。架构在工程层面不分领域,且思想是通用的。引用维基百科对于软件架构的定义^1:


软件体系结构是构建计算机软件实践的基础。与建筑师设定建筑项目的设计原则和目标,作为绘图员画图的基础一样,软件架构师或者系统架构师陈述软件架构以作为满足不同客户需求的实际系统设计方案的基础。从和目的、主题、材料和结构的联系上来说,软件架构可以和建筑物的架构相比拟。一个软件架构师需要有广泛的软件理论知识和相应的经验来实施和管理软件产品的高级设计。软件架构师定义和设计软件的模块化,模块之间的交互,用户界面风格,对外接口方法,创新的设计特性,以及高层事物的对象操作、逻辑和流程。


架构的合理设计可以解决面对复杂系统时可能面临的很多问题,例如:

  • 业务边界与模块职责划分问题

  • 代码权限控制问题(数据库不应直接被业务方调用)

  • 代码重复,逻辑分支多,坏味道多的问题

  • 由于考虑不周,可能存在隐藏bug

  • 修改一个逻辑需要修改N个地方代码逻辑

从实际的实践来看,的确如此。以前在移动端做的架构设计流程,在后端重新得到了实践。


移动端架构思考


尚未接触到强大的Spring容器之前,我一直探索着在移动端有一种能够在编译期暴露服务声明,运行时自动注入实现类的做法;接触到Spring以后,得以理解这其实就是IoC容器的概念。Android的组件化思想,以及网上发布的各类组件化的技术文章,给了我很多值得借鉴的思路。客户端的代码一般是以module来组织的。一个module,既可以配置成为一个独立发布的库,也可以编译成一个单独的apk。组件化的概念正是利用了module这一特点,将一个大工程中的业务拆分成一个个module,各个module间的业务相对独立,组件间通过各自暴露的业务接口实现通信。基于此思想的移动端架构模型可以使用下图来表示。

20181206135052224b07c1-f315-47dd-9e6a-0edeaf8d7e12.png


点击查看原图

该架构模型由5个部分组成,分别是Toolkit/ToolkitSDK module、基础组件库/基础组件库module、基础服务接口/业务服务接口module、服务调度中心module以及业务module。

  • Toolkit/Toolkit SDK

Toolkit是工具类及与工具类相关的SDK的集合。工具类属于工程架构里最基础的模块,提供了通用的方法与工具类服务(工具类服务是指可以被抽象成一个独立的与业务无关的基础服务,如缓存、数据库操作等)。工具类通常作为最底层的module,被其他所有模块引用。

  • 基础组件库/基础组件SDK

基础组件库是基础组件及相关SDK的集合。基础组件库提供与业务相关的基础组件,是构建一个移动端应用所需要的通用组件的集合。它与工具类的区别在于基础组件库可能会包含少量业务逻辑代码,是无法拆分给其他应用使用的;另一方面,基础组件库是基础服务接口的实现,是不对业务层暴露的,避免了业务层与基础SDK打交道,有利于整体替换底层基础框架的实现(例如Volley替换为OkHttp、Fresco替换为Glide)。

  • 基础服务接口/业务服务接口

基础服务接口声明了一组通用的基础服务,业务层通过基础服务接口获取基础服务,如网络请求、图片加载等。业务服务接口声明了一组该模块提供给其他模块的服务,业务之间的通信也是通过服务接口来完成的。例如首页模块需要获取购物车的商品数量,首先通过服务调度中心获取购物车的服务接口,再通过服务接口调用购物车获取商品数量的接口方法即可。

  • 服务调度中心

服务调度中心,是一个接口收集与管理的容器。服务调度中心将所有基础服务接口与业务接口收集起来,通过一定的方式与它们的实现类进行绑定。所有的业务都需要通过服务调度中心才能够获取到服务。服务的注册与发现和Spring容器的IoC思想是类似的。

  • 业务层

业务层是每个业务的具体实现的集合。业务层的业务之间是没有直接引用关系的,业务层提供了业务服务接口中暴露的服务的具体实现。业务之间的通信需要通过服务调度中心获取其他业务的服务接口。


移动端架构小结

通过接口服务架构模型,模块之间是高度解耦的。业务负责人唯一需要维护的公共部分便是这个模块在业务服务接口中暴露的服务。对于业务服务的接口功能增改变得非常方便,业务实现的逻辑更改、代码优化等,只要不改变服务接口的签名,就不需要其他业务方改动任何代码即可完成,由此团队的开发效率是非常高的。


后端架构思考


对于后端工程来说,架构的设计与实现必定是与工程的业务难度及复杂程度相关的,如果只是很简单的业务模型,就没有必要弄得太过复杂,避免得不偿失。本人只接触了几个月后端知识,对于后端的架构体系与演进过程处于不断地学习和探索中。投放系统是我接触到的第一个完整的后端工程,其中Web工程采用传统的MVC架构^2,对我具有很大地学习和借鉴意义,项目架构如下图所示。

20181206135101f1c38e12-2589-40e3-a527-49aec9ba96dc.png


点击查看原图

该架构纵向划分成展示层、控制层、服务层、对象关系映射层和数据服务层5个部分,层级间通过AOP的方式插入了业务监控、日志、权限控制、统计分析等功能。

  • 展示层(View)

展示层是系统与用户打交道的地方,提供与用户交互的界面。对于用户而言,只有展示层是可见的、可操作的。展示层对于某些工程来说不是必须的,例如提供纯后台服务的工程。

  • 控制层(Controller)

主要负责与Model和View打交道,但同时又保持其相对独立。Controller决定使用哪些Model,对Model执行什么操作,为视图准备哪些数据,是MVC中沟通的桥梁。在Controller层提供了http服务供展示层调用。在依赖管理中,控制层需要依赖服务层提供服务。

  • 服务层(Service/Facade)

服务层是业务逻辑实现的地方,上层需要使用的功能都在服务层来实现具体的业务逻辑。服务层就是将底层的数据通过一定的条件和方式进行数据组装并提供给上层调用。服务层可以拆分为业务接口和业务实现,业务实现可以对外部隐藏。在投放工程中,控制层既依赖了业务接口,又依赖了业务实现。后面的改造我们可以看到,编译期红色线依赖是完全没有必要的。服务层需要依赖数据关系映射层与持久层的数据打交道。

  • 对象关系映射层(ORM)

对象关系映射层的作用是在持久层和业务实体对象之间作一层数据实体的映射,这样在具体操作业务对象时,只需简单的操作对象的属性和方法,不需要去和复杂的SQL语句打交道。ORM使得业务不需要关心底层数据库的任何细节,包括使用的数据库类型、数据库连接与释放细节等。对象关系映射层只依赖数据服务层提供服务。

  • 数据服务层(Data Server)

数据服务就是提供数据源的地方。数据服务可以提供持久化数据及缓存数据。持久,即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的数据存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。而缓存是将信息(数据或页面)放在内存中以避免频繁的数据库存储或执行整个页面的生命周期,直到缓存的信息过期或依赖变更才再次从数据库中读取数据或重新执行页面的生命周期。数据服务层是数据源头,处于架构的最底层。


后端架构小结

后端工程,更加注重层级的概念,每一层的职责非常明确。展示层负责与用户进行页面交互,控制层合并业务数据并控制View的展示,服务层则是实现业务逻辑的聚集地,对象关系映射层在业务层和数据服务层之间建立通道,而数据服务层则提供数据。总体而言,投放工程的MVC架构给我的感觉是比移动端架构复杂,层级多,职责分工明确,带来的问题是层级间的交互也比较麻烦。另外服务层里承载了几乎所有的业务逻辑,层级偏重,如果没有好好地梳理业务逻辑划清边界,很容易把服务层搞成一锅粥。清晰的模块职责划分,可以帮助服务层更好地为控制层服务。


架构思想的摩擦


可以看到,客户端与后端有着非常相似的架构模型。

  • 从代码组织的角度:以module作为层级代码组织的基本工具,分为工具库、基础组件库(中间件)、服务接口/API、服务层/业务层、视图层等。module间的依赖关系几乎是一样的。

  • 从业务模型的角度:后端工程分为交易组、商品组、售后组、客服组等,对应移动端的交易链路浮层、商品详情页、售后详情页、帮助与客服页等,每个业务是由不同的组负责的,业务之间通过约定的接口相互提供服务,各种各样的业务模型聚合成了整个系统。

  • 从功能服务的角度:分为业务服务接口的暴露、业务服务实现的隔离、业务服务的查找与注册。

下面从功能服务的角度,详细说明本文在思想摩擦过程中想要表达的观点。


业务接口的作用


业务接口,可以认为是这组业务向外暴露其功能的一套标准。标准一旦形成并发布,就需要业务方持续维护这套标准,使得标准变得完善和稳定。同时标准可以更新升级,可以通过版本来实现,提供新的功能。业务接口一般具备以下特性:

  • 业务接口包含一组Java的接口集合以及与这些接口相关的POJO,通常打包成一个JAR/AAR包。

  • 业务接口只提供接口功能的定义,不包含任务业务逻辑。

  • 业务接口可以进行版本管理,一旦版本发布,则该版本的接口不再可变。业务需要新增功能时,只需要在原有业务接口的基础上,增加新的功能接口或方法,同时升级业务接口版本号并发布。

其他业务方需要使用该业务的功能,只需要引入该业务的JAR/AAR包,通过服务调度中心获取该服务接口即可。


业务逻辑的存放


业务接口仅提供了功能的定义,不包含任何业务逻辑。那么,业务逻辑(即接口的实现类)放哪里呢?不管是移动端架构还是后端架构,在工程领域,业务逻辑在任何时候都不应该对业务的使用方暴露。这样做有两个好处:

  • 业务方只关心功能,不关心功能实现的过程。隐藏业务的实现逻辑可以降低业务方使用该功能的成本及复杂度。

  • 业务功能的后端逻辑改动及必要的技术优化、性能优化,只要不更改接口签名,则不会影响当前的业务方使用。

一般情况下,业务的逻辑实现会放在单独的业务模块中,该业务模块仅限工程内部引用。后端传统的MVC工程架构把业务的实现逻辑放在了Service/Facade层,层级之间的类不相互引用;而基于驱动领域的设计模型把业务的实现逻辑限定在了一个领域/子域里,领域之间通过界限上下文绑定。在移动端,时下较为热门的众多组件化方案,也是将一个独立的功能模块作为单独的module,module可以独立编译为apk,也可以通过aar的方式集成到主Application中。


业务逻辑的隔离


业务的使用方包括工程内部的上层业务和外部服务,业务接口与业务逻辑有必要进行代码级别的隔离,这样才能避免上层业务引用到业务逻辑的代码。通常,工程的每个模块负责不同的功能,模块之间的引用关系通过依赖管理工具(如Maven或Gradle)来配置。我们可以巧用依赖管理工具的runtime compile机制来实现运行时依赖。即在编写对外接口的时候,不直接引用包含业务逻辑的module,等到编译的时候再把业务逻辑代码一起编译进来,然后在运行时通过一定的方式调用对应的业务逻辑。

  • Maven通过在dependencyManagement的依赖中加入runtime标签实现^3。

  • Gradle通过在dependencies将compile改为runtime实现^4。

下面的两张图简单介绍了后端工程和移动端工程基于业务逻辑隔离的工程架构思路。其中,虚线表示runtime compile依赖,实线表示正常的依赖关系。

20181206135112976bcb17-f32b-4964-bf9c-bb01b9101300.png


点击查看原图

工程的启动入口几乎不包含业务代码,只包含配置文件。Controller层是一组RestfulApi的集合,给前端和客户端提供http请求服务,业务接口/API是一组dubbo接口,给其他工程业务方提供RPC调用。Controller层在编码的时候只依赖Service/Facade接口,在编译期依赖Service和Facade接口的实现。这样设计还有一个好处是对DAO层的保护,DAO层只和Service层打交道,Controller以及对外提供的dubbo接口是引用不到的,更好地保护数据安全。

20181206135119781bc60a-e56d-46ab-b555-2863ef9910e7.png


点击查看原图

在移动端的架构中,单Application+多module已经成为主流。每个module负责一块独立的业务,如首页、订单、购物车等,核心模块也可以拆分为独立模块,如网络引擎、图片引擎。这些独立的模块可以抽离出BusinessService/CoreService服务接口,模块间的交互只需要通过Service接口通信即可,业务对于其他的p_CoreSDK/Business的逻辑实现是不可见的。


业务接口的注册


有了业务接口和业务实现,还需要一种在运行时把它们“粘合”起来的工具,这一过程可以称为业务接口的注册。当业务方访问业务接口时,这个工具需要帮助我们查找到对应的业务实现。控制反转(IoC)或依赖注入(DI)的思想给我们提供了解决办法。

在后端工程中,Spring是最为常用的IoC容器之一。Spring在运行时根据配置文件或注解动态生成对象,再由变量注解通过Java反射注入到对应的实例中。因此代码中只需要通过在全局变量声明相应的注解即可完成业务接口的注册。

移动端由于有限的硬件资源,更多地把CPU时间分配给了页面渲染,保证应用体验流畅,不太可能在应用启动的过程中大量通过反射生成实例对象,因此移动端并没有出现Spring框架。尽管如此,依赖注入的思想是通用的。通常移动端只需要保证在运行时能够获取到对应的业务实现,几乎没有在运行时动态改变业务实现的需求,聪明的工程师想到了把服务的注册提前到编译期进行,这一过程可以使用JDK提供的Annocation Processing Tool完成(例如Dagger2),也可以在编译生成Class字节码以后使用ASM操作字节码注册实现(如ARouter)。


 

免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐

更多网易技术、产品、运营经验分享请点击。


相关文章:
【推荐】 如何学习、了解Kubernetes?
【推荐】 分析自己遇到的Excel导出报NullpointException问题
【推荐】 当我们谈论计划时我们在谈论什么

转载于:https://www.cnblogs.com/zyfd/p/10077010.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/395803.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

logging记录日志

日志是一个系统的重要组成部分,用以记录用户操作、系统运行状态和错误信息。日志记录的好坏直接关系到系统出现问题时定位的速度。logging模块Python2.3版本开始成为Python标准库的一部分。 日志级别 在最简单的使用中,我们直接导入logging模块&#xff…

C#编程之接口

1.定义 接口是把公共方法和属性组合起来,以封装特定功能的一个集合。(一旦定义了接口,就可以在类中实现它。这样类就可以支持接口所指定的所有属性和成员) 注意1:接口不能单独存在。不能像实例化一个类那样实例化一个接…

supervisor守护进程

2019独角兽企业重金招聘Python工程师标准>>> supervisor 是一个client/server系统,把不是守护进程的进程变成守护进程,并监控和控制类 Unix 操作系统上的进程。 upervisor就是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台dae…

【学习笔记】深入理解js原型和闭包(11)——执行上下文栈

继续上文的内容。 执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境。当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个…

Java基础--访问权限控制符

今天我们来探讨一下访问权限控制符。 使用场景一:攻城狮A编写了ClassA,但是他不想所有的攻城狮都可以使用该类,应该怎么办? 使用场景二:攻城狮A编写了ClassA,里面有func1方法和func2方法,但是他…

Dubbo简单介绍及实例

1、概念 Dubbo是一个分布式服务框架,以及阿里巴巴内部的SOA服务化治理方案的核心框架。其功能主要包含:高性能NIO通讯及多协议集成。服务动态寻址与路由。软负载均衡与容错,依赖分析与降级等。 说通俗点,就是首先将程序组件化成一…

bzoj1116: [POI2008]CLO

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id1116 题目大意:Byteotia城市有n个 towns m条双向roads. 每条 road 连接 两个不同的 towns ,没有重复的road. 你要把其中一些road变成单向边使得:每个town都有且只有一个入度 题解&am…

java排序算法大全_各种排序算法的分析及java实现

排序一直以来都是让我很头疼的事,以前上《数据结构》打酱油去了,整个学期下来才勉强能写出个冒泡排序。由于要找工作了,也知道排序算法的重要性(据说是面试必问的知识点),所以又花了点时间重新研究了一下。排序大的分类可以分为两…

6/12 Sprint2 看板和燃尽图

转载于:https://www.cnblogs.com/queenjuan/p/5578551.html

转:PHP应用性能优化指南

程序员都喜欢最新的PHP 7,因为它使PHP成为执行最快的脚本语言之一(参考PHP 7 vs HHVM 比较)。但是保持最佳性能不仅需要快速执行代码,更需要我们知道影响性能的问题点,以及这些问题的解决方案。本文涵盖了保障PHP应用平…

java list集合增删改_Java中集合类list的增删改查

今天给大家带来的是Java中list类的使用,java.util 包提供了list类来对线性数据操作List接口是Collection接口的子接口,List有一个重要的实现类--ArrayList类,List中的元素是有序排列的而且可重复,所以被称为是序列List可以精确的控…

IIS6、IIS7和IIS8各版本的差别

一、写在前面 目前市面上所用的IIS版本估计都是>6.0的.所以我们主要以下面三个版本进行讲解 服务器版本IIS默认版本server20036.0server20087.0server20128.0二、IIS6的请求过程 由图可知,所有的请求会被服务器中的http.sys组件监听到,它会根据IIS中的 Metabase 查看基于该 …

Android Studio 插件的使用

1、GsonFormat https://github.com/zzz40500/GsonFormat 2、Android SelectorChapek http://blog.csdn.net/weifei554287925/article/details/41727541

安卓Java虚拟机大小_虚拟机为安卓流畅度背锅,是因为关系数十万程序员饭碗?...

导读:虚拟机相当于应用程序在不同运行环境中的翻译。说起谷歌安卓系统的“虚拟机”,很多人爱拿它和苹果iOS做比较,结果,安卓的很多短腿儿都让虚拟机背了锅,比如安卓手机运存容量是iPhone的两到三倍,流畅度却…

AppCompatActivity实现全屏的问题

前言:我的 Activity 是继承 BaseActivity , 而 BaseActivity 继承 AppCompatActivity 。 BaseActivity 的继承 /*** 应用程序的基类**/ public class BaseActivity extends AppCompatActivity {}HomeActivity 的继承 public class HomeActivity extends BaseActivit…

Cinder 组件详解 - 每天5分钟玩转 OpenStack(47)

本节我们将详细讲解 Cinder 的各个子服务。 cinder-api cinder-api 是整个 Cinder 组件的门户,所有 cinder 的请求都首先由 nova-api 处理。cinder-api 向外界暴露若干 HTTP REST API 接口。在 keystone 中我们可以查询 cinder-api 的 endponits。 客户端可以将请…

RedHat Enterprise Linux 6 配置Xmanager ,实现图形界面连接

我们经常见到的几种最为常用的windows下远程管理Linux服务器的方法,基本上都是利用SecureCRT,或者是PUTTY等客户端工具通过ssh服务来实现Windows下管理Linux服务器的,这些客户端工具几乎不需要什么配置,使用简单,但是它们都无法启…

Mac下配置iterm2 支持rz sz命令

转自:http://blog.csdn.net/citywolf4/article/details/49071679 1.安装lrzsz,使用brew命令:brew install lrzsz如果找不到lrzsz,使用以下命令更新brew库:brew update2.下载zmoden脚本在https://github.com/mmastrac/iterm2-zmode…

java中session对象登录_JavaWeb中Session对象的学习笔记

一、Session简单介绍在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独…

微信小程序 没有找到 node_modules 目录

在学习小程序云开发的时候,遇到一个问题,使用npm i --production 和npm i vant-weapp -S --production之后,在微信开发者工具中并没有node_modules文件夹 但是在根目录下生成了一个package-lock.json文件。也就是下载的依赖都已经装好了&…