架构设计随想-Unix哲学和Node.js

前几天,我在TxJS上发表了一篇演讲,其中提到Unix哲学是Node.js模式,观点和文化的重要组成部分。像往常一样,我可以在提供谈话视频之前在线提供我的幻灯片

出于某种原因,这篇简短的提及“Unix哲学”引发了一些人的愤怒。由于我只有25分钟,并且每张幻灯片都可能完全是它自己的谈话,所以我对阐述很轻松。有可能,视频不会添加太多的背景。但目标是激起对话,所以如果它引起批评,也许这是成功的。毕竟,不知情的拖钓只是教育的邀请,所以我想我会解释。

Eric S. Raymond在他的书“Unix的编程艺术”中收集了一些关于Unix哲学的最佳解释。他详细阐述了17个具体的原则,但我最喜欢的Unix哲学的解释是Doug McIlroy在“四分之一世纪的Unix”中引用的简洁解释:

这是Unix哲学:

编写完成一件事并做得好的程序。

编写程序以协同工作。

编写程序来处理文本流,因为这是一个通用接口。

McIlroy的原始4点配方略长一点:

(i)让每个项目做好一件事。要做一份新工作,重新构建而不是通过添加新功能使旧程序复杂化。

(ii)期望每个节目的输出成为另一个尚未知的节目的输入。不要使用无关信息使输出混乱。避免严格的柱状或二进制输入格式。不要坚持互动输入。

(iii)设计和构建软件,甚至是操作系统,以便在几周内尽早尝试。不要犹豫扔掉笨拙的部分并重建它们。

(iv)使用工具优先于不熟练的帮助来减轻编程任务,即使你不得不绕道去构建工具并期望在你完成它们之后把它们中的一些扔掉。

从事X Window系统工作的Mike Gancarz总结了以下9点的Unix哲学:

  1. 小是美丽的。
  2. 让每个程序做好一件事。
  3. 尽快建立原型。
  4. 选择便携性超过效率。
  5. 将数据存储在平面文本文件中。
  6. 使用软件杠杆对您有利。
  7. 使用shell脚本来提高杠杆率和可移植性。
  8. 避免强制用户界面。
  9. 使每个程序成为过滤器。

最后一点确实引起了瑞恩达尔经常说的话,“每个节目都是代理人。”前三个基本上是詹姆斯·哈利迪的生活规则。

很多时候,人们会对Unix哲学的错误方面感到困惑,并错过森林中的树木。Unix哲学不是关于特定的实现,也不是任何Unix操作系统或程序必需的。它不是关于文件描述符,管道,套接字或信号。这些抱怨就像是说除非他们说Pali,否则某人不是佛教徒。

Unix哲学是软件开发的前景,而不是软件中的任何特定技术开发。这是一种理想的接触,也许具有讽刺意味的是,它是一种理想,它指示我们偶尔避开理想主义以支持实用性。

在Node中,人们共享和交互的基本构建块不是命令行上的二进制文件,而是加载的模块require()。通用接口一个文本流,但它是一个JavaScript Stream对象,而不是一个stdio管道。(标准输入输出管道是由JavaScript流代表,当然,因为那是我们的通用接口,所以还有什么我们使用?)

那么,就Node.js而言,这就是我如何表达Unix哲学。唉,我不是麦克罗伊,我没有时间或技巧来写这个更短。

编写能很好地完成一件事的模块。编写一个新模块而不是使旧模块复杂化。

编写鼓励构图而不是扩展的模块。

编写处理数据流的模块,因为这是通用接口。

编写与输入源或输出目标无关的模块。

编写解决您知道的问题的模块,这样您就可以了解那些您不知道的问题。

写小的模块。快速迭代。无情地重构。勇敢地改写。

只需进行一些合规性测试,即可快速编写模块以满足您的需求。避免广泛的规格。为您修复的每个错误添加测试。

编写模块以供发布,即使您只是私下使用它们。您将来会欣赏文档。


工作胜于完美。

专注比功能更好。

兼容性优于纯度。

简单优于任何事物。

Unix哲学是一种实用主义的意识形态。它是关于平衡编写优秀软件和编写任何软件的双重需求。这是一套实用的建议,用于交易适度增加的开发成本,从而大幅降低维护成本。

在现实世界中,我们面临着在编写程序和调试程序时成为人类的完全不公平的约束,并且这些成本都不能减少到零。这种意识形态是上下文的,可以应用于堆栈的所有级别。这是一个公开承认我们实际上不够聪明,不知道如何编写我们第一次需要的软件,因为我们通常只有在完成解决后才能完全理解我们的问题。

这些规则都不是神圣不可侵犯的! 事实上,在许多情况下,他们可能存在分歧,甚至完全矛盾。但是,如果我们通过简单的通用接口保持我们的编程单元很小,我们可以发现将零碎结构作为质量棘轮,在我们进行时更换笨拙的部件。

关于Unix哲学的任何内容都没有明确涉及软件共享的文化。然而,它来自软件社区并不是一个谜,我们会详细讨论使我们的程序正常免费的最佳方法。根据这些原则开发的软件更易于共享,重用,重新利用和维护。


翻译自 :node.js 维护者

http://blog.izs.me/post/48281998870/unix-philosophy-and-nodejs

架构横向对比-精读前后端渲染之争

这是我在前端精度专栏发的一篇文章。精读的文章是:Here’s why Client-side Rendering Won

1 引言


 

我为什么要选这篇文章呢?

十年前,几乎所有网站都使用 ASP、Java、PHP 这类做后端渲染,但后来随着 jQuery、Angular、React、Vue 等 JS 框架的崛起,开始转向了前端渲染。从 2014 年起又开始流行了同构渲染,号称是未来,集成了前后端渲染的优点,但转眼间三年过去了,很多当时壮心满满的框架(rendrLazo)从先驱变成了先烈。同构到底是不是未来?自己的项目该如何选型?我想不应该只停留在追求热门和拘泥于固定模式上,忽略了前后端渲染之“争”的“核心点”,关注如何提升“用户体验”。

原文分析了前端渲染的优势,并没有进行深入探讨。我想以它为切入口来深入探讨一下。

明确三个概念:「后端渲染」指传统的 ASP、Java 或 PHP 的渲染机制;「前端渲染」指使用 JS 来渲染页面大部分内容,代表是现在流行的 SPA 单页面应用;「同构渲染」指前后端共用 JS,首次渲染时使用 Node.js 来直出 HTML。一般来说同构渲染是介于前后端中的共有部分。

2 内容概要


前端渲染的优势

  • 局部刷新。无需每次都进行完整页面请求
  • 懒加载。如在页面初始时只加载可视区域内的数据,滚动后rp加载其它数据,可以通过 react-lazyload 实现
  • 富交互。使用 JS 实现各种酷炫效果
  • 节约服务器成本。省电省钱,JS 支持 CDN 部署,且部署极其简单,只需要服务器支持静态文件即可
  • 天生的关注分离设计。服务器来访问数据库提供接口,JS 只关注数据获取和展现
  • JS 一次学习,到处使用。可以用来开发 Web、Serve、Mobile、Desktop 类型的应用

 

后端渲染的优势

  • 服务端渲染不需要先下载一堆 js 和 css 后才能看到页面(首屏性能)
  • SEO
  • 服务端渲染不用关心浏览器兼容性问题(随着浏览器发展,这个优点逐渐消失)
  • 对于电量不给力的手机或平板,减少在客户端的电量消耗很重要

以上服务端优势其实只有首屏性能和 SEO 两点比较突出。但现在这两点也慢慢变得微不足道了。React 这类支持同构的框架已经能解决这个问题,尤其是 Next.js 让同构开发变得非常容易。还有静态站点的渲染,但这类应用本身复杂度低,很多前端框架已经能完全囊括。

3 精读

本次提出独到观点的同学有:@javie007 @杨森 @流形 @camsong @Turbe Xue @淡苍 @留影@FrankFang @alcat2008 @xile611 @twobin @黄子毅 精读由此归纳。

大家对前端和后端渲染的现状基本达成共识。即前端渲染是未来趋势,但前端渲染遇到了首屏性能和SEO的问题。对于同构争议最多,在此我归纳一下。

前端渲染遇到的问题

前端渲染主要面临的问题有两个 SEO首屏性能

SEO 很好理解。由于传统的搜索引擎只会从 HTML 中抓取数据,导致前端渲染的页面无法被抓取。前端渲染常使用的 SPA 会把所有 JS 整体打包,无法忽视的问题就是文件太大,导致渲染前等待很长时间。特别是网速差的时候,让用户等待白屏结束并非一个很好的体验。

同构的优点

同构恰恰就是为了解决前端渲染遇到的问题才产生的,至 2014 年底伴随着 React 的崛起而被认为是前端框架应具备的一大杀器,以至于当时很多人为了用此特性而放弃 Angular 1 而转向 React。然而近3年过去了,很多产品逐渐从全栈同构的理想化逐渐转到首屏或部分同构。让我们再一次思考同构的优点真是优点吗?

  1. 有助于 SEO

