在首席架构师眼里,架构的本质是……

在首席架构师眼里,架构的本质是……

浏览次数:117次  36 氪  2016年03月03日  字号:   

编者按:本文作者王庆友,前 1号店首席架构师,先后就职于 ebay、腾讯、1号店、找钢网,精通电商业务,擅长复杂系统业务建模和架构分析,目前在中国 B2B 第一电商公司找钢网担任首席架构师,微信号Brucetwins,欢迎一起聊架构。本文由 36 氪经授权转载自微信公众号“聊聊架构”(ID:archtime)。

目前讨论架构实操(术)的文章较多,讨论架构理念(道)的较少,本文基于作者在大型电商系统架构方面的一些实践和思考,和大家聊聊架构理念性的东西,希望能够抛砖引玉,推进大家对架构的认识。

什么是道,什么是术?道是事物发展的本质规律,术是事物发展的具体途径。规律只有一个,途径很多,条条大路通罗马,罗马是道,大路是术。道为本,术为途,如果事先知道罗马在哪里,那么遍地是路,路路相通。架构也是如此,如果能领悟架构的本质,就不会拘泥于现有的实践和理论框框,而以最直接的方式解决问题,无招胜有招。本文的内容包括架构的本质、架构的服务对象、架构师能力模型 、架构境界等。

架构的本质

任何系统,自然情况下,都是从有序到无序,这是有科学依据的, 按照热力学第二定律,自然界的一切自发过程都有方向性,一个孤立系统会由有序变为无序,即它的熵会不断增加,最终寂灭。但生物可以通过和外界交互,主动进行新陈代谢,制造 “负熵” 来保证自身有序,继续生存。

同样,一个软件系统随着功能越来越多,调用量急剧增长,整个系统逐渐碎片化,越来越无序,最终无法维护和扩展,所以系统在一段时间的野蛮生长后,也需要及时干预,避免越来越无序。

架构的本质就是对系统进行有序化重构,不断减少系统的 “熵”,使系统不断进化。

那架构是如何实现无序到有序的呢? 基本的手段就是分和合,先把系统打散,然后重新组合。

在首席架构师眼里,架构的本质是……分的过程是把系统拆分为各个子系统 / 模块 / 组件,拆的时候,首先要解决每个组件的定位问题,然后才能划分彼此的边界,实现合理的拆分。合就是根据最终要求,把各个分离的组件有机整合在一起,相对来说,第一步的拆分更难。

拆分的结果使开发人员能够做到业务聚焦、技能聚焦,实现开发敏捷,合的结果是系统变得柔性,可以因需而变,实现业务敏捷。

举个例子,在 Web 1.0 时代,一个 ASP 或 JSP 页面里,HTML 和脚本代码混在一起,此时脚本代码越多,系统越混乱(即熵增加),最终连开发者自己都无法理解。此时就需要对系统重新架构,办法是引入 view helper 模式,分离 HTML 和脚本,HTML 成为 view,脚本成为帮助类。然后再简单整合在一起。通过重新分和合,整个系统层次清晰,职责明确,系统的无序度降低,容易扩展。同时不同技能的开发人员,如 UED 和程序员,可以负责不同部分,有效提高开发效率。

好的架构就像一篇优美的散文,形散神不散,表面看无序,实则高度有序。

架构分类和服务对象

架构一般可分业务架构、应用架构、技术架构,那么它们分别解决什么问题,服务于谁呢? 我们首先看一个系统落地过程:

在首席架构师眼里,架构的本质是……对于负责开发的人来说,怕的是业务太复杂,代码逻辑太乱,超出他能理解的范畴,系统无法维护。因此开发的需求是系统整体概念清晰,容易理解,方便扩展。

对于负责运行的机器来说,怕的是业务并发量太大,系统核心资源不够用(如数据库连接)。它希望在业务量增加时,系统能够支持水平扩展,支持硬件容错(如避免单点故障)。

开发的痛点主要由业务架构和应用架构解决,业务架构从概念层面帮助开发理解系统(动态的包括业务流程 / 节点 / 输入输出,静态的包括业务域 / 业务模块 / 单据模型)。

应用架构从逻辑层面帮助开发落地系统(应用种类 / 应用形式 / 数据交互关系 / 交互方式等),整个系统逻辑上容易理解,最近大家谈的比较多的 SOA 即属于应用架构的范畴。

