题目
论软件架构风格软件体系结构风格是描述某一特定应用领域中系统组织方式的惯用模式。体系结构风格定义一个系统家族,即一个体系结构定义一个词汇表和一组约束。词汇表中包含一些构件和连接件类型,而这组约束指出系统是如何将这些构件和连接件组合起来的。体系结构风格反应了领域中众多系统所共有的结构和语义特性,并指导如何将各个模块和子系统有效地组织成一个完整的系统。
请围绕“论软件架构风格”论题,依次从以下三个方面进行论述。
- 概要叙述你参与分析和设计的软件系统开发项目以及你所担任的主要工作。
- 软件系统开发中常用的软件架构风格有哪些?详细阐述每种风格的具体含义。
- 详细说明你所参与分析和设计的软件系统是采用什么软件架构风格的,并分析采用该架构风格设计的原因。
摘要
近些年,新能源汽车蓬勃发展,2022 年 6 月我司决定开发一个针对新能源汽车一体化平台,其中包括媒体、社区、选车等功能。我担任该项目的技术负责人,负责系统方案定制及技术指导工作。本文描述了该系统的架构分析与设计工作,其中包括三个主要部分,应用系统,该系统采用 B/S 架构,前后端分离的服务端渲染模式,满足了推广、跨平台等需求,降低了开发成本,明确了职责分工。后端服务,该部分采用层次化架构,通过划分不同的层,实现了一种明确职责,耦合性低,方便测试的应用程序。应用通讯,该部分采用 REST 架构风格,一种约定大于配置的架构,有良好的扩展性,降低了学习与开发成本。该系统于 2023 年 10 月上线运行,运行效果良好。
正文
2022 年,新能源汽车开始蓬勃发展,国家发布了相关政策进行引导规划。此时,市面上并没有针对新能源汽车的应用平台,导致用户在搜索车型时,无法快速准确的找到对应的新能源汽车信息。为响应政府号召,满足用户需求,我司决定开发一个针对新能源汽车的一体化平台。
该项目于 2022 年 6 月开始,团队成员共 17 人,我在该项目中担任技术负责人,主要负责系统方案定制及技术指导工作。该项目主要包括媒体、社区、车型库等主要功能模块,其中媒体模块用于新能源汽车的消息发布,包括了图文、视频、简报及最新车型信息等子模块,可以让用户方便的接收新能源汽车消息。社区模块主要为用户提供讨论渠道,其中包括了车型品牌圈子、车型话题讨论、热点新闻、车型打分与评价等功能。车型库模块是其中的核心,在传统汽车平台的基础上,我们增加了许多新能源汽车特有的功能,比如三电系统、辅助驾驶、智能座舱等,我们针对这些功能做了相应评测,建立了对应的评测系统,并在此基础上增加了榜单与选车等功能。
在实践中,我们使用不同的架构来满足系统的需求,架构风格是对历史工程经验的总结,可以有效的降低设计与沟通成本。传统的架构风格可以分为五大类,数据流风格、调用/返回风格、独立构件风格、虚拟机风格、仓库风格。其中数据流风格包括批处理、管道/过滤器风格。调用/返回风格包括主程序/子程序风格、数据抽象和面向对象风格、层次化结构风格。独立构件风格包括进程通讯风格、事件驱动风格。虚拟机风格包括解释器架构风格与基于规则的系统。仓库风格包括以数据库架构风格、黑板架构风格。其中常用的风格有,批处理风格,该风格的主要特征为一次性输入数据,数据经过不同的节点,每个节点处理完后进入下一个节点,直到处理完毕。层次化架构风格,该模式约定不同的层级,每个层级只做一类事情,一般只调用下一层,具有结构明确、耦合度低、维护方便等优点,同时分层过多也会带来性能降低等问题。数据库架构风格,该风格以数据为中心,进行集中存储,同时共享数据状态。除上述架构外,微服务架构也是一种比较常用的架构风格,该风格颗粒度细,独立部署运行,使用轻量级的通讯协议,如 HTTP,具有耦合度低,合作开发效率高,适合大型项目等特点。
该项目立项后进行需求分析,为了通用性,应用系统使用 B/S 架构,优先满足推广与跨平台等需求并保留未来的扩展性。服务端采用分层架构,明确责任并解耦。同时约定使用类 REST 架构风格进行通讯。
- 应用系统
该系统主要采用 B/S 架构风格,传统 B/S 架构中,客户端与服务端开发人员往往是不同的人,但是要在一个项目中进行开发,会出现分工不明确,责任不清晰等问题,所以使用前后端分离的方式进行架构设计,客户端开发人员可以专注于 UI 的交互,数据通过 HTTP 协议与后端进行通讯,后端开发人员可以专注于业务流程的开发,明确责任,方便维护。一般前后端分离,前端只负责 UI 的交互,数据通过 AJAX 等技术去后端获取,展示效果通过 JavaScript 进行操作渲染,导致最终呈现在浏览器源码上的 HTML 是一个固定的结构,没有业务的相关数据,无法进行 SEO。因为该项目有推广需求,所以决定使用 SSR(服务端渲染)同构技术,客户端服务端都采用相同的 JavaScript 语言,客户端使用浏览器提供的 JS 引擎,服务端使用 NodeJS 运行时。用户或者爬虫初次访问页面的时候,NodeJS 会执行对应的 JavaScript 代码,生成对应的 HTML 与运行上下文返回给客户端,客户端拿到后,可以在客户端进行 UI 展示、路由控制等功能。按照该方案,可以做 SEO,满足推广需求,使用同构模式,同一套代码运行在两端,降低了开发成本。同时前后端分离,明确职责与分工。
- 后端服务
该部分主要采用层次化架构风格,主要包括的层包括:中间件层,守卫层,管道层,控制器层,服务层,数据层。其中中间件层用来处理日志,包括用户请求信息,系统响应时间等。项目使用 Json Web Token 进行登录验证,其中主要的用户权限验证在守卫层进行处理。管道层用于输入输出数据的转换与校验,其中有输入部分可以在该层转化为标准输入,以及安全性的预防,如 XSS 攻击。输出数据的处理包括 HTTP 状态码的处理,返回体数据的格式标准化等功能。控制器层用于处理路由响应以及提供标准出入参模板,并委托给服务层处理业务。服务层为主要的业务逻辑处理空间,提供可复用的功能模块,做好封装,方便调用。数据层,该层主要用于数据的存储与交互,其中包括两个主要部分,一部分是数据的实体映射,该部分标记了数据实体与类的映射关系,简化了数据操作,增加了可维护性。另外一部分是针对数据操作的封装,包括数据库、缓存、队列等数据源的增删改查,由于已标记实体映射关系,可以做到与数据库无关,降低了耦合度。该架构风格可以提供一种职责清晰,低耦合,方便测试的应用程序。
- 前后端通讯
考虑到通用性,通讯协议使用 HTTP,架构采用 REST 风格。REST 风格是一种以资源为中心的架构风格,所有的操作均是对资源的控制。REST 对应的 HTTP 控制操作有 GET、POST、PATCH、PUT、DELETE 等方法,GET 表示对资源的获取,POST 表示对资源的新增,PATCH 表示对资源的部分更新,PUT 表示对资源的整体替换,DEL 表示删除资源。REST 标准中要求使用 HTTP 状态码来表示对操作资源的响应,同时在返回体中标记该资源的状态。但是实际开发中会发现 HTTP 状态码过少,且有些复杂状态需要通过响应体里面的内容去判断如何继续交互。所以我们对 REST 做了一些改动,将请求分为是否到达业务层,如果没有达到业务层,按照标准 REST 风格进行响应。如果达到业务层那么统一状态码为 200,表示操作已经发生,在响应体里面以约定的标准格式表示本次处理的状态。项目中约定,业务状态码为 0,那么表示此次交互成功。如果业务状态码非 0,表示业务处理异常,客户端需要根据响应体里面的状态码、错误信息、参考文档链接等信息定位异常确定后续操作逻辑。这种以约定大于配置的方式降低学习和实现成本,极大简化了开发人员的心智负担。
该项目于 2023 年 10 月上线,目前已运行一年多,在此期间未出现严重生产事故。由于基于 B/S 架构风格,且前后端分离,扩展性较好,在二期工程增加移动设备支持的时候,服务端仅少量改动,便顺利上线,节约了开发成本。项目在运行过程中,遇到过数据库压力比较大的情况,我们在调用频率较高的业务上增加了缓存,并在数据库上做了读写分离、分库分表等操作后趋于稳定。
后续为了部署扩容方便,增加了 CI/CD 流程,因为服务端在设计之初是无状态的,方便横向扩展,我们将所有的应用都打包为 Docker 镜像,部署在云服务平台的 ServerLess 上,使用后才付费,极大降低了运行及维护成本。但同时也遇到了 ServerLess 的冷启动问题,当用户的请求到达云服务后,服务会判断当前的实例是否充足,如果无法满足当前的请求,服务会进入冷启动。冷启动时,服务器会去拉取镜像到本地,并启动该镜像,启动成功后,用户的请求才会被响应,该过程耗时几秒至几十秒不等,用户体验较差。由于 ServerLess的资源准备、镜像拉取和启动是云服务厂商控制,这部分环节我们无法参与,所以方案上,主要分为两部分,一是在流量高峰期提前预热服务,满足峰值流量要求,降低冷启动出现概率。二是优化容器镜像,减少镜像拉取和启动时间。目前冷启动概率已明显减少,镜像优化还有提升空间。