首先确定你的应用是否都要做 SEO,如果是一个后台应用,那么只要首页做一些静态内容宣导就可以了。如果是内容型的网站,那么可以考虑专门做一些页面给搜索引擎
时到今日,谷歌已经能够可以在爬虫中执行 JS 像浏览器一样理解网页内容,只需要往常一样使用 JS 和 CSS 即可。并且尽量使用新规范,使用 pushstate 来替代以前的 hashstate。不同的搜索引擎的爬虫还不一样,要做一些配置的工作,而且可能要经常关注数据,有波动那么可能就需要更新。第二是该做 sitemap 的还得做。相信未来即使是纯前端渲染的页面,爬虫也能很好的解析。

  1. 共用前端代码,节省开发时间

其实同构并没有节省前端的开发量,只是把一部分前端代码拿到服务端执行。而且为了同构还要处处兼容 Node.js 不同的执行环境。有额外成本,这也是后面会具体谈到的。

  1. 提高首屏性能

由于 SPA 打包生成的 JS 往往都比较大,会导致页面加载后花费很长的时间来解析,也就造成了白屏问题。服务端渲染可以预先使到数据并渲染成最终 HTML 直接展示,理想情况下能避免白屏问题。在我参考过的一些产品中,很多页面需要获取十几个接口的数据,单是数据获取的时候都会花费数秒钟,这样全部使用同构反而会变慢。

同构并没有想像中那么美

  1. 性能

把原来放在几百万浏览器端的工作拿过来给你几台服务器做,这还是花挺多计算力的。尤其是涉及到图表类需要大量计算的场景。这方面调优,可以参考 walmart的调优策略

个性化的缓存是遇到的另外一个问题。可以把每个用户个性化信息缓存到浏览器,这是一个天生的分布式缓存系统。我们有个数据类应用通过在浏览器合理设置缓存,双十一当天节省了 70% 的请求量。试想如果这些缓存全部放到服务器存储,需要的存储空间和计算都是很非常大。

  1. 不容忽视的服务器端和浏览器环境差异

前端代码在编写时并没有过多的考虑后端渲染的情景,因此各种 BOM 对象和 DOM API 都是拿来即用。这从客观层面也增加了同构渲染的难度。我们主要遇到了以下几个问题:

  • document 等对象找不到的问题
  • DOM 计算报错的问题
  • 前端渲染和服务端渲染内容不一致的问题

由于前端代码使用的 window 在 node 环境是不存在的,所以要 mock window,其中最重要的是 cookie,userAgent,location。但是由于每个用户访问时是不一样的 window,那么就意味着你得每次都更新 window
而服务端由于 js require 的 cache 机制,造成前端代码除了具体渲染部分都只会加载一遍。这时候 window就得不到更新了。所以要引入一个合适的更新机制,比如把读取改成每次用的时候再读取。

export const isSsr = () => (
  !(typeof window !== 'undefined' && window.document && window.document.createElement && window.setTimeout)
);

原因是很多 DOM 计算在 SSR 的时候是无法进行的,涉及到 DOM 计算的的内容不可能做到 SSR 和 CSR 完全一致,这种不一致可能会带来页面的闪动。

  1. 内存溢出

前端代码由于浏览器环境刷新一遍内存重置的天然优势,对内存溢出的风险并没有考虑充分。
比如在 React 的 componentWillMount 里做绑定事件就会发生内存溢出,因为 React 的设计是后端渲染只会运行 componentDidMount 之前的操作,而不会运行 componentWillUnmount 方法(一般解绑事件在这里)。

  1. 异步操作

前端可以做非常复杂的请求合并和延迟处理,但为了同构,所有这些请求都在预先拿到结果才会渲染。而往往这些请求是有很多依赖条件的,很难调和。纯 React 的方式会把这些数据以埋点的方式打到页面上,前端不再发请求,但仍然再渲染一遍来比对数据。造成的结果是流程复杂,大规模使用成本高。幸运的是 Next.js 解决了这一些,后面会谈到。

  1. simple store(redux)

这个 store 是必须以字符串形式塞到前端,所以复杂类型是无法转义成字符串的,比如function。

总的来说,同构渲染实施难度大,不够优雅,无论在前端还是服务端,都需要额外改造。

首屏优化

再回到前端渲染遇到首屏渲染问题,除了同构就没有其它解法了吗?总结以下可以通过以下三步解决

  1. 分拆打包

现在流行的路由库如 react-router 对分拆打包都有很好的支持。可以按照页面对包进行分拆,并在页面切换时加上一些 loading 和 transition 效果。

  1. 交互优化

首次渲染的问题可以用更好的交互来解决,先看下 linkedin 的渲染

有什么感受,非常自然,打开渲染并没有白屏,有两段加载动画,第一段像是加载资源,第二段是一个加载占位器,过去我们会用 loading 效果,但过渡性不好。近年流行 Skeleton Screen 效果。其实就是在白屏无法避免的时候,为了解决等待加载过程中白屏或者界面闪烁造成的割裂感带来的解决方案。

  1. 部分同构

部分同构可以降低成功同时利用同构的优点,如把核心的部分如菜单通过同构的方式优先渲染出来。我们现在的做法就是使用同构把菜单和页面骨架渲染出来。给用户提示信息,减少无端的等待时间。

相信有了以上三步之后,首屏问题已经能有很大改观。相对来说体验提升和同构不分伯仲,而且相对来说对原来架构破坏性小,入侵性小。是我比较推崇的方案。

3 总结


我们赞成客户端渲染是未来的主要方向,服务端则会专注于在数据和业务处理上的优势。但由于日趋复杂的软硬件环境和用户体验更高的追求,也不能只拘泥于完全的客户端渲染。同构渲染看似美好,但以目前的发展程度来看,在大型项目中还不具有足够的应用价值,但不妨碍部分使用来优化首屏性能。做同构之前 ,一定要考虑到浏览器和服务器的环境差异,站在更高层面考虑。

附:Next.js 体验

Next.js 是时下非常流行的基于 React 的同构开发框架。作者之一就是大名鼎鼎的 Socket.io 的作者 Guillermo Rauch。它有以下几个亮点特别吸引我:

  1. 巧妙地用标准化的解决了请求的问题。同构和页面开发类似,异步是个大难题,异步中难点又在接口请求。Next.js 给组件新增了 getInitialProps 方法来专门处理初始化请求,再也不用手动往页面上塞 DATA 和调用 ReactDOMServer.renderToString
  2. 使用 styled-jsx 解决了 css-in-js 的问题。这种方案虽然不像 styled-component 那样强大,但足够简单,可以说是最小的成本解决了问题
  3. Fast by default。页面默认拆分文件方式打包,支持Prefetch页面预加载

全家桶式的的解决方案。简洁清晰的目录结构,这一点 Redux 等框架真应该学一学。不过全家桶的方案比较适合全新项目使用,旧项目使用要评估好成本

讨论地址是:前后端渲染之争 · Issue #5 · dt-fe/weekly


一些读者意见:

我还没有看【Here’s why Client-side Rendering Won】这篇文章,但是我对你的精读归纳并不赞同:

  1. 这里只是列出了一些前后端同构遇到的或大或小的问题,并非不能通过技术方案解决;
  2. 替代同构的方案里,拆分打包是 ‘自古以来’ 都在做的事情,但是即使是最小的包,也需要加载时间;
  3. 替代方案里的实现,并不是所有的网站都可以使用的,比如新闻门户网站,让用户等待一个loading动画?我觉得不符合大部分用户的使用习惯。
  4. SEO问题如何解决, 此处并没有给出方案,我们总不能把希望寄托在百度升级爬虫这种事情上吧?

作为一个读者,我希望读到的不是问题的对错,而是对于技术的改进方案。


来自:https://github.com/camsong/blog/issues/8

架构横向对比-多页应用与单页应用

多页应用

每一次页面跳转的时候,后台服务器都会给返回一个新的html文档,这种类型的网站也就是多页网站,也叫做多页应用。

多页应用
为什么多页应用的首屏时间快?

首屏时间叫做页面首个屏幕的内容展现的时间,当我们访问页面的时候,服务器返回一个html,页面就会展示出来,这个过程只经历了一个HTTP请求,所以页面展示的速度非常快。

为什么搜索引擎优化效果好(SEO)?

搜索引擎在做网页排名的时候,要根据网页内容才能给网页权重,来进行网页的排名。搜索引擎是可以识别html内容的,而我们每个页面所有的内容都放在Html中,所以这种多页应用,seo排名效果好。

但是它也有缺点,就是切换慢

因为每次跳转都需要发出一个http请求,如果网络比较慢,在页面之间来回跳转时,就会发现明显的卡顿。

单页应用

第一次进入页面的时候会请求一个html文件,刷新清除一下。切换到其他组件,此时路径也相应变化,但是并没有新的html文件请求,页面内容也变化了。
原理是:JS会感知到url的变化,通过这一点,可以用js动态的将当前页面的内容清除掉,然后将下一个页面的内容挂载到当前页面上,这个时候的路由不是后端来做了,而是前端来做,判断页面到底是显示哪个组件,清除不需要的,显示需要的组件。这种过程就是单页应用,每次跳转的时候不需要再请求html文件了。

我是单页应用
为什么页面切换快?

页面每次切换跳转时,并不需要做html文件的请求,这样就节约了很多http发送时延,我们在切换页面的时候速度很快。

缺点:首屏时间慢,SEO差