机器的痛点主要由技术架构解决,如技术平台选型(操作系统 / 中间件 / 设备等),部署上希望支持多机房,水平扩展,无单点等。

强调一下,系统是人的系统,架构首先是为人服务的,业务概念清晰、应用逻辑合理、人好理解是第一位的(即系统有序度高)。现在大家讨论更多的是技术架构,如高并发设计,分布式事务处理等,只是因为这个不需要业务上下文背景,比较好相互沟通。具体架构设计时,首先要关注业务架构和应用架构,这个架构新手要特别注意。

架构师能力模型

架构师只做分和合的事情,但综合能力要求很高,要求内外兼修,下得厨房,上得厅堂,下图通过典型的架构方式介绍一个架构师的能力要求:

在首席架构师眼里,架构的本质是……在此基础上,架构师要有技术的广度(多领域知识),又有深度(技术前瞻),对主流公司的系统设计非常了解,知道优劣长短,碰到实际问题,很快有多种方案可供评估。

抽象思维是架构师最重要的能力,架构师要善于把实物概念化并归类。比如面对一个大型的 B2C 网站,能够迅速抽象为采购->运营->前台搜索->下单->履单这几大块,对系统分而治之,庖丁解牛,早已目无全牛。

抽象思维是往高层次的总结升华,由实到虚;而透过问题看本质则是由虚到实,往深层次地挖掘。比如看到一段 java 代码,知道它在 JVM 如何执行;一个跨网络调用,知道数据是如何通过各种介质到达目标 (操作系统内核 / 网卡端口 / 电磁介质等)。透过问题看本质使架构师能够敏锐地发现底层之真实,系统性端到端地思考问题,识别木桶的短板并解决之。

能落地的架构才是好架构,良好的沟通能力确保各方对架构达成共识,愿意采取行动;良好的平衡取舍能力确保架构在现有资源约束下是最合理的,理想最终照进现实。

总结下,架构师的能力要求包括:

  1. 兼具技术的广度(多领域知识)和深度(技术前瞻)

  2. 兼具思维的高度(抽象思维)和深度(问题到本质)

  3. 兼具感性(沟通)和理性(平衡)

架构境界

架构师从境界上由浅到深可以分为四层:第一看山不是山,第二看山是山,第三看山不是山,第四看山是山。

在首席架构师眼里,架构的本质是……刚接手项目时,对业务不了解,时时被业务方冒出的术语弄得一愣一愣的,如果把现有问题比作山,则是横看成岭侧成峰,根本摸不透,此时看山不是山。

经过业务梳理和对系统深入了解,可以设计出一个屌丝的方案,把各个系统串起来,解决当前的问题,对当前这个山能够看清楚全貌,此时能够做到看山是山。

通过进一步抽象,发现问题的本质,原来这个问题是共性的,后续还会有很多类似问题。设计上进行总结和升华,得出一个通用的方案,不光能解决当前的问题,还可以解决潜在的问题。此时看到的已经是问题本质,看山不是山。

最后回到问题本身,去除过度的抽象,给出的设计简洁明了,增之一分嫌肥,减之一分嫌瘦,既解决当前问题,又保留最基本的扩展,此时问题还是那个问题,山还是那个山。

第一境界给不出合适方案,不表。

第二境界的方案只解决表面问题,往往设计不够,碰到其它类似问题或者问题稍微变形,系统需要重新做。

第三境界的方案往往过度设计,太追求通用化会创造出过多抽象,生造概念,理解和实现均困难,此时系统的无序度反而增加,过犹不及。

第四境界的方案,在了解问题本质的基础上,同时考虑现状,评估未来,不多做,不少做。

佛教讲空和色,色即事物现象,空即事物本质,从这个意义上说,第一重境界无色无空,第二重境界过色,第三重境界过空,第四重境界站在色和空之间,既色又空,不执着于当前,不虚无于未来。

不空不色,既空既色,道法自然,本性如来,架构之髓也。


浅谈软件架构设计

浏览次数:168次  开源技术社区  2016年03月03日  字号:   

软件架构介绍

架构定义:

软件架构不仅仅注重软件本身的结构和行为, 还注重其他特性:使用, 功能性, 性能, 弹性, 重用, 可理解性, 经济和技术的限制及权衡。