单页应用的首屏时间慢,首屏时需要请求一次html,同时还要发送一次js请求,两次请求回来了,首屏才会展示出来。相对于多页应用,首屏时间慢。
SEO效果差,因为搜索引擎只认识html里的内容,不认识js的内容,而单页应用的内容都是靠js渲染生成出来的,搜索引擎不识别这部分内容,也就不会给一个好的排名,会导致单页应用做出来的网页在百度和谷歌上的排名差。

有这些缺点,为什么还要使用Vue呢?

Vue还提供了一些其它的技术来解决这些缺点,比如说服务器端渲染技术(我是SSR),通过这些技术可以完美解决这些缺点,解决完这些问题,实际上单页面应用对于前端来说是非常完美的页面开发解决方案。

来自:八宝君    https://www.jianshu.com/p/4c9c29967dd6

架构演变-MV*模式的分析与演变

前言


做客户端开发、前端开发对MVC、MVP、MVVM这些名词不了解也应该大致听过,都是为了解决图形界面应用程序复杂性管理问题而产生的应用架构模式。网上很多文章关于这方面的讨论比较杂乱,各种MV模式之间的区别分不清,甚至有些描述都是错误的。本文追根溯源,从最经典的Smalltalk-80 MVC模式开始逐步还原图形界面之下最真实的MV模式。

GUI程序所面临的问题


图形界面的应用程序提供给用户可视化的操作界面,这个界面提供给数据和信息。用户输入行为(键盘,鼠标等)会执行一些应用逻辑,应用逻辑(application logic)可能会触发一定的业务逻辑(business logic)对应用程序数据的变更,数据的变更自然需要用户界面的同步变更以提供最准确的信息。例如用户对一个电子表格重新排序的操作,应用程序需要响应用户操作,对数据进行排序,然后需要同步到界面上。

在开发应用程序的时候,以求更好的管理应用程序的复杂性,基于**职责分离(Speration of Duties)**的思想都会对应用程序进行分层。在开发图形界面应用程序的时候,会把管理用户界面的层次称为View,应用程序的数据为Model(注意这里的Model指的是Domain Model,这个应用程序对需要解决的问题的数据抽象,不包含应用的状态,可以简单理解为对象)。Model提供数据操作的接口,执行相应的业务逻辑。

有了View和Model的分层,那么问题就来了:View如何同步Model的变更,View和Model之间如何粘合在一起。

带着这个问题开始探索MV模式,会发现这些模式之间的差异可以归纳为对这个问题处理的方式的不同。而几乎所有的MV模式都是经典的Smalltalk-80 MVC的修改版。

Smalltalk-80 MVC


历史背景

早在上个世纪70年代,美国的施乐公司(Xerox)的工程师研发了Smalltalk编程语言,并且开始用它编写图形界面的应用程序。而在Smalltalk-80这个版本的时候,一位叫Trygve Reenskaug的工程师设计了MVC图形应用程序的架构模式,极大地降低了图形应用程序的管理难度。而在四人帮(GoF)的设计模式当中并没有把MVC当做是设计模式,而仅仅是把它看成解决问题的一些类的集合。Smalltalk-80 MVC和GoF描述的MVC是最经典的MVC模式。

MVC的依赖关系

MVC出了把应用程序分成View、Model层,还额外的加了一个Controller层,它的职责为进行Model和View之间的协作(路由、输入预处理等)的应用逻辑(application logic);Model进行处理业务逻辑。Model、View、Controller三个层次的依赖关系如下:

 

Controller和View都依赖Model层,Controller和View可以互相依赖。在一些网上的资料Controller和View之间的依赖关系可能不一样,有些是单向依赖,有些是双向依赖,这个其实关系不大,后面会看到它们的依赖关系都是为了把处理用户行为触发的事件处理权交给Controller。

MVC的调用关系

用户的对View操作以后,View捕获到这个操作,会把处理的权利交移给Controller(Pass calls);Controller会对来自View数据进行预处理、决定调用哪个Model的接口;然后由Model执行相关的业务逻辑;当Model变更了以后,会通过观察者模式(Observer Pattern)通知View;View通过观察者模式收到Model变更的消息以后,会向Model请求最新的数据,然后重新更新界面。如下图:

 

看似没有什么特别的地方,但是由几个需要特别关注的关键点:

  1. View是把控制权交移给Controller,Controller执行应用程序相关的应用逻辑(对来自View数据进行预处理、决定调用哪个Model的接口等等)。
  2. Controller操作Model,Model执行业务逻辑对数据进行处理。但不会直接操作View,可以说它是对View无知的。
  3. View和Model的同步消息是通过观察者模式进行,而同步操作是由View自己请求Model的数据然后对视图进行更新。

需要特别注意的是MVC模式的精髓在于第三点:Model的更新是通过观察者模式告知View的,具体表现形式可以是Pub/Sub或者是触发Events。而网上很多对于MVC的描述都没有强调这一点。通过观察者模式的好处就是:不同的MVC三角关系可能会有共同的Model,一个MVC三角中的Controller操作了Model以后,两个MVC三角的View都会接受到通知,然后更新自己。保持了依赖同一块Model的不同View显示数据的实时性和准确性。我们每天都在用的观察者模式,在几十年前就已经被大神们整合到MVC的架构当中。

这里有一个MVC模式的JavaScript Demo,实现了一个小的TodoList应用程序。经典的Smalltalk-80 MVC不需要任何框架支持就可以实现。目前Web前端框架当中只有一个号称是严格遵循Smalltalk-80 MVC模式的:maria.js

MVC的优缺点

优点:

  1. 把业务逻辑和展示逻辑分离,模块化程度高。且当应用逻辑需要变更的时候,不需要变更业务逻辑和展示逻辑,只需要Controller换成另外一个Controller就行了(Swappable Controller)。
  2. 观察者模式可以做到多视图同时更新。

缺点:

  1. Controller测试困难。因为视图同步操作是由View自己执行,而View只能在有UI的环境下运行。在没有UI环境下对Controller进行单元测试的时候,应用逻辑正确性是无法验证的:Model更新的时候,无法对View的更新操作进行断言。
  2. View无法组件化。View是强依赖特定的Model的,如果需要把这个View抽出来作为一个另外一个应用程序可复用的组件就困难了。因为不同程序的的Domain Model是不一样的

MVC Model 2


在Web服务端开发的时候也会接触到MVC模式,而这种MVC模式不能严格称为MVC模式。经典的MVC模式只是解决客户端图形界面应用程序的问题,而对服务端无效。服务端的MVC模式又自己特定的名字:MVC Model 2,或者叫JSP Model 2,或者直接就是Model 2 。Model 2客户端服务端的交互模式如下:

 

服务端接收到来自客户端的请求,服务端通过路由规则把这个请求交由给特定的Controller进行处理,Controller执行相应的应用逻辑,对Model进行操作,Model执行业务逻辑以后;然后用数据去渲染特定的模版,返回给客户端。

因为HTTP协议是单工协议并且是无状态的,服务器无法直接给客户端推送数据。除非客户端再次发起请求,否则服务器端的Model的变更就无法告知客户端。所以可以看到经典的Smalltalk-80 MVC中Model通过观察者模式告知View更新这一环被无情地打破,不能称为严格的MVC。

Model 2模式最早在1998年应用在JSP应用程序当中,JSP Model 1应用管理的混乱诱发了JSP参考了客户端MVC模式,催生了Model 2。

 

后来这种模式几乎被应用在所有语言的Web开发框架当中。PHP的ThinkPHP,Python的Dijango、Flask,NodeJS的Express,Ruby的RoR,基本都采纳了这种模式。平常所讲的MVC基本是这种服务端的MVC。

MVP


MVP模式有两种:

  1. Passive View
  2. Supervising Controller

而大多数情况下讨论的都是Passive View模式。本文会对PV模式进行较为详细的介绍,而SC模式则简单提及。

历史背景

MVP模式是MVC模式的改良。在上个世纪90年代,IBM旗下的子公司Taligent在用C/C++开发一个叫CommonPoint的图形界面应用系统的时候提出来的。

MVP(Passive View)的依赖关系

MVP模式把MVC模式中的Controller换成了Presenter。MVP层次之间的依赖关系如下:

 

MVP打破了View原来对于Model的依赖,其余的依赖关系和MVC模式一致。

MVP(Passive View)的调用关系

既然View对Model的依赖被打破了,那View如何同步Model的变更?看看MVP的调用关系:

 

和MVC模式一样,用户对View的操作都会从View交移给Presenter。Presenter会执行相应的应用程序逻辑,并且对Model进行相应的操作;而这时候Model执行完业务逻辑以后,也是通过观察者模式把自己变更的消息传递出去,但是是传给Presenter而不是View。Presenter获取到Model变更的消息以后,通过View提供的接口更新界面

关键点:

  1. View不再负责同步的逻辑,而是由Presenter负责。Presenter中既有应用程序逻辑也有同步逻辑。
  2. View需要提供操作界面的接口给Presenter进行调用。(关键)

对比在MVC中,Controller是不能操作View的,View也没有提供相应的接口;而在MVP当中,Presenter可以操作View,View需要提供一组对界面操作的接口给Presenter进行调用;Model仍然通过事件广播自己的变更,但由Presenter监听而不是View。

MVP模式,这里也提供一个用JavaScript编写的例子

MVP(Passive View)的优缺点

优点:

  1. 便于测试。Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。这里根据上面的例子给出了Presenter的单元测试样例
  2. View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。

缺点:

  1. Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。

MVP(Supervising Controller)

上面讲的是MVP的Passive View模式,该模式下View非常Passive,它几乎什么都不知道,Presenter让它干什么它就干什么。而Supervising Controller模式中,Presenter会把一部分简单的同步逻辑交给View自己去做,Presenter只负责比较复杂的、高层次的UI操作,所以可以把它看成一个Supervising Controller。

Supervising Controller模式下的依赖和调用关系:

 

因为Supervising Controller用得比较少,对它的讨论就到这里为止。

MVVM


MVVM可以看作是一种特殊的MVP(Passive View)模式,或者说是对MVP模式的一种改良。

历史背景

MVVM模式最早是微软公司提出,并且了大量使用在.NET的WPF和Sliverlight中。2005年微软工程师John Gossman在自己的博客上首次公布了MVVM模式。

ViewModel

MVVM代表的是Model-View-ViewModel,这里需要解释一下什么是ViewModel。ViewModel的含义就是 “Model of View”,视图的模型。它的含义包含了领域模型(Domain Model)和视图的状态(State)。 在图形界面应用程序当中,界面所提供的信息可能不仅仅包含应用程序的领域模型。还可能包含一些领域模型不包含的视图状态,例如电子表格程序上需要显示当前排序的状态是顺序的还是逆序的,而这是Domain Model所不包含的,但也是需要显示的信息。

可以简单把ViewModel理解为页面上所显示内容的数据抽象,和Domain Model不一样,ViewModel更适合用来描述View。

MVVM的依赖

MVVM的依赖关系和MVP依赖,只不过是把P换成了VM。

MVVM的调用关系

MVVM的调用关系和MVP一样。但是,在ViewModel当中会有一个叫Binder,或者是Data-binding engine的东西。以前全部由Presenter负责的View和Model之间数据同步操作交由给Binder处理。你只需要在View的模版语法当中,指令式地声明View上的显示的内容是和Model的哪一块数据绑定的。当ViewModel对进行Model更新的时候,Binder会自动把数据更新到View上去,当用户对View进行操作(例如表单输入),Binder也会自动把数据更新到Model上去。这种方式称为:Two-way data-binding,双向数据绑定。可以简单而不恰当地理解为一个模版引擎,但是会根据数据变更实时渲染。

 

也就是说,MVVM把View和Model的同步逻辑自动化了。以前Presenter负责的View和Model同步不再手动地进行操作,而是交由框架所提供的Binder进行负责。只需要告诉Binder,View显示的数据对应的是Model哪一部分即可。

这里有一个JavaScript MVVM的例子,因为MVVM需要Binder引擎。所以例子中使用了一个MVVM的库:Vue.js

MVVM的优缺点

优点:

  1. 提高可维护性。解决了MVP大量的手动View和Model同步的问题,提供双向绑定机制。提高了代码的可维护性。
  2. 简化测试。因为同步逻辑是交由Binder做的,View跟着Model同时变更,所以只需要保证Model的正确性,View就正确。大大减少了对View同步更新的测试。

缺点:

  1. 过于简单的图形界面不适用,或说牛刀杀鸡。
  2. 对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高。
  3. 数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的。

 

结语


可以看到,从MVC->MVP->MVVM,就像一个打怪升级的过程。后者解决了前者遗留的问题,把前者的缺点优化成了优点。同样的Demo功能,代码从最开始的一堆文件,优化成了最后只需要20几行代码就完成。MV*模式之间的区分还是蛮清晰的,希望可以给对这些模式理解比较模糊的同学带来一些参考和思路。

References

小广告:欢迎follow关注个人github:https://github.com/livoras

来自:戴嘉华   https://github.com/livoras/blog/issues/11

软件分析模式-概述

分析模式:可复用的对象模型


 

分析模式:可复用的对象模型
原文书名:Analysis Patterns: Reusable Object Models
作者: (英) Martin Fowler
译者:樊东平 张路 等
书号:978-7-111-30530-9
上市时间:2010年6月

 

作者简介:

Martin Fowler 在面向对象分析设计、UML、模式、软件开发方法学、XP、重构等方面,都是世界顶级的专家,现为ThoughtWorks公司的首席科学家。 ThoughtWorks是一家从事企业应用开发和集成的公司。早在20世纪80年代,Fowler就是适用对象技术构建多层企业应用的倡导者,他著有几 本经典书籍:《分析模式》、《UML精粹》和《重构》等。

 

内容简介:
《分析模式:可复用的对象模型 》的作者Martin Fowler是国际著名的OO专家,敏捷开发方法的创始人之一,现为ThoughtWorks公司的首席科学家,本书是作者的代表作之一,深受业界专业人士和广大读者的好评,经久不衰。
《分析模式:可复用的对象模型 》讲述各种分析模式(即来自概念性业务模型的模式)和支持模式(即讲述如何使用分析模式的辅助性模式),把论述重点放在介绍面向对象分析和设计的最终结果—即模型本身。作者透过平实朴素的语言,将自己丰富的对象建模经验与读者分享,使读者可以马上采纳这些经验性模式。
本书适合的读者范围非常广:面向对象的计算机分析人员和设计人员(尤其是那些参与系统分析的人员)、数据建模人员、编程人员以及专业的软件工程师都可以从本书中获得宝贵的知识和经验。

本书赞誉:
“《分析模式:可复用的对象模型 》是对不断发展的模式文献的一个重要贡献。它捕捉来自不同领域的深奥的对象建模专业知识,形成一个模式目录。这些领域模式将有助于你解决不同领域中具有挑战性的建模问题。”
———《设计模式》作者Erich Gamma
“Martin Fowler为我们给出答案,而不仅仅是一个可以找到这些答案的过程。在《分析模式:可复用的对象模型 》中,透过作者平实朴素的语言,你将找到自己下一个业务对象模型的重要内容。”
———Ward Cunningham
“就像‘四人帮’在他们的经典著作《设计模式》中总结出了通用的设计模式,Martin Fowler在这本让人期待已久的书中为我们总结出应用领域的诸多模式。《分析模式:可复用的对象模型 》是从事面向对象业务建模和业务过程重组工作的所有分析人员和设计人员的必备之书。”
——Donald G. Firesmith

 