软件架构的目标:

  • 可延伸性(Extensible)。在新技术出现的时候,一个软件系统应当允许导入新技术,从而对现有系统进行功能和性能的扩展;
  • 可维护性(Maintainable)。软件系统的维护包括两方面:1、排除现有的错误;2、将新的软件需求反映到现有系统中去,一个易于维护的系统可以有效地降低技术支持的花费。
  • 客户体验(Customer Experience)。软件系统必须易于使用。
  • 市场时机(Time to Market)。软件用户

软件逻辑架构:

逻辑架构:软件系统中元件之间的关系,比如用户界面,数据库,外部系统接口,商业逻辑元件,等等

软件系统的逻辑架构图:

ceng.jpg

软件物理架构:

物理架构:软件元件是怎样放到硬件上的?

软件系统的物理架构图

wl.png

物联网架构的演变

单机(One Box)
简单web应用:

  • 访问量小
  • Apache/PHP/MySQL 在同一主机上
  • 瓶颈

1、通常先出现在数据库,然后才是Apache/php
2、硬盘 I/O (Innodb) 或MyISAM锁等待

one.png

双机(Two Box)

  • 访问量逐渐增大
  • Apache/PHP在Server A;MySQL在Server B
  • 瓶颈

1、硬盘 I/O (Innodb) 或MyISAM锁等待
2、网络I/O

two.png

多机 (Many Boxes with Replication)

  • 访问量继续增大
  • MySQL主从复制及读写分离 (master 负责IN/UP/DEL, slave负责 SELECT)
  • SELECT, IN/UP/DEL可以在应用程序内指定访问不同服务器 (如使用不同的handle或Db Adapter)
  • WEB Server可能需要使用负载均衡
  • NoSQL/Cache/CDN

many.png

系统分层:

fc.png

Cache Tier

Memcached、Redis等广泛使用。

cache.png

架构新问题
Mysql:

  1. Slave Lag 每台Slave数据完全一样。有的忙,有的闲
  2. 数据量越来越大,单表过大,查询效率太低;

综合1.2通常采用

  • memcached数据缓存
  • MySQL水平扩展(库表拆分)

Web Server 负载过高

  • 提高PHP代码执行效率 Opcode Cache
  • 静态文件缓存 Squid/Varnish / CDN
  • 负载均衡

fcache.png

新架构目标:

  • 高可用
  • 高性能
  • 可扩展
  • 监控
  • 成本控制

分布式系统架构

分布式系统概念

What is a Distributed System?
“一个分布式系统是若干个独立的计算机的集合,但是对该系统的用户来说,系统就像一台计算机一样。”

两方面的含义:

  1. 硬件方面:各个计算机都是自治的
  2. 软件方面:用户将整个系统看作是一台计算机

分布式系统定义:

一个分布式系统组织成中间件形式,中间件层分布在多台机器上。

fbs.png

分布式系统优点:

yd.png

分布式操作系统特点:

td.png

网络操作系统(NOS)
网络操作系统的一般结构:

netos.png

分布式系统中设计的关键问题:
透明性(对用户、程序)

tm.png

灵活性

lhx.png

单内核基本上是目前的集中式操作系统,增加了网络功能和远程服务集合。
微内核的四种基本服务:
(1)进程间通信机制
(2)少量内存管理功能
(3)必要的低层进程管理和调度
(4)低层输入/输出服务
可靠性

kkx.png

性能

xn.png

可伸缩性(scalability)
避免:

  • 集中式硬件
  • 集中式算法
  • 集中式的数据结构

kssx.png

可扩展性

  • 没有一台机器上存放着关于系统状态的全部信息
  • 机器只是基于本地信息做出决定
  • 一个机器出故障不会破坏算法
  • 不一定存在全局时钟。

可扩展性示例:

sl.png

总结

没有固定的架构,架构都是随着时间和业务的迁徙和变动而发生改变和重构的。所以架构不是一成不变的,也不是淘宝的架构就是万能的,适应你们业务的架构演变的架构就是最优架构。万变不离其宗,但是其底层技术实现和方法是可以借鉴采用大公司场景做法。

为什么我不再使用 MVC 框架?

浏览次数:322次  @infoQ  2016年03月03日  字号:   