目录
Ralph Johnson序
Ward Cunningham序
前言
第1章 绪论 1
1.1 概念模型 1
1.2 模式世界 4
1.2.1 Christopher Alexander 5
1.2.2 描述格式 5
1.2.3 关于模式的抽象程度 6
1.3 本书中的模式 7
1.3.1 建模实例 8
1.3.2 模式的来源 8
1.3.3 跨领域的模式 9
1.4 概念模型与业务过程重组 9
1.5 模式与框架 10
1.6 本书的使用 11
第一部分 分析模式
第2章 责任模式 17
2.1 团体 18
2.2 组织层次 19
2.3 组织结构 21
2.4 责任 22
2.5 责任知识级 24
2.6 团体类型泛化 26
2.7 层次型责任 27
2.8 操作范围 29
2.9 职位 31
第3章 观察和测量模式 33
3.1 数量 34
3.2 转换率 36
3.3 复合单位 37
3.4 测量 38
3.5 观察 40
3.6 观察概念的子类型化 43
3.7 观察方案 44
3.8 双时间记录 44
3.9 被否决的观察 45
3.10 临床观察、假设与推理 45
3.11 关联观察 46
3.12 观察过程 48
第4章 针对公司财务的观察模式 52
4.1 企业片断 53
4.1.1 定义维度 57
4.1.2 维度的属性以及企业片断 59
4.2 测量方案 60
4.2.1 保持计算的有效性 61
4.2.2 比较和因果测量方案 62
4.2.3 状态类型:定义计划的和实际的状态 63
4.2.4 构造测量 66
4.2.5 维度合并 66
4.3 范围 69
4.4 带范围的现象 70
4.4.1 带范围属性的现象 71
4.4.2 范围函数 73
4.5 使用最终框架 75
第5章 引用对象 77
5.1 名称 77
5.2 标识方案 79
5.3 对象合并 81
5.3.1 复制并替换 82
5.3.2 替代 82
5.3.3 本质/表象 83
5.4 对象等价 83
第6章 库存与账务 85
6.1 账目 87
6.2 事务 88
6.3 汇总账目 90
6.4 备注账目 92
6.5 记入规则 93
6.5.1 可逆性 94
6.5.2 不使用事务 94
6.6 个体实例方法 95
6.6.1 使用singleton类实现 95
6.6.2 使用策略模式实现 96
6.6.3 使用内部case语句实现 97
6.6.4 使用参数化方法实现 98
6.6.5 使用解释器实现 98
6.6.6 实现方式的选择 99
6.7 记入规则的执行 99
6.7.1 急切触发 99
6.7.2 基于账目的触发 101
6.7.3 基于记入规则的触发 102
6.7.4 向后链式触发 102
6.7.5 触发手段的比较 102
6.8 多个账目的记入规则 103
6.9 选择条目 106
6.10 账务实践 107
6.11 条目来源 109
6.12 结算单和所得计算书 110
6.13 对应账目 111
6.14 专门化的账目模型 112
6.15 登记条目到多个账目 113
6.15.1 使用备注账目 116
6.15.2 派生账目 116
进一步阅读 118
第7章 使用财务模型 119
7.1 结构模型 120
7.2 结构的实现 122
7.3 设置新的电话服务 124
7.4 建立通话 126
7.5 实现基于账目的触发 127
7.6 把电话分成白天和夜晚两类 128
7.7 按时间收费 130
7.8 计算税款 133
7.9 结论 134
7.9.1 记入规则的结构 134
7.9.2 什么时候不能使用框架 136
7.9.3 账务实践图 137
第8章 计划 139
8.1 提议和执行的动作 140
8.2 完成和放弃的动作 141
8.3 挂起 142
8.4 计划 143
8.5 方案 146
8.6 资源分配 149
8.7 输出和启动函数 153
第9章 交易 156
9.1 合同 156
9.2 合同夹 160
9.3 报价 165
9.4 场景 168
第10章 派生合同 176
10.1 期货合同 177
10.2 期权 179
10.2.1 多头、空头、看涨和看跌:体现一种谋略的词汇 181
10.2.2 子类型化或者非子类型化 182
10.3 产品 184
10.4 子类型状态机 188
10.4.1 确保状态图的一致 190
10.4.2 一致性的使用问题 192
10.5 并行的应用和领域层次结构 194
10.5.1 应用外观的类型检查 195
10.5.2 给超类型一个包装性接口 196
10.5.3 使用一个运行时属性 196
10.5.4 使应用外观对领域模型可见 198
10.5.5 使用异常处理 199
第11章 交易包 201
11.1 对一个包的多重访问级别 201
11.2 相互可见性 205
11.3 包的子类型化 208
11.4 结论 209
第二部分 支持模式
第12章 信息系统的分层构架 213
12.1 两层构架 214
12.2 三层构架 215
12.3 表示层和应用逻辑层 218
12.3.1 表示层/应用逻辑层分离的优点 222
12.3.2 在客户/服务器环境中伸展外观 222
12.4 数据库交互 224
12.4.1 把领域层连接到数据源 224
12.4.2 数据库接口层 225
12.5 结论 227
第13章 应用外观 229
13.1 一个医疗保健示例 229
13.2 外观的内容 231
13.2.1 方法的类型 232
13.2.2 样本方法 233
13.3 公共方法 234
13.4 操作 235
13.5 类型转换 236
13.6 多重外观 237
第14章 类型模型的模式—设计模板 240
14.1 实现关联 242
14.1.1 双向关联和单向关联 243
14.1.2 关联的接口 243
14.1.3 基础类型 245
14.1.4 实现一个单向关联 246
14.1.5 在两个方向上都使用指针的双向实现 246
14.1.6 在一个方向上使用指针的双向实现 247
14.1.7 使用关联对象的双向实现 248
14.1.8 双向实现的比较 248
14.1.9 派生映射 249
14.1.10 非集合映射 249
14.2 实现泛化 249
14.2.1 用继承实现 249
14.2.2 用多重继承组合类实现 250
14.2.3 用标志实现 250
14.2.4 用委托给一个隐藏类来实现 251
14.2.5 通过创建一个替换来实现 253
14.2.6 泛化的接口 254
14.2.7 实现hasType操作 255
14.3 对象创建 255
14.3.1 创建的接口 256
14.3.2 创建的实现 256
14.4 对象析构 256
14.4.1 析构的接口 257
14.4.2 析构的实现 257
14.5 入口点 258
14.5.1 查找对象的接口 259
14.5.2 查找操作的实现 260
14.5.3 使用类或者登记表对象 260
14.6 实现约束 260
14.7 其它技术的设计模板 261
第15章 关联模式 263
15.1 关联类型 264
15.2 带键值的映射 266
15.3 历史映射 268
第16章 后记 273
第三部分 附 录
附录A 技术和符号 277
附录B 模式列表 293
索引 301


网络资源搜索:https://github.com/holbrook/mybooks/blob/master/1.analysis/%E5%88%86%E6%9E%90%E6%A8%A1%E5%BC%8F%E5%8F%AF%E5%A4%8D%E7%94%A8%E7%9A%84%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B(%E4%B8%AD%E6%96%87%E7%89%88).pdf

软件过程模型-概述

软件工程–软件过程模型

软件过程是为了获得高质量软件所需要完成的一系列任务的框架,它规定了完成各项任务的工作步骤。通常使用生命周期模型简洁地描述软件过程。生命周期模型规定了把生命周期划分成哪些阶段及各个阶段的执行顺序,因此,也称为过程模型。常见的过程模型有瀑布模型、快速原型模型、增量模型、螺旋模型、喷泉模型等。

1.瀑布模型

这个特点有两重含义:

1.必须等前一阶段的工作完成之后,才能开始后一阶段的工作;

2.前一阶段的输出文档就是后一阶段的输入文档,因此,只有前一阶段的输出文档正确,后一阶段的工作才能获得正确的结果。

瀑布模型每个阶段都应坚持两个重要做法:

1.每个阶段都必须完成规定的文档,没有交出合格的文档就是没有完成该阶段的任务。完整、准确的合格文档是软件开发时期各类人员之间相互通信的媒介,也是运行时期对软件进行维护的重要依据。

2.每个阶段结束前都要对所完成的文档进行评审,以便迟早发现问题,改正错误。事实上越是早期阶段犯下的错误,暴露出来的时间就越晚,排除故障改正错误所需付出的代价也越高。因此,及时审查,是保证软件质量,降低软件成本的重要措施。

可以说瀑布模型是由文档驱动的。这个事实也是它的一个缺点,在可运行的软件产品交付给用户之前,用户只能通过文档来了解产品是什么样的。瀑布模型历史悠久、广为人知的,它的优势在于它是规范的、文档驱动的方法;这种模型的问题是,最终开发出的产品可能并不是用户真正需要的。

(1)传统的瀑布模型:

 

(2)实际的瀑布模型:

 

2.快速原型模型

所谓快速原型是快速建立起来的可以在计算机上运行的程序,它所能完成的功能往往是最终产品能完成的功能的一个子集。快速原型的本质是“快速”,开发人员应该尽可能快地建造出原型系统,以加速软件开发过程,节约软件开发成本。原型的用作是获知用户的真正需求,一旦需求确定了,原型系统将被抛弃。

快速原型模型正是为了克服瀑布模型的缺点而提出来的。它通过快速构建一个可在计算机上运行的原型系统,让用户试用原型系统并收集用户反馈意见的办法,获取用户的真实需求。

 

3.增量模型

增量模型也称为渐增模型,使用增量模型开发软件时,把软件产品作为一系列的增量构件来设计、编码、集成和测试。每个构件由多个相互作用的模块构成,并且能够完成特定的功能。使用增量模型时,第一个增量构件往往实现软件的基本需求,提供最核心的功能。

优点:

1.能在较短的时间内向用户提交可完成部分工作的产品。

2.逐步增加产品功能可以使用户有充裕的时间学习和适应新产品,从而减少一个全新的软件可能给客户组织带来的冲击。

增量模型具有可在软件开发的早期阶段使投资获得明显回报和较易维护的优点,但是,要求软件具有开放的结构是使用这种模型时固有的困难。

 

4.螺旋模型

螺旋模型的基本思想就是,使用原型及其他方法来尽量降低风险。理解这种模型的一个简便方法,是把它看作每个阶段之前都增加了风险分析过程的快速原型模型。

螺旋模型主要适用于内部开发的大规模软件项目。如果进行风险分析的费用接近整个项目的经费预算,则风险分析是不可行的。事实上项目越大,风险也越大,因此进行风险分析的必要性也越大。此外只有内部开发的项目,才能在风险过大时方便中止项目。

螺旋模型的主要优势在于,它是风险驱动,但是,这也可能是它的一个弱点。除非软件开发人员具有丰富的风险评估经验和这方面的专门知识,否则将出现真正的风:当项目实际上正在走向灾难时,开发人员可能还认为一切正常。

风险驱动的螺旋模型适用于内部开发的大型软件项目,但是,只有在开发人员具有风险分析和排除风险的经验及专门知识时,使用这种模型才会获得成功。

(1)简化的螺旋模型

 

(2)完整的螺旋模型

4.喷泉模型

喷泉模型对软件复用和生存周期中多项开发活动的集成提供了支持,以面向对象的软件开发方法为基础,它适合面向对象的开发方法。它克服了瀑布模型不支持软件重用和多项开发活动集成的局限性。喷泉模型使开发过程具有迭代性和无间隙性。系统某个部分常常重复工作多次,相关功能在每次迭代中随之加入演化的系统。无间隙是指在分析、设计和实现等开发活动之间不存在明显的边界。

 

按照在软件生命周期过程中应完成的任务的性质,在概念上可以把软件生命周期划分成定义、可行性研究、需求分析、总体设计、详细设计、编码和单元测试、综合测试以及运行维护等8个阶段。实际从事软件开发工作时,软件规模、种类、开发环境及使用的技术方法等因素,都影响各阶段的划分。

软件过程是为了获得高质量的软件产品所需要完成的一系列任务的框架,它规定了完成各项任务的工作步骤。由于没有适用所有软件项目的任务集合,科学、有效的软件过程应该定义一组适合所承担的项目特点的任务集合。通常使用软件过程模型简洁地描述软件过程,它规定了把软件生命周期划分成的阶段及各个阶段的顺序。

来自:http://www.cnblogs.com/houleixx/archive/2009/10/20/software-engineering-process-model.html

软件架构模式-附录A和关于作者

附录A

模式分析总结