Jean-Jacques Dubray 是一名资深工程师,他最近引入了一个新的模式:状态-行为-模型(State-Action-Model,SAM)。SAM 是一个函数式反应型的编程模式,它致力于简化数据 Model 和 View 之间的交互。它究竟有何优点值得作者弃用 MVC 呢?

话题起因

在我最近的工作中,最让人抓狂的就是为前端开发人员设计 API。我们之间的对话大致就是这样的:

开发人员:这个页面上有数据元素x,y,z…,你能不能为我创建一个 API,响应格式为{x: , y:, z: }

我:好吧

我甚至没有进行进一步的争论。项目结束时会积累大量的 API,这些 API 与经常发生变化的页面是关联在一起的,按照“设计”,只要页面改变,相应的 API 也要随之变化,而在此之前,我们甚至对此毫不知情,最终,由于形成因素众多且各平台之间存在些许差异,必须创建非常多的 API 来满足这些需求。

Sam Newman 甚至将这种制度化的过程称之为BFF 模式,这种模式建议为每种设备、平台当然还包含 APP 版本开发特定的 API。Daniel Jacobson 在接受 InfoQ 的采访时曾指出,Netflix 颇为勉强地将“体验式 API”与“临时 API(Ephemeral API)”划上了等号。唉……

几个月前,我开始思考是什么造成了如今的这种现象,该做些什么来应对它,这个过程使我开始质疑应用架构中最强大的理念,也就是 MVC,我感受到了函数式反应型编程(reactive)的强大威力,这个过程致力于流程的简化,并试图消除我们这个行业在生产率方面的膨胀情绪。我相信 你会对我的发现感兴趣的。

  MVC 的辉煌过去与现存问题

在每个用户界面背后,我们都在使用 MVC 模式,也就是模型-视图-控制器(Model-View-Controller)。MVC 发明的时候,Web 尚不存在,当时的软件架构充其量是胖客户端在原始网络中直接与单一数据库会话。但是,几十年之后,MVC 依然在使用,持续地用于 OmniChannel 应用的构建。

Angular 2 正式版即将发布,在这个时间节点重估 MVC 模式及各种 MVC 框架为应用架构带来的贡献意义重大。

我第一次接触到 MVC 是在 1990 年,当时 NeXT 刚刚发布 Interface Builder(让人惊讶的是,如今这款软件依然发挥着重大的作用)。当时,我们感觉 Interface Builder 和 MVC 是一个很大的进步。在 90 年代末期,MVC 模式用到了 HTTP 上的任务中(还记得 Struts 吗?),如今,就各个方面来讲,MVC 是所有应用架构的基本原则。

MVC 的影响十分深远,以致于 React.js 在介绍他们的框架时都委婉地与其划清界限:“React 实现的只是 MVC 中视图(View)的部分”。

当我去年开始使用 React 的时候,我感觉它在某些地方有着明显的不同:你在某个地方修改一部分数据,不需要显式地与 View 和 Model 进行交互,整个 UI 就能瞬间发生变化(不仅仅是域和表格中的值)。这也就是说,我很快就对 React 的编程模型感到了失望,在这方面,我显然并不孤独。我分享一下 Andre Medeiros 的观点:

React 在很多方面都让我感到失望,它主要是通过设计不佳的 API 来引导程序员[…]将多项关注点混合到一个组件之中。

作为服务端的 API 设计者,我的结论是没有特别好的方式将 API 调用组织到 React 前端中,这恰恰是因为 React 只关注 View,在它的编程模型中根本不存在控制器。

到目前为止,Facebook 一直致力于在框架层面弥合这一空白。React 团队起初引入了 Flux 模式,不过它依然令人失望,最近 Dan Abramov 又提倡另外一种模式,名为 Redux,在一定程度上来讲,它的方向是正确的,但是在将 API 关联到前端方面,依然比不上我下面所介绍的方案。

Google 发布过 GWT、Android SDK 还有 Angular,你可能认为他们的工程师熟知何为最好的前端架构,但是当你阅读 Angular 2 设计考量的文章时,便会不以为然,即便在 Google 大家也达成这样的共识,他们是这样评价之前的工作成果的:

Angular 1 并不是基于组件的理念构建的。相反,我们需要将控制器与页面上各种[元素]进行关联(attach),其中包含了我们的自定义逻辑。根据我们自定义的指令 如何对其进行封装(是否包含 isolate scope?),scope 会进行关联或继续往下传递。