图A-1 总结了在这个报告中,对于架构模式的每部分进行的模式分析所产生的影响。这个总结帮助你确定哪些模式可能是最适合你的情况。例如,如果你的架构模式重点是可伸缩性,你可以在这个图表看看事件驱动模式,microservices模式,和基于空间模式,这些对于你来说可能是很好的架构模式的选择。同样的,如果你的程序注重的是分层架构模式,你可以参考图看到部署、性能和可伸缩性的在你的架构中所存在的风险。

同时这个图表将指导你选择正确的模式,因为在选择一种架构模式的时候,有更多的因素需要考虑。你必须分析你的环境的各个方面,包括基础设施的支持,开发人员技能,项目预算,项目最后期限,和应用程序大小等等。选择正确的架构模式是至关重要的,因为一旦一个架构被确定就很难改变。

关于作者

Mark•Richards是一位有丰富经验的软件架构师,他参与架构、设计和实施microservices体系结构、面向服务的体系结构和在J2EE中的分布式系统和其他技术。自1983年以来,他一直从事软件行业,在应用、继承和企业架构方面有大量的经验和专业知识。

Mark在1999到2003年间担任新英格兰Java用户组的主席。他是许多技术书籍和视频的作者,包括软件架构基础(O‘Reilly视频)、企业消息传递(O’Reilly视频),《Java消息服务,第二版》(O’Reilly)和《软件架构师应该知道的97件事》(O’Reilly)的特约作者。Mark拥有一个计算机科学硕士学位并且多次获得IBM、Sun、开放集团和BEA等颁发的架构师和开发人员认证。

他是Fluff Just Stuff(NFJS)研讨会系列(一个不定期会议)议长,并且有过上百次的在世界各地公益会议和用户组上围绕技术主题的演讲经验)。Mark不工作的时候经常会到白色山脉或阿帕拉契山径徒步旅行。

软件架构模式-第五章 基于空间的架构

第五章 基于空间的架构


大多数基于网站的商务应用都遵循相同的请求流程:一个请求从浏览器发到web服务器,然后到应用服务器,然后到数据库服务器。虽然这个模式在用户数不大的时候工作良好,但随着用户负载的增加,瓶颈会开始出现,首先出现在web服务器层,然后应用服务器层,最后数据库服务器层。通常的解决办法就是向外扩展,也就是增加服务器数量。这个方法相对来说简单和廉价,并能够解决问题。然而,对于大多数高访问量的情况,它只不过是把web服务器的问题移到了应用服务器。而扩展应用服务器会更复杂,而且成本更高,并且又只是把问题移动到了数据库服务器,那会更复杂,更贵。就算你能扩展数据库服务器,你最终会陷入一个金字塔式的情形,在金字塔最下面是web服务器,它会出现最多的问题,但也最好伸缩。金字塔顶部是数据库服务器,问题不多,但最难伸缩。

在一个高并发大容量的应用中,数据库通常是决定应用能够支持多少用户同时在线的关键因素。虽然各种缓存技术和数据库伸缩产品都在帮助解决这个问题,但数据库难以伸缩的现实并没有改变。

基于空间的架构模型是专门为了解决伸缩性和并发问题而设计的。它对于用户数量不可预测且数量级经常变化的情况同样适用。在架构级别来解决这个伸缩性问题通常是比增加服务器数量或者提高缓存技术更好的解决办法。

模型介绍

基于空间的模型(有时也称为云架构模型)旨在减少限制应用伸缩的因素。模型的名字来源于分布式共享内存中的 tuple space(数组空间)概念。高伸缩性是通过去除中心数据库的限制,并使用从内存中复制的数据框架来获得的。保存在内存的应用数据被复制给所有运行的进程。进程可以动态的随着用户数量增减而启动或结束,以此来解决伸缩性问题。这样因为没有了中心数据库,数据库瓶颈就此解决,此后可以近乎无限制的扩展了。

大多数使用这个模型的应用都是标准的网站,它们接受来自浏览器的请求并进行相关操作。竞价拍卖网站是一个很好的例子 ( 12306更是一个典型的示例 )。网站不停的接受来自浏览器的报价。应用收到对某一商品的报价,记录下报价和时间,并且更新对该商品的报价,将信息返回给浏览器。

这个架构中有两个主要的模块:处理单元 和 虚拟化中间件。下图展示了这个架构和里面的主要模块。

处理单元包含了应用模块(或者部分的应用模块)。具体来说就是包含了web组件以及后台业务逻辑。处理单元的内容根据应用的类型而异——小型的web应用可能会部署到单一的处理单元,而大型一些的应用会将应用的不同功能模块部署到不同的处理单元中。典型的处理单元包括应用模块,以及保存在内存的数据框架和为应用失败时准备的异步数据持久化模块。它还包括复制引擎,使得虚拟化中间件可以将处理单元修改的数据复制到其他活动的处理单元。

虚拟化中间件负责保护自身以及通信。它包含用于数据同步和处理请求的模块,以及通信框架,数据框架,处理框架和部署管理器。这些在下文中即将介绍的部分,可以自定义编写或者购买第三方产品来实现。

组件间合作

基于空间的架构的魔力就在虚拟化中间件,以及各个处理单元中的内存中数据框架。下图展示了包含着应用模块、内存中数据框架、处理异步数据恢复的组件和复制引擎的处理单元架构。

虚拟化中间件本质上是架构的控制器,它管理请求,会话,数据复制,分布式的请求处理和处理单元的部署。虚拟化中间件有四个架构组件:通信框架,数据框架,处理框架和部署管理器。

通信框架

通信框架管理输入请求和会话信息。当有请求进入虚拟化中间件,通信框架就决定有哪个处理单元可用,并将请求传递给这个处理单元。通信框架的复杂程度可以从简单的round robin算法到更复杂的用于监控哪个请求正在被哪个处理单元处理的next-available算法。

数据框架

数据框架可能是这个架构中最重要和关键的组件。它与各个处理单元的数据复制引擎交互,在数据更新时来管理数据复制功能。由于通信框架可以将请求传递给任何可用的处理单元,所以每个处理单元包含完全一样的内存中数据就很关键。下图展示处理单元间如何同步数据复制,实际中是通过非常迅速的并行的异步复制来完成的,通常在微秒级。

处理框架

处理框架,就像下图所示,是虚拟化中间件中一个可选组件,负责管理在有多个处理单元时的分布式请求处理,每个处理单元可能只负责应用中的某个特定功能。如果请求需要处理单元间合作(比如,一个订单处理单元和顾客处理单元),此时处理框架就充当处理单元见数据传递的媒介。

部署管理器

部署管理器根据负载情况管理处理单元的动态启动和关闭。它持续检测请求所需时间和在线用户量,在负载增加时启动新的处理单元,在负载下降时关闭处理单元。它是实现可变伸缩性需求的关键。

其他考虑

基于空间的架构是一个复杂和实现起来相对昂贵的框架。对于有可变伸缩性需求的小型web应用是很好的选择,然而,对于拥有大量数据操作的传统大规模关系型数据库应用,并不那么适用。

虽然基于空间的架构模型不需要集中式的数据储存,但通常还是需要这样一个,来进行初始化内存中数据框架,和异步的更新各处理单元的数据。通常也会创建一个单独的分区,来从隔离常用的断电就消失的数据和不常用的数据,这样减少处理单元之间对对方内存数据的依赖。

值得注意的是,虽然这个架构的另一个名字是云架构,处理单元(以及虚拟化中间件)都没有放在云端服务或者PaaS上。他们同样可以简单的放在本地服务器,这也是为什么我更倾向叫它“基于空间的架构”。

从产品实现的角度讲,这个架构中的很多组件都可以从第三方获得,比如GemFire, JavaSpaces, GigaSpaces,IBM Object Grid,nCache,和 Oracle Coherence。由于架构的实现根据工程的预算和需求而异,所以作为架构师,你应该在实现或选购第三方产品前首先明确你的目标和需求。

架构分析

下面的表格是这个架构的特征分析和评分。每个特征的评分是基于一个典型的架构实现来给出的。要知道这个模式相对别的模式的对比,请参见最后的附录A。

综合能力

评分:高 分析:综合能力是对环境变化做出快速反应的能力。因为处理单元(应用的部署实例)可以快速的启动和关闭,整个应用可以根据用户量和负载做出反应。使用这个架构通常在应对代码变化上,由于较小的应用规模和组件间相互依赖,也会反映良好。

易于部署

评分:高 分析:虽然基于空间的架构通常没有解耦合并且功能分布,但他们是动态的,也是成熟的基于云的工具,允许应用轻松的部署到服务器。

可测试性

评分:低 分析:测试高用户负载既昂贵又耗时,所以在测试架构的可伸缩性方面很困难

性能

评分:高 分析:通过内存中数据存取和架构中的缓存机制可获得高性能

伸缩性

评分:高 分析:高伸缩性是源于几乎不依赖集中式的数据库,从而去除了这个限制伸缩性的瓶颈。

易于开发

评分:低 分析:主要是因为难以熟悉这个架构开发所需得工具和第三方产品,因此使用该架构需要较大的学习成本。而且,开发过程中还需要特别注意不要影响到性能和可伸缩性。

软件架构模式-第四章 微服务架构

第四章 微服务架构


微服务架构模式作为替代单体应用和面向服务架构的一个可行的选择,在业内迅速取得进展。由于这个架构模式仍然在不断的发展中,在业界存在很多困惑——这种模式是关于什么的?它是如何实现的?本报告的这部分将为你提供关键概念和必要的基础知识来理解这一重要架构模式的好处(和取舍),以此来判断这种架构是否适合你的应用。

模式描述

不管你选择哪种拓扑或实现风格,有几种常见的核心概念适用于一般架构模式。第一个概念是单独部署单元。如图4-1所示,微服务架构的每个组件都作为一个独立单元进行部署,让每个单元可以通过有效、简化的传输管道进行通信,同时它还有很强的扩展性,应用和组件之间高度解耦,使得部署更为简单。

也许要理解这种模式,最重要的概念就是服务组件(service component)。不要考虑微服务架构内部的服务,而最好是考虑服务组件,从粒度上讲它可以小到单一的模块,或者大至一个应用程序。服务组件包含一个或多个模块(如Java类),这些模块可以提供一个单一功能(如,为特定的城市或城镇提供天气情况),或也可以作为一个大型商业应用的一个独立部分(如,股票交易布局或测定汽车保险的费率)。在微服务架构中,正确设计服务组件的粒度是一个很大的挑战。在接下来的服务组件部分对这一挑战进行了详细的讨论。

微服务架构模式的另一个关键概念是它是一个分布式的架构,这意味着架构内部的所有组件之间是完全解耦的,并通过某种远程访问协议(如, JMS, AMQP, REST, SOAP, RMI等)进行访问。这种架构的分布式特性是它实现一些优越的可扩展性和部署特性的关键所在。

微服务架构另一个令人兴奋的特性是它是由其他常见架构模式存在的问题演化来的,而不是作为一个解决方案被创造出来等待问题出现。微服务架构的演化有两个主要来源:使用分层架构模式的单体应用和使用面向服务架构的分布式应用。

由单体应用( 一个应用就是一个整体 )到微服务的发展过程主要是由持续交付开发促成的。从开发到生产的持续部署管道概念,简化了应用程序的部署。单体应用通常是由紧耦合的组件组成,这些组件同时又是另一个单一可部署单元的一部分,这使得它繁琐,难以改变、测试和部署应用(因此常见的“月度部署”周期出现并通常发生在大型IT商店项目)。这些因素通常会导致应用变得脆弱以至于每次有一点新功能部署后应用就不能运行。微服务架构模式通过将应用分隔成多个可部署的单元(服务组件)的方法来解决这一问题,这些服务组件可以独立于其他服务组件进行单独开发、测试和部署。

另一个导致微服务架构模式产生的演化过程是由面向服务架构模式(SOA)应用程序存在的问题引起的。虽然SOA模式非常强大,提供了无与伦比的抽象级别、异构连接、服务编排,并保证通过IT能力调整业务目标,但它仍然是复杂的,昂贵的,普遍存在,它很难理解和实现,对大多数应用程序来说过犹不及。微服务架构通过简化服务概念,消除编排需求、简化服务组件连接和访问来解决复杂度问题。

模式拓扑

虽然有很多方法来实现微服务架构模式,但三个主要的拓扑结构脱颖而出,最常见和流行的有:基于REST API的拓扑结构,基于REST的应用拓扑结构和集中式消息拓扑结构。

基于REST的API拓扑适用于网站,通过某些API对外提供小型的、自包含的服务。这种拓扑结构,如图4 – 2所示,由粒度非常细的服务组件(因此得名微服务)组成,这些服务组件包含一个或两个模块并独立于其他服务来执行特定业务功能。在这种拓结构扑中,这些细粒度的服务组件通常被REST-based的接口访问,而这个接口是通过一个单独部署的web API层实现的。此种拓扑的例子包含一些常见的专用的、基于云的RESTful web service,大型网站像Yahoo, Google, and Amazon都在使用。

基于REST的应用拓扑结构与基于REST API的不同,它通过传统的基于web的或胖客户端业务应用来接收客户端请求,而不是通过一个简单的API层。如图4-3所示,应用的用户接口层(user interface layer)是一个web应用,可以通过简单的REST-based接口访问单独部署的服务组件(业务功能)。该拓扑结构中的服务组件与API-REST-based拓扑结构中的不同,这些服务组件往往会更大、粒度更粗、代表整个业务应用程序的一小部分,而不是细粒度的、单一操作的服务。这种拓扑结构常见于中小型企业等复程度相对较低的应用程序。

微服务架构模式中另一个常见的方法是集中式消息拓扑。该拓扑(如图4-4所示)与前面提到的基于REST的应用拓扑类似,不同的是,application REST- based拓扑结构使用REST进行远程访问,而该拓扑结构则使用一个轻量级的集中式消息代理(如,ActiveMQ, HornetQ等等)。不要将该拓扑与面向服务架构模式混淆或将其当做SOA简化版(“SOA-Lite”),这点是极其重要的。该拓扑中的轻量级消息代理(Lightweight Message Broker)不执行任何编排,转换,或复杂的路由;相反,它只是一个轻量级访问远程服务组件的传输工具。

集中式消息拓扑结构通常应用在较大的业务应用程序中,或对于某些对传输层到用户接口层或者到服务组件层有较复杂的控制逻辑的应用程序中。该拓扑较之先前讨论的简单基于REST的拓扑结构,其好处是有先进的排队机制、异步消息传递、监控、错误处理和更好的负载均衡和可扩展性。与集中式代理相关的单点故障和架构瓶颈问题已通过代理集群和代理联盟(将一个代理实例为分多个代理实例,把基于系统功能区域的吞吐量负载划分开处理)解决。

避免依赖和编排

微服务架构模式的主要挑战之一就是决定服务组件的粒度级别。如果服务组件粒度过粗,那你可能不会意识到这个架构模式带来的好处(部署、可扩展性、可测试性和松耦合),然而,服务组件粒度过细将导致服务编制要求,这会很快导致将微服务架构模式变成一个复杂、容易混淆、代价昂贵并易于出错的重量级面向服务架构。

如果你发现需要从应用内部的用户接口或API层编排服务组件,那么很有可能你服务组件的粒度太细了。如果你发现你需要在服务组件之间执行服务间通信来处理单个请求,那么很有可能要么是你服务组件的粒度太细了,要么是没有从业务功能角度正确划分服务组件。

服务间通信,可能导致组件之间产生耦合,但可以通过共享数据库进行处理。例如,若一个服务组件处理网络订单而需要用户信息时,它可以去数据库检索必要的数据,而不是调用客户服务组件的功能。

共享数据库可以处理信息需求,但是共享功能呢?如果一个服务组件需要的功能包含在另一个服务组件内,或是一个公共的功能,那么有时你可以将服务组件的共享功能复制一份(因此违反了DRY规则:don’t repeat yourself)。为了保持服务组件独立和部署分离,微服务架构模式实现中会存在一小部分由重复的业务逻辑而造成的冗余,这在大多数业务应用程序中是一个相当常见的问题。小工具类可能属于这一类重复的代码。

如果你发现就算不考虑服务组件粒度的级别,你仍不能避免服务组件编排,这是一个好迹象,可能此架构模式不适用于你的应用。由于这种模式的分布式特性,很难维护服务组件之间的单一工作事务单元。这种做法需要某种事务补偿框架回滚事务,这对此相对简单而优雅的架构模式来说,显著增加了复杂性。

注意事项

微服务架构模式解决了很多单体应用和面向服务架构应用存在的问题。由于主要应用组件被分成更小的,单独部署单元,使用微服务架构模式构建的应用程序通常更健壮,并提供更好的可扩展性,支持持续交付也更容易。

该模式的另一个优点是,它提供了实时生产部署能力,从而大大减少了传统的月度或周末“大爆炸”生产部署的需求。因为变化通常被隔离成特定的服务组件,只有变化的服务组件才需要部署。如果你的服务组件只有一个实例,你可以在用户界面程序编写专门的代码用于检测一个活跃的热部署,一旦检测到就将用户重定向到一个错误页面或等待页面。你也可以在实时部署期间,将服务组件的多个实例进行交换,允许应用程序在部署期间保持持续可用性(分层架构模式很难做到这点)。

最后一个要重视的考虑是,由于微服务架构模式是分布式的架构,他与事件驱动架构模式具有一些共同的复杂的问题,包括约定的创建、维护,和管理,远程系统的可用性,远程访问身份验证和授权。

模式分析

下面这个表中包含了微服务架构模式的特点分析和评级,每个特性的评级是基于自然趋势,基于典型模式实现的能力特性,以及该模式是以什么闻名的。本报告中该模式与其他模式的并排比较,请参考报告最后的附件A。

整体灵活性

评级:高 分析:整体的灵活性是能够快速响应不断变化的环境。由于单独部署单元的概念,变化通常被隔离成单独的服务组件,使得部署变得快而简单。同时,使用这种模式构建的应用往往是松耦合的,也有助于促进改变。

易于部署

评级:高 分析:整体来讲,由于该模式的解耦特性和事件处理组件使得部署变得相对简单。broker拓扑往往比mediator拓扑更易于部署,主要是因为event-mediator组件与事件处理器是紧耦合的,事件处理器组件有一个变化可能导致event mediator跟着变化,有任何变化两者都需要部署。

可测试性