基于组件的 Angular 2 看起来能简单一点吗?其实并没有好多少。Angular 2 的核心包本身就包含了 180 个语义(semantics),整个框架的语义已经接近 500 个,这是基于 HTML5 和 CSS3 的。谁有那么多时间学习和掌握这样的框架来构建 Web 应用呢?当 Angular 3 出现的时候,情况又该是什么样子呢?

在使用过 React 并了解了 Angular 2 将会是什么样子之后,我感到有些沮丧:这些框架都系统性地强制我使用 BFF“页面可替换模式(Screen Scraping)”模式,按照这种模式,每个服务端的 API 要匹配页面上的数据集,不管是输入的还是输出的。

弃用 MVC 之后怎么走?

此时,我决定“让这一切见鬼去吧”。我构建了一个 Web 应用,没有使用 React、没有使用 Angular 也没有使用任何其他的 MVC 框架,通过这种方式,我看一下是否能够找到一种在 View 和底层 API 之间进行更好协作的方式。

就 React 来讲,我最喜欢的一点在于 Model 和 View 之间的关联关系。React 不是基于模板的,View 本身没有办法请求数据(我们只能将数据传递给 View),看起来,针对这一点进行探索是一个很好的方向。

如果看得足够长远的话,你会发现 React 唯一的目的就是将 View 分解为一系列(纯粹的)函数和 JSX 语法:

<Vparams={M}/>

它实际上与下面的格式并没有什么差别:

V=f(M)

例如,我当前正在从事项目的 Web 站点,Gliiph,就是使用这种函数构建的:

图1:用于生成站点 Slider 组件 HTML 的函数

这个函数需要使用 Model 来填充数据:

图2:支撑 slider 的 Model

如果用简单的 JavaScript 函数就能完成任务,我们为什么还要用 React 呢?

虚拟 DOM(virtual-dom)?如果你觉得需要这样一种方案的话(我并不确定有很多的人需要这样),其实有这样的可选方案,我也期望开发出更多的方案。

GraphQL?并不完全如此。不要因为 Facebook 大量使用它就对其产生误解,认为它一定是对你有好处的。GraphQL 仅仅是以声明的方式来创建视图模型。强制要求 Model 匹配 View 会给你带来麻烦,而不是解决方案。React 团队可能会觉得使用“客户端指定查询(Client-specified queries)”是没有问题的(就像反应型团队中那样):

GraphQL 完全是由 View 以及编写它们的前端工程师的需求所驱动的。[…]另一方面,GraphQL 查询会精确返回客户端请求的内容,除此之外,也就没什么了。

GraphQL 团队没有关注到 JSX 语法背后的核心思想:用函数将 Model 与 View 分离。与模板和“前端工程师所编写的查询”不同,函数不需要 Model 来适配 View。

当 View 是由函数创建的时候(而不是由模板或查询所创建),我们就可以按需转换 Model,使其按照最合适的形式来展现 View,不必在 Model 的形式上添加人为的限制。

例如,如果 View 要展现一个值v,有一个图形化的指示器会标明这个值是优秀、良好还是很差,我们没有理由将指示器的值放到 Model 中:函数应该根据 Model 所提供的v值,来进行简单的计算,从而确定指示器的值。

现在,把这些计算直接嵌入到 View 中并不是什么好主意,使 View-Model 成为一个纯函数也并非难事,因此当我们需要明确的 View-Model 时,就没有特殊的理由再使用 GraphQL 了:

V = f ( vm (M) )

作为深谙 MDE 之道的人,我相信你更善于编写代码,而不是元数据,不管它是模板还是像 GraphQL 这样的复杂查询语言。

这个函数式的方式能够带来多项好处。首先,与 React 类似,它允许我们将 View 分解为组件。它们创建的较为自然的界面允许我们为 Web 应用或 Web 站点设置“主题”,或者使用不同的技术来渲染 View(如原生的方式)。函数实现还有可能增强我们实现反应型设计的方式。

在接下来的几个月中,可能会出现开发者交付用 JavaScript 函数包装的基于组件的 HTML5 主题的情况。这也是最近这段时间,在我的 Web 站点项目中,我所采用的方式,我会得到一个模板,然后迅速地将其封装为 JavaScript 函数。我不再使用 WordPress。基本上花同等的工夫(甚至更少),我就能实现 HTML5 和 CSS 的最佳效果。