评级:高 分析:由于业务功能被分离成独立的应用模块,可以在局部范围内进行测试,这样测试工作就更有针对性。对一个特定的服务组件进行回归测试比对整个单体应用程序进行回归测试更简单、更可行。而且,由于这种模式的服务组件是松散耦合的,从开发角度来看,由一个变化导致应用其他部分也跟着变化的几率很小,并能减小由于一个微小的变化而不得不对整个应用程序进行测试的负担。

性能

评级:低 分析:虽然你可以从实现该模式来创建应用程序并可以很好的运行,整体来说,由于微服务架构模式的分布式特性,并不适用于高性能的应用程序。

伸缩性

评级:高 分析:由于应用程序被分为单独的部署单元,每个服务组件可以单独扩展,并允许对应用程序进行扩展调整。例如,股票交易的管理员功能区域可能不需要扩展,因为使用该功能的用户很少,但是交易布局服务组件可能需要扩展,因为大多数交易应用程序需要具备处理高吞吐量的功能。

易于开发

评级:高 分析:由于功能被分隔成不同的服务组件,由于开发范围更小且被隔离,开发变得更简单。程序员在一个服务组件做出一个变化影响其他服务组件的几率是很小的,从而减少开发人员或开发团队之间的协调。

软件架构模式-第三章 微内核架构

第三章 微内核架构


微内核架构模式(也称为插件化应用架构)对于基于产品的应用程序来说是一个很自然的选择。基于产品的应用是指一个经过打包的、可以通过版本下载的一个典型的第三方产品。然而,很多公司也会开发和发布他们的内部商业软件,完整的版本号、发布日志和可插拔的新特性,这些就非常符合微内核架构的思想。微内核架构模式可以通过插件的形式添加额外的特性到核心系统中,这提供了很好的扩展性,也使得新特性与核心系统隔离开来。( 译者注: 比如,著名的Eclipse IDE就是基于插件化开发的,eclipse核心更像是一个微内核,或者我们可把它叫做开放平台,其他的功能通过安装插件的形式添加到eclipse中。 )

模式描述

微内核架构主要需要考虑两个方面: 核心系统和插件模块。应用逻辑被划分为独立的插件模块和核心系统,这样就提供良好的可扩展性、灵活性,应用的新特性和自定义处理逻辑也会被隔离。图3-1演示了基本的微内核架构。

微内核架构的核心系统一般情况下只包含一个能够使系统运作起来的最小化模块。很多操作系统的实现就是使用微内核架构,因此这也是该架构名字的由来。从商业应用的角度看,核心系统通常是为特定的使用场景、规则、或者复杂条件处理定义了通用的业务逻辑,而插件模块根据这些规则实现了具体的业务逻辑。

插件模块是一个包含专业处理、额外特性的独立组件,自定义代码意味着增加或者扩展核心系统以达到产生附加的业务逻辑的能力。通常,插件模块之间应该是没有任何依赖性的,但是你也可以设计一个需要依赖另一个插件的插件。但无论如何,使得插件之间可以通信的同时避免插件之间产生依赖又是一个特别重要的问题。

核心系统需要了解插件模块的可用性以及如何获取到它们。一个通用的实现方法是通过一组插件注册表。这个插件注册表含有每个插件模块的信息,包括它的名字、数据规约和远程访问协议(取决于插件如何与核心系统建立连接)。例如,一个税务软件的用于标识高风险的税务审计插件可能会有一个含有插件名(比如AuditChecker)的注册入口,数据规约(输入数据、输出数据)和规约格式( 比如xml )。如果这个插件是通过SOAP服务访问,那么它可能会包含一个WSDL (Web Services Definition Language).

插件模块可以通过多种方式连接到核心系统,包括OSGi ( open service gateway initiative )、消息机制、web服务或者直接点对点的绑定 ( 比如对象实例化,即依赖注入 )。你使用的连接类型取决于你构建的应用类型和你的特殊需求(比如单机部署还是分布式部署)。微内核架构本身没有指定任何的实现方式,唯一的规定就是插件模块之间不要产生依赖。

插件和核心系统的通信规范包含标准规范和自定义规范。自定义规范典型的使用场景是插件组件是被第三方构建的。在这种情况下,通常是在第三方插件规约和你的标准规范创建一个Adapter来使核心系统根本不需要知道每个插件的具体细节。当创建标准规范 ( 通常是通过XML或者Java Map )时,从一开始就创建一个版本策略是非常重要的。

架构示例

也许微内核架构的最好示例就是大家熟知的Eclipse IDE了。下载最基本的Eclipse后,它只能提供一个编辑器。然后,一旦你开始添加插件,它就变成一个高度可定制化和非常有用的产品(译者注 : 更多内容大家可以参考 开源软件架构 卷1:第6章 Eclipse之一 )。浏览器是另一个使用微内核架构的产品示例,它由一个查看器和其他扩展的插件组成。

基于微内核架构的示例数不胜数,但是大型的商业应用呢?微内核应用架构也适用于这些情形。为了阐述这个观点,让我们来看看另一个保险公司的示例,但是这次的示例会涉及保险赔偿处理。

赔偿处理是一个非常复杂的过程。每个州都有不同的关于保险赔偿的规则和条文。例如一些州允许在你的挡风玻璃被石头砸碎时免费进行替换,但是一些州则不是这样。因为大家的标准都不一样,因此赔偿标准几乎可以是无限的。

有很多保险赔偿应用运用大型和复杂的规则处理引擎来处理不同规则带来的复杂性。然而,可能会因为某条规则的改变而引起其他规则的改变而使得这些规则处理引擎变成一个大泥球,或者使简单需求变更会需要一个很大的分析师、工程师、测试工程师来进行处理。使用微内核架构能够很好的解决这个问题,核心系统只知道根据赔偿规则处理,但这个赔偿规则是抽象的,系统将赔偿规则作为一个插件规范,具体的规则有对应的实现,然后注入到系统中即可。

图3-2中的一堆文件夹代表了赔偿处理核心系统。它包含一些处理保险赔偿的基本业务逻辑。每一个插件模块包含每个州的具体赔偿规则。在这个例子中,插件模块通过自定义源代码实现或者分离规则引起实例。不管具体实现如何,关键就在于赔偿规则和处理都从核心系统中分离,而这些规则和处理过程都可以被动态地添加、移除,而这些改变对于核心系统和其他插件只有很小的影响或者根本不产生影响。

注意事项

对于微内核架构来说一个很重要的一点就是它能够被嵌入或者说作为另一种架构的一部分。例如,如果这个架构解决的是一个你应用中易变领域的特定的问题 ( 译者注 : 即插件化能够解决你应用中的某个特定模块的架构问题 ),你可能会发现你不能在整个应用中使用这种架构。在这种情况下,你可以将微内核架构嵌入到另一个架构模式中 ( 比如分层架构 )。同样的,在上一章节中描述的事件驱动架构中的事件处理器组件也可以使用微内核架构。

微内核架构对渐进式设计和增量开发提供了非常好的支持。你可以先构建一个单纯的核心系统,随着应用的演进,系统会逐渐添加越来越多的特性和功能,而这并不会引起核心系统的重大变化。

对基于产品的应用来说,微内核架构应该是你的第一选择。特别是那些你会在后续开发中发布附加特性和控制哪些用户能够获取哪些特性的应用。如果你在后续开发中发现这个架构不能满足你的需求了,你能够根据你的特殊需求将你的应用重构为另一个更好的架构。

模式分析

下面的表格中包含了微内核架构每个特性的评级和分析。以微内核架构的最经典的实现方式的自然趋势为依据对每个特性进行评级。关于微内核架构与其他模式的相关性比较请参考附录A。

整体灵活性

评级 : 高 分析 : 整体灵活性是指能够快速适应不断变化的环境的能力。通过插件模块的松耦合实现,可以将变化隔离起来,并且快速满足需求。通常,微内核架构的核心系统很快趋于稳定,这样系统就变得很健壮,随着时间的推移它也不会发生多大改变。

易于部署

评级 : 高 分析 : 根据实现方式,插件模块能够在运行时被动态地添加到核心系统中 ( 比如,热部署 ),把停机时间减到最小。

可测试性

评级 : 高 分析 : 插件模块能够被独立的测试,能够非常简单地被核心系统模拟出来进行演示,或者在对核心系统很小影响甚至没有影响的情况下对一个特定的特性进行原型展示。

性能

评级 : 高 分析 : 使用微内核架构不会自然而然地使你的应用变得高性能。通常,很多使用微内核架构的应用运行得很好,因为你能定制和简化应用程序,使它只包含那些你需要的功能模块。JBoss应用服务器就是这方面的优秀示例: 依赖于它的插件化架构,你可以只加载你需要的功能模块,移除那些消耗资源但没有使用的功能特性,比如远程访问,消息传递,消耗内存、CPU的缓存,以及线程,从而减小应用服务器的资源消耗。

伸缩性

评级 : 低 分析 : 因为微内核架构的实现是基于产品的,它通常都比较小。它们以独立单元的形式实现,因此没有太高的伸缩性。此时,伸缩性就取决于你的插件模块,有时你可以在插件级别上提供可伸缩性,但是总的来说这个架构并不是以构建高度伸缩性的应用而著称的。

易于开发

评级 : 低 分析 : 微内核架构需要考虑设计和规约管理,使它不会很难实现。规约的版本控制,内部的插件注册,插件粒度,丰富的插件连接的方式等是涉及到这个架构模式实现复杂度的重要因素。