这种方式也需要在设计师和开发人员之间建立一种新型的关系。任何人都可以编写这些 JavaScript 函数,尤其是模板的设计人员。人们不需要学习绑定方法、JSX 和 Angular 模板的语法,只掌握简单的 JavaScript 核心函数就足以让这一切运转起来。

有意思的是,从反应型流程的角度来说,这些函数可以部署在最合适的地方:在服务端或在客户端均可。

最为重要的是,这种方式允许在 View 与 Model 之间建立最小的契约关系,让 Model 来决定如何以最好的方式将其数据传递给 View。让 Model 去处理诸如缓存、懒加载、编配以及一致性的问题。与模板和 GraphQL 不同,这种方式不需要从 View 的角度来直接发送请求。

既然我们有了一种方式将 Model 与 View 进行解耦,那么下一个问题就是:在这里该如何创建完整的应用模型呢?“控制器”该是什么样子的?为了回答这个问题,让我们重新回到 MVC 上来。

苹果公司了解 MVC 的基本情况,因为他们在上世纪 80 年代初,从 Xerox PARC“偷来了”这一模式,从那时起,他们就坚定地实现这一模式:

图3:MVC 模式

Andre Medeiros 曾经清晰地指出,这里核心的缺点在于,MVC 模式是“交互式的(interactive)”(这与反应型截然不同)。在传统的 MVC 之中,Action(Controller)将会调用 Model 上的更新方法,在成功(或出错)之时会确定如何更新 View。他指出,其实并非必须如此,这里还有另外一种有效的、反应型的处理方式,我们只需这样考虑,Action 只应该将值传递给 Model,不管输出是什么,也不必确定 Model 该如何进行更新。

那核心问题就变成了:该如何将 Action 集成到反应型流程中呢?如果你想理解 Action 的基础知识的话,那么你应该看一下 TLA+。TLA 代表的是“Action 中的逻辑时序(Temporal Logic of Actions)”,这是由 Dr. Lamport 所提出的学说,他也因此获得了图灵奖。在 TLA+ 中,Action 是纯函数:

data’ = A (data)

我真的非常喜欢 TLA+ 这个很棒的理念,因为它强制函数只转换给定的数据集。

按照这种形式,反应型 MVC 看起来可能就会如下所示:

V = f ( M.present ( A (data) ) )

这个表达式规定当 Action 触发的时候,它会根据一组输入(例如用户输入)计算一个数据集,这个数据是提交到 Model 中的,然后会确定是否需要以及如何对其自身进行更新。当更新完成后,View 会根据新的 Model 状态进行更新。反应型的环就闭合了。Model 持久化和获取其数据的方式是与反应型流程无关的,所以,它理所应当地“不应该由前端工程师来编写”。不必因此而感到歉意。

再次强调,Action 是纯函数,没有状态和其他的副作用(例如,对于 Model,不会包含计数的日志)。

反应型 MVC 模式很有意思,因为除了 Model 以外,所有的事情都是纯函数。公平来讲,Redux 实现了这种特殊的模式,但是带有 React 不必要的形式,并且在 reducer 中,Model 和 Action 之间存在一点不必要的耦合。Action 和接口之间是纯粹的消息传递。

这也就是说,反应型 MVC 并不完整,按照 Dan 喜欢的说法,它并没有扩展到现实的应用之中。让我们通过一个简单的样例来阐述这是为什么。

假设我们需要实现一个应用来控制火箭的发射:一旦我们开始倒计时,系统将会递减计数器(counter),当它到达零的时候,会将 Model 中所有未定的状态设置为规定值,火箭的发射将会进行初始化。

这个应用有一个简单的状态机:

图4:火箭发射的状态机

其中 decrement 和 launch 都是“自动”的 Action,这意味着我们每次进入(或重新进入)counting 状态时,将会保证进行转换的评估,如果计数器的值大于零的话,decrementAction 将会继续调用,如果值为零的话,将会调用 launchAction。在任何的时间点都可以触发 abortAction,这样的话,控制系统将会转换到 aborted 状态。

在 MVC 中,这种类型的逻辑将会在控制器中实现,并且可能会由 View 中的一个计时器来触发。

这一段至关重要,所以请仔细阅读。我们已经看到,在 TLA+ 中,Action 没有副作用,只是计算结果的状态,Model 处理 Action 的输出并对其自身进行更新。这是与传统状态机语义的基本区别,在传统的状态机中,Action 会指定结果状态,也就是说,结果状态是独立于 Model 的。

在 TLA+ 中,所启用的 Action 能够在状态表述(也就是 View)中进行触发,这些 Action 不会直接与触发状态转换的行为进行关联。换 句话说,状态机不应该由连接两个状态的元组(S1, A, S2)来进行指定,传统的状态机是这样做的,它们元组的形式应该是(Sk, Ak1, Ak2,…),这指定了所有启用的 Action,并给定了一个状态 Sk,Action 应用于系统之后,将会计算出结果状态,Model 将会处理更新。

当我们引入“state”对象时,TLA+ 提供了一种更优秀的方式来对系统进行概念化,它将 Action 和 view(仅仅是一种状态的表述)进行了分离。

我们样例中的 Model 如下所示:

model={
counter:,
started:,
aborted:,
launched:
}

系统中四个(控制)状态分别对应于 Model 中如下的值:

ready={counter:10,started:false,aborted:false,launched:false}
counting={counter:[0..10],started:true,aborted:false,launched:false}
launched={counter:0,started:true,aborted:false,launched:true}
aborted={counter:[0..10],started:true,aborted:true,launched:false}

这个 Model 是由系统的所有属性及其可能的值所指定的,状态则指定了所启用的 Action,它会给定一组值。这种类型的业务逻辑必须要在某个地方进行实现。我们不能指望用户能够知道哪个 Action 是否可行。在这方面,没有其他的方式。不过,这种类型的业务逻辑很难编写、调试和维护,在没有语义对其进行描述时,更是如此,比如在 MVC 中就是这样。

让我们为火箭发射的样例编写一些代码。从 TLA+ 角度来讲,next-action 断言在逻辑上会跟在状态渲染之后。当前状态呈现之后,下一步就是执行 next-action 断言,如果存在的话,将会计算并执行下一个 Action,这个 Action 会将其数据交给 Model,Model 将会初始化新状态的表述,以此类推。

图5:火箭发射器的实现

需要注意的是,在客户端/服务器架构下,当自动 Action 触发之后,我们可能需要使用像 WebSocket 这样的协议(或者在 WebSocket 不可用的时候,使用轮询机制)来正确地渲染状态表述。

我曾经使用 Java 和 JavaScript 编写过一个很轻量级的开源库,它使用 TLA+ 特有的语义来构造状态对象,并提供了样例,这些样例使用 WebSocket、轮询和队列实现浏览器/服务器交互。在火箭发射器的样例中可以看到,我们并非必须要使用那个库。一旦理解了如何编写,状态实现的编码 相对来讲是很容易的。

  新模式——SAM 模式

对于要引入的新模式来说,我相信我们已经具备了所有的元素,这个新模式作为 MVC 的替代者,名为 SAM 模式(状态-行为-模型,State-Action-Model),它具有反应型和函数式的特性,灵感来源于 React.js 和 TLA+。

SAM 模式可以通过如下的表达式来进行描述:

V = S ( vm ( M.present ( A (data) ) ), nap (M))

它表明在应用一个 Action A 之后,View V 可以计算得出,Action 会作为 Model 的纯函数。

在 SAM 中,A(Action)、vm(视图-模型,view-model)、nap(next-action 断言)以及S(状态表述)必须都是纯函数。在 SAM 中,我们通常所说的“状态”(系统中属性的值)要完全局限于 Model 之中,改变这些值的逻辑在 Model 本身之外是不可见的。

随便提一下,next-action 断言,即 nap ()是一个回调,它会在状态表述创建完成,并渲染给用户时调用。

图7:“修改地址”的实现

模式中的元素,包括 Action 和 Model,可以进行自由地组合:

函数组合

data’ = A (B(data))

端组合(Peer)(相同的数据集可以提交给两个 Model)

M1.present (data’)

M2.present (data’)

父子组合(父 Model 控制的数据集提交给子 Model)

M1.present(data’,M2)
functionpresent(data,child){
//执行更新
…
//同步Model
child.present(c(data))
}

  发布/订阅组合

M1.on (“topic”, present )

M2.on (“topic”, present )

M1.on (“data”, present )

M2.on (“data”, present )

有些架构师可能会考虑到 System of Record 和 Systems of Engagement,这种模式有助于明确这两层的接口(图8),Model 会负责与 systems of record 的交互。

图8:SAM 组合模型

整个模式本身也是可以进行组合的,我们可以实现运行在浏览器中的 SAM 实例,使其支持类似于向导(wizard)的行为(如 ToDo 应用),它会与服务器端的 SAM 进行交互:

图9:SAM 实例组合

请注意,里层的 SAM 实例是作为状态表述的一部分进行传送的,这个状态表述是由外层的实例所生成的。

会话检查应该在 Action 触发之前进行(图 10)。SAM 能够启用一项很有意思的组合,在将数据提交给 Model 之前,View 可以调用一个第三方的 Action,并且要为其提供一个 token 和指向系统 Action 的回调,这个第三方 Action 会进行授权并校验该调用的合法性。

图 10:借助 SAM 实现会话管理

从 CQRS 的角度来讲,这个模式没有对查询(Query)和命令(Command)做特殊的区分,但是底层的实现需要进行这种区分。搜索或查询“Action”只是 简单地传递一组参数到 Model 中。我们可以采用某种约定(如下划线前缀)来区分查询和命令,或者我们可以在 Model 上使用两个不同的 present 方法:

{ _name : ‘/^[a]$/i’ } // 名字以A或a开头

{ _customerId: ‘123’ } // id=123 的 customer

Model 将会执行必要的操作以匹配查询,更新其内容并触发 View 的渲染。类似的约定可以用于创建、更新或删除 Model 中的元素。在将 Action 的输出传递给 Model 方面,我们可以实现多种方式(数据集、事件、Action……)。每种方式都会有其优势和不足,最终这取决于个人偏好。我更喜欢数据集的方式。

在异常方面,与 React 类似,我们预期 Model 会以属性值的形式保存异常信息(这些属性值可能是由 Action 提交的,也可能是 CRUD 操作返回的)。在渲染状态表述的时候,会用到属性值,以展现异常信息。

在缓存方面,SAM 在状态表述层提供了缓存的选项。直观上来看,缓存这些状态表述函数的结果能够实现更高的命中率,因为我们现在是在组件/状态层触发缓存,而不是在 Action/响应层。

该模式的反应型和函数式结构使得功能重放(replay)和单元测试变得非常容易。

SAM 模式完全改变了前端架构的范式,因为根据 TLA+ 的基础理念,业务逻辑可以清晰地描述为:

  • Action 是纯函数

  • CRUD 操作放在 Model 中

  • 状态控制自动化的 Action

作为 API 的设计者,从我的角度来讲,这种模式将 API 设计的责任推到了服务器端,在 View 和 Model 之间保持了最小的契约。

Action 作为纯函数,能够跨 Model 重用,只要某个 Model 能够接受 Action 所对应的输出即可。我们可以期望 Action 库、主题(状态表述)甚至 Model 能够繁荣发展起来,因为它们现在能够独立地进行组合。

借助 SAM 模式,微服务能够非常自然地支撑 Model。像 Hivepod.io 这样的框架能够插入进来,就像它本来就在这层似得。

最为重要的是,这种模式像 React 一样,不需要任何的数据绑定或模板。

随着时间的推移,我希望能够推动浏览器永久添加虚拟 DOM 的特性,新的状态表述能够通过专有 API 直接进行处理。

我发现这个旅程将会带来一定的革新性:在过去的几十年中,面向对象似乎无处不在,但它已经一去不返了。我现在只能按照反应型和函数式来进行思考。我 借助 SAM 所构建的东西及其构建速度都是前所未有的。另外,我能够关注于 API 和服务的设计,它们不再遵循由前端决定的模式。

  老司机介绍

Jean-JacquesDubray 是 xgen.io 和 gliiph 的创立者。在过去的 15 年中,他一直致力于构建面向服务的架构和 API 平台。他曾经是 HRL 的研究人员,在普罗旺斯大学(吕米尼校园)获取了博士学位,Prolog 语言就是由该学校发明的,同时他是 BOLT 方法学的发明者。


  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值