2. mORMot架构原理
2. mORMot架构原理

这个框架试图实现一些“最佳实践”模式,其中包括:
- 模型视图控制器,详见模型视图控制器部分;
- 多层架构-见下文,详见多层架构部分;
- 测试驱动的设计,详见测试和日志部分;
- 无状态CRUD/REST,详见REST部分;
- 对象关系映射,详见对象关系映射(ORM)部分;
- 对象文档映射,详见NoSQL和对象文档映射(ODM)部分;
- 面向服务的架构,详见面向服务的架构(SOA)部分。
所有这些使得各种项目的实现成为可能,一直到复杂的领域驱动设计。
2.1. 总体设计
mORMot架构的总体设计如下图所示:

您还可以使用以下公共特性:

不要害怕,这幅图看起来庞大而令人困惑,尤其是当你有RAD(快速应用开发)开发经验,并且对现代设计模式接触不多时。
后面会详细解释框架是如何实现这个架构的,示例代码可以帮助您认清惊人的mORMot领域。
在前面的图示中,您已经可以确定mORMot的一些关键概念:
- 跨平台、多客户端、多设备;
- 能够集成到现有的代码库或架构;
- 客户端-服务端基于REST的设计;
- 分层(多层)实现;
- 流程可以通过一组服务(SOA)来定义;
- 业务规则和数据模型由客户端和服务器共享;
- 数据由对象(ORM/ODM)映射;
- 数据库可以是嵌入式SQLite3、一个或多个标准RDBMS(具有自动生成的SQL)、MongoDB NoSQL引擎、快速内存对象列表或另一个mORMot服务器;
- 所有层都集成了安全性(身份验证和授权);
- 可提供用户界面和报表类;
- 可以使用ORM/SOA方法编写MVC/MVVM AJAX或Web应用程序;
- 基于简单且经过验证的模式(REST、JSON、MVC、SOLID);
- 集成了一致的测试调试API;
- 优化的伸缩和稳定性。
2.2. 架构设计过程
首先,你不能孤立地谈论架构。架构总是由应用程序的实际需求驱动的,而不是由架构师昨天晚上读到的内容驱动的,架构希望了解它在现实世界中的工作方式。没有这样的“一种架构适合所有人”,也没有“一种框架适合所有人”的解决方案。架构仅仅是考虑如何构建自己的软件。
事实上,软件架构不是关于理论和图表,也不仅仅是关于最佳实践,而是关于为您的客户实现工作解决方案的一种方法。

这个图表展示了架构是典型的SCRUM敏捷迭代过程的一部分。即使您公司的某些人可能负责全球软件体系结构,或者即使您的项目管理遵循经典的v循环而不遵循敏捷宣言,体系结构也不应该被看作是一组规则,被每个开发人员应用。体系结构是编码的一部分,但不是全部编码。
译者注:Scrum是迭代式增量软件开发过程,通常用于敏捷软件开发。Scrum包括了一系列实践和预定义角色的过程骨架。Scrum中的主要角色包括同项目经理类似的Scrum主管角色负责维护过程和任务,产品负责人代表利益所有者,开发团队包括了所有开发人员。虽然Scrum是为管理软件开发项目而开发的,它同样可以用于运行软件维护团队,或者作为计划管理方法。
以下是一些实现渐进式设计的方法:
- 让每个开发人员根据自己的知识(和心情?)来决定如何实现用例,而不需要审查、实现文档或对等协作;
- 让每个团队根据自己的知识(以及不为人知的内部领导?)来决定如何在没有系统范围协作的情况下实现用例;
- 让架构应该在相对较高的层次上被决定,而不会影响开发人员的实际编码风格(无感知);
- 让架构变得无处不在,以至于每一行代码都必须遵循一个典型的实现模式,生成符合架构要求的工程代码;
- 让架构满足当前目标,当然最好有一些中期目标;
- 让技术、框架或刚刚发表的想法不受歧视地使用(不要相信开发营销的危言耸听)。
因此,一些建议:
- 协作是一种需要,没有人是独自战斗的,单个团队不可能完成所有工作,也没有管理者总是正确的;
- 分享是一种需要,个人之间、团队之间、管理者之间;
- 以客户和内容为中心;
- 今天的实现是明天的准备;
- 放松,也就是说,试着让你和你的团队成员更轻松地完成明天的工作;
- 他们不知道这是不可能的,所以他们做了。
mORMot框架的目的就是为您的团队提供工作所需的集成的类集合,这样您就可以专注于您的产品,享受与其他开源用户的协作,以便使用不断发展的适用的软件架构。
2.3. 模型-视图-控制器
模型-视图-控制器(MVC)是一种软件架构,是目前软件工程中使用的一种架构模式。模式将“域逻辑”(用户的应用程序逻辑)与用户界面(输入和表现)隔离开来,允许逐个(关注点分离)进行独立开发、测试和维护。

模型管理应用领域的行为和数据,响应有关其状态信息的请求(通常来自视图),响应更改状态的指令(通常来自控制器)。在事件驱动系统中,当信息发生变化时,模型会通知观察器(通常是视图),以便他们能够做出反应,而我们的ORM是无状态的,所以它不需要处理这些事件,参见无状态ORM部分。
视图将模型呈现为适合交互的表单,通常是用户界面元素。为了不同的目的,一个模型通常存在多个视图。典型的,视图通常与外观显示一一对应,并知道如何渲染它。
控制器接收用户输入并通过调用模型对象发起响应。控制器接受用户的输入,并指示模型和视图根据输入执行操作。

在框架中,模型不一定只是一个数据库;MVC中的模型是应用程序所需要的各种数据和业务/领域逻辑的总和。在我们的ORM中,模型是通过TSQLModel类实现的,TSQLModel类集中了应用程序用到的所有TSQLRecord派生类,包括与数据库相关的类和与业务逻辑相关的类。
视图可以通过以下方式实现:
- 对于桌面客户端,框架的一整套用户界面单元,大部分是由代码自动生成的,它们将根据模型呈现数据;
- 对于Web客户端,集成的高速Mustache渲染引擎(参见Mustache模版引擎)能够使用无逻辑模板呈现HTML页面,并嵌入Delphi编写的控制器方法(参见MVC/MVVM Web应用);
- 对于AJAX客户端,可以通过RESTful JSON服务轻松访问服务端。
控制器已经在我们的框架中基本实现了,通过RESTful提交命令,完成与相关视图(如刷新用户界面)和模型(用于数据处理)交互。一些与业务逻辑相关的自定义操作可以通过自定义TSQLRecord类或自定义RESTful服务来实现,参见服务端服务。
2.4. 多层架构
在软件工程中,多层架构(通常称为n层架构)是一个客户端�服务端架构,应用的表现、处理和数据管理是逻辑上独立的进程。例如,使用中间件在用户和数据库之间请求数据服务的应用程序应该采用多层架构。多层架构最广泛的应用是三层体系结构。
实际上,用Delphi编写的典型VCL/FMX RAD应用程序具有两层架构:

在这种方式中,应用程序层在表单和模块中混合了UI和逻辑。
我们的RESTful框架在ORM和SOA方面都使得三层架构的开发变得很容易。

Synopse mORMot框架遵循以下开发模式:
- 数据层采用SQLite3或高速内存数据库;很多SQL查询是动态创建的,数据库表是用Delphi类定义的;可以使用各种外部数据库,目前支持SQLite3、Oracle、Jet/MSAccess、MS SQL、Firebird、DB2、PostgreSQL、MySQL、Informix和NexusDB SQL等,也包括MongoDB这样的NoSQL引擎,参加访问外部SQL数据库部分;
- 逻辑层是纯粹用ORM和SOA实现:Delphi类通过数据层映射到数据库中,业务逻辑使用Delphi的interface服务化,如果你的项目达到某种程度的复杂性,还可采用领域驱动设计,参见领域驱动设计;
- 表示层是Delphi客户端,或AJAX应用程序,框架支持基于通过HTTP / 1.1的RESTful JSON通信(Delphi客户端界面通过RTTI和结构代码生成,而不是作为一个RAD,AJAX应用程序需要使用自己的工具和JavaScript框架编写,目前还没有包括“官方”的AJAX框架)。
事实上,mORMot可以扩展到领域驱动设计的四层架构,如下所示:
- 表示层,例如Delphi或AJAX客户端;
- 为客户端应用程序提供JSON内容的应用层;
- 业务逻辑层,集中所有领域处理,在所有应用程序之间共享;
- 持久化/数据层,可以是进程内(如SQLite3或内存中),也可以是外部(如Oracle、MS SQL、DB2、PostgreSQL、MySQL、Informix…)。

您必须在物理和逻辑的n层体系结构之间做出选择。大多数情况下,n层是一个物理(硬件)视图,例如数据库服务器和应用服务器之间的分离,将数据库放在单独的机器上以方便维护。在mORMot中,或通常的SOA中(参见接口),我们的逻辑层次是接口的分层(参见接口),底层硬件实现通常与逻辑布局不一致。

在本文中,我们将重点讨论思路/编码的逻辑方式,而让物理部署根据最终用户的期望去实施。
2.5. 面向服务的架构(SOA)
面向服务的体系结构(SOA)是一套灵活的设计原则,用于系统开发和信息处理集成阶段。基于SOA的系统将把功能打包为一组可互操作的服务,这些服务可以在多个不同业务领域的系统中独立使用。
软件服务是可重复活动的逻辑表示。简而言之,消费者要求生产者采取行动以获得结果。在大多数情况下,此调用不受以前任何调用的影响(因此称为无状态调用)。
SOA的实现建立在一组软件服务基础上。服务由不相关的、松散耦合的功能单元组成,这些功能单元之间没有相互调用。每个服务都执行一个操作,比如填写一个在线账户申请,或者查看一个在线银行对账单,或者发布一个在线预订或机票订单。它们使用定义好的协议向服务描述如何使用元数据传递和解析消息,而不是在源代码中嵌入服务间的调用。

由于大多数服务定义为无状态的,因此通常会定义一些服务的组合以提供多层的逻辑服务。更高级的服务调用多个服务来作为自包含的无状态服务工作;因此,较底层的服务仍然是无状态的,但是高级服务的使用者能够安全地处理某些事务流程。

有关SOA的详细信息,请参阅http://en.wikipedia.org/wiki/serviceoriented_architecture
SOA核心是解耦合。
也就是说,它可以通过多种方式独立实现,例如:
| 依赖 | 理想的解耦 | 解耦技术 |
|---|---|---|
| 平台 | 硬件、框架或操作系统不应限制服务使用者的选择 | 标准协议,主要是Web服务(例如SOAP或RESTful/JSON) |
| 位置 | 使用者应该不受服务主机更改的影响 | 通过路由和代理维护服务访问 |
| 可用性 | 维护任务应该是透明的 | 在服务器端集中提供远程访问支持 |
| 版本 | 引入新服务,无需客户端升级 | 在服务器端实现业务封装 |
SOA和ORM(参见对象关系映射)并不排斥它们自己。事实上,即使一些软件架构师倾向于只使用这两种特性中的一种,它们也可以共存,并在各中客户端-服务端应用程序中相互支撑:
- ORM访问可以用来访问数据对象,将服务端或客户端数据在本地呈现(Delphi,JavaScript…),所以可以使用ORM提供高效的数据和业务逻辑访问的思路就是CQRS模式;
- SOA可提供更高级的业务逻辑的处理方法:使用自定义参数和数据类型,可以向客户机提供一些高级服务,隐藏大部分业务逻辑,并减少所需的带宽。
特别是,SOA有利于将业务逻辑放在服务端,有助于增加调整多层体系结构。通过减少客户端和服务端之间的交互,能减少网络带宽、服务器资源(在服务端运行服务、添加所有远程连接和序列化所需的数据库访问的总成本低于在客户机运行服务)。我们的基于接口的SOA模型允许在客户端和服务器端运行相同的代码,在服务器端有更好的性能,但是双方都具有完全的互操作性。
2.6. 对象关系映射(ORM)
在实践中,ORM提供了一组方法来简化高级对象在RDBMS中的持久性。
我们的Delphi类实例不是直接使用关系数据库,虽然这是几十年来最方便的数据持久化方法。因此,需要某种“粘合剂”来将类属性保存到一个或多个表中。您可以使用数据库的本地语言(即SQL)与数据库交互。而SQL本身是一种完整的编程语言,其风格取决于具体的后端引擎(请考虑如何定义能够存储文本的列类型)。因此,编写和维护SQL语句可能会成为一项费时、困难且容易出错的任务。
有时候,没有什么比优化SQL语句更好的了,它能够聚合和连接来自多个表的信息。但多数情况下,您只需要对指定的对象执行一些基本操作,即CRUD(用于创建检索更新删除操作):在这方面我们的ORM可以带给您一个巨大的惊喜,因为它能够为您生成SQL语句。
ORM的工作原理如下:

ORM核心获取信息并执行映射:
- 通过
class类型(通过RTTI)定义对象; - 从每个数据库引擎获取数据库模型。

由于有几种可能的实现方案,我们将首先讨论每种方案的优缺点。
首先,这里有一个图,展示了使用Delphi进行数据库访问的一些常见实现方案(它也代表了大多数其他语言或框架,包括c#或Java)。

下表对Delphi世界中的一些常见方案进行了非常有启发性的介绍,ORM只是其中一种不错的可选性。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 使用带有GUI组件的DB视图和表 | SQL是一种功能强大的语言 可以使用高级DB工具(UML)和RAD方法 |
业务逻辑需要用存储过程来表述 SQL代码和存储过程将把你绑定到一个数据库引擎<br />客户端交互差<br />必须直接调用数据库生成报表<br />没有多层架构 |
| 用Delphi类映射数据库表或视图 | 可以在Delphi中使用详细的业务逻辑 与UI和数据分离 |
SQL代码必须手工编写,并与类同步 代码往往是重复的<br />SQL代码将你绑定到一个数据库引擎<br />从代码或通过数据库相关工具生成报表<br />难以实现真正的多层架构 |
| 使用ORM数据库 | 能在Delphi中使用非常详细的业务逻辑 SQL代码是由ORM生成的(通常情况下)<br />ORM将生成与DB引擎适配的SQL |
设计时需要更多的抽象(没有RAD方法) 在某些情况下,可能导致从数据库检索的数据多于所需<br />还不是一个真正的多层架构,因为ORM只用于DB访问,业务逻辑需要创建独立的类 |
| 完整的多层架构 | 能在Delphi中使用非常详细的业务逻辑 SQL代码是由ORM生成的(通常情况下)<br />ORM将生成与DB引擎适配的SQL<br />服务只检索或处理所需要的数据<br />服务器可以创建被客户端视为数据库对象的对象视图,不管这些对象仅在内存中可用,还是Delphi中定义的一些业务逻辑<br />完整的多层架构 |
设计时需要更多的抽象(没有RAD方法) |
您会发现我们的框架实现了一个客户端-服务端ORM,如果需要,可以将其缩小到独立模式,但是由于其独特的实现,它可以扩展到任何复杂的领域驱动设计。
就我们所知,看看周围的每种语言和技术,几乎没有其他ORM支持这种面向本机客户端-服务端的方法。通常的实践是使用面向服务的架构(SOA)远程访问ORM。有些项目允许远程访问现有ORM,但它们是独立的项目。我们的mORMot从一开始就以RESTful客户端-服务端为导向,非常独特。
如果您在多年前就进入了Delphi世界,那么您可能非常熟悉RAD方法。但是您可能还会发现,维护一个混合了UI组件、业务逻辑和数据库查询的应用程序是多么困难。今天的软件用户对软件可用性有一些巨大的人体工程学期望:仅带有表格和按钮的屏幕,映射数据库,并不一定有吸引力。使用mORMot的ORM/SOA方法将帮助您专注于您的业务和您的客户期望,让框架为您执行大多数模块集成。
2.7. NoSQL和对象文档映射(ODM)
SQL是数据操作的事实标准。
- 基于Schema;
- 基于关系;
- ACID事务;
- 实践证明有效;
- 事实上的“标准”(每个DB都有自己的列类型系统)。
NoSQL是一种新的范例,在2009年初被命名为NoSQL(一些数据库引擎,如Lotus Domino,可能在几十年前就符合这个定义):
- NoSQL代表“Not Only SQL”,比“no SQL”更贴切;
- 适用于网络和大数据(如亚马逊、谷歌、Facebook),如简单的复制和API;
- 不依赖标准(数据建模和查询);
- 拥有许多不同的实现,涵盖任何数据使用,http://nosql-database.org列出了150多个引擎。
我们可以确定NoSQL数据库的两个主要系列:
- 面向图形的数据库;
- 面向聚合的数据库。
面向图形的数据库通过关系/关联存储数据:

这种数据库是非常有用的,例如,对于开发任何“社交”软件,它会通过每个节点之间的关系来评估其数据。这样的数据模型与关系模型不兼容,像Neo4j这样的NoSQL引擎在本地处理这类数据。按照设计,面向图形的数据库符合ACID。
主流的NoSQL数据库系列是面向聚合的数据库。对于聚合,我们的定义与下面用于领域驱动设计的定义相同。它是作为一个单元进行交互的数据集合,构成了给定模型中ACID操作的边界。
事实上,面向聚合的数据库可以指定为三种主要的实现/查询模式:
- 基于文档的(如MongoDB、CouchDB、RavenDB);
- 键/值(例如Redis、Riak、Voldemort);
- 列族(如Cassandra、HiBase)。
他们中的一些可以无模式(即数据布局是不固定的,并且可以动态进化没有重新索引整个数据库),但是基于列驱动模式,甚至仅能存储普通的BLOB数据(这是键/值引擎的目的,关注存储速度和依赖客户端处理数据)。
简言之,RDBMS按表存储数据,需要连接引用来获得聚合信息:

而NoSQL将其聚合存储为文档:所有数据都嵌入其中。

这可以表示为以下JSON数据:
{
"ID": 1234,
"UserName": "John Smith",
"Contact": {
"Phone": "123-456-789",
"Email": "xyz@abc.com"
},
"Access": {
"Level": 5,
"Group": "dev"
}
}
这样的文档将直接适合对象编程模型,而不需要考虑关联查询和数据库装配。
因此,我们可以讨论两种数据模型:
- 具有高度结构化的表组织、严格定义的数据格式和记录结构的关系数据模型;
- 文档数据模型是具有任意嵌套数据格式和不同“记录”格式的复杂文档的集合。
关系模型的特点是数据的规范化,即有效组织关系数据库的字段和表,以减少冗余。
另一方面,文档模型的特点是数据的反规范化,通过添加冗余数据或对数据进行分组来优化数据库的读取性能。它还支持服务器的水平伸缩,因为数据可以很容易地在多个服务器之间进行平衡,而没有执行远程连接的速度损失。
在使用NoSQL时,主要困难之一是如何定义反规范化数据,以及何时以规范化格式存储数据。
一个好习惯是根据需要经常执行的查询为数据建模。例如,您可能会嵌入子文档,这些子文档很可能是应用程序在大多数情况下请求的。大多数NoSQL引擎都具有投影机制,该机制允许您仅返回查询所需的字段,如果您此时不需要这些字段,则将子文档保留在服务器上。而不频繁的查询可以在单独的集合上执行,如使用信息查合并询。
由于NoSQL数据库比它们的关系数据库祖先拥有更少的硬而快速的规则,因此您更有可能根据您的期望优化您的模型。在实践中,与使用RDBMS相比,您可能花费更少的时间考虑“如何”存储数据,并且如果需要的话,以后仍然能够规范化信息。只要遵循客户端应用程序处理整个数据的一致性(例如通过一个ORM)的规则,NoSQL引擎就不会担心冗余信息。
正如您可能已经注意到的,这个文档数据模型比传统的关系模式更接近OOP范式。以至于在采用NoSQL的同时出现了一个新的框架家族,名为对象文档映射(Object Document Mapping, ODM),对应RDBMS就是对象关系映射(Object- relational Mapping, ORM)。
简而言之,这两种方法都有好处,需要加以权衡。
| SQL | NoSQL |
|---|---|
| 无处不在的SQL | 映射OOP和复杂类型(例如数组或嵌套文档) |
| 垂直扩展容易 | 解耦数据:水平扩张 |
| 数据大小(避免重复且没有schema) | Schema-less:清晰进化 |
| 数据存储一次,一致性好 | 版本管理(例如CouchDB) |
| 复杂的ACID语句 | 图形存储(如Redis) |
| 聚合函数(依赖) | Map/Reduce或聚合函数(如从MongoDB 2.2开始) |
译者注:Map/Reduce是一个编程模型,处理产生大数据集的相关实现.用户指定一个map函数处理一个key/value对,从而产生中间的key/value对集.然后再指定一个reduce函数合并所有的具有相同中间key的中间value
使用mORMot,在服务器端初始化数据时,只需一行代码,就可以从传统的SQL引擎切换到时髦的MongoDB服务器。对于要求较高的客户,您可以在任何时候从ORM切换到ODM,包括运行时。
2.8. 领域驱动设计
2.8.1. 定义
http://domaindrivendesign.org给出了领域驱动设计(DDD)的某种“官方”定义:
在过去的一二十年中,一种哲学作为一种潜流在对象社区中发展起来。领域驱动设计的前提是双重的:
- 对于大多数软件项目,主要关注点应该是领域和领域逻辑;
- 复杂的领域设计应该基于模型。
领域驱动的设计不是一种技术或方法。它是一种思维方式和一组优先级,旨在加速处理复杂领域的软件项目。
当然,这个特定的体系结构可以根据每个项目的需要进行定制。我们只是建议遵循一个基线,根据他们的需要和需求做好修改或调整服务。
2.8.2. 模式
对于其他类型的多层架构,DDD引入了一些限制性的模式,使设计更加简洁:
- 专注于某一领域,即某一种特定的知识;
- 在该领域中定义有界上下文;
- 创建域的演进模型,供应用程序使用;
- 识别某些类型的对象,值对象或实体对象/聚合;
- 在生成的模型和代码中使用普遍存在的语言;
- 将域与其他类型的关注点隔离(如不应从域层调用持久性,即域不应受技术考虑的影响,而应依赖于Factory和Repository模式);
- 将域发布为定义良好的解耦服务;
- 将域服务与现有应用程序或遗留代码集成。
下图是它们之间的模式呈现及关系映射。
它的灵感来自埃里克·埃文斯(Eric Evans)的《领域驱动的设计》, Addison-Wesley, 2004年出版的一本书(该书更新后提出了此后出现的一些观点)。

您可能已经遇到或使用过现有的许多模式。DDD的独特之处在于,由于几十年的商业软件试验,这些模式已经围绕一些清晰的概念进行了组织。
2.8.3. DDD适合您吗
领域驱动设计并不能在任何地方、任何情况下使用。
首先,使用DDD的前提是:
- 识别和界定领域(如您的业务目标的识别应该是明确的);
- 您必须能够访问领域专家,以敏捷迭代的方式建立创造性的协作;
- 技术熟练的团队,能够编写清晰的代码,因为DDD更多的是关于代码的表达能力,而不是技术,所以对于年轻的开发人员来说,它可能显得不那么“时髦”;
- 您希望您的内部团队积累领域的知识,因此,外包可能局限于应用程序,而不是核心领域。
然后检查DDD是否值得,即:
- 它能帮助你解决你想要解决的问题;
- 它符合您的战略目标:DDD将用于能获得业务资金的地方,并使您从竞争对手中脱颖而出;
- 您需要思路清晰,需要解决内部的复杂性,例如,对许多规则建模(您不会使用DDD来构建简单的应用程序,否则RAD可能就足够了);
- 你的企业正在探索:你的目标已经确定,但你不知道如何实现它;
- 不要贪多,选择一两个目标。
2.8.4. DDD简介
现在也许DDD听起来很吸引你。在这种情况下,我们的mORMot框架将提供实现它所需的所有模块,重点放在您的领域上,让库完成所有需要的基础工作。
如果您确定现在不使用DDD,您将始终能在mORMot找到您需要的工具,准备在需要时切换到DDD。
遗留代码和现有项目将受益于DDD模式。在使用DDD技术重构和收紧代码中最有价值的部分是所谓的缝合和隔离核心领域。使用DDD模式重写整个现有软件不是必须的:一旦您确定了业务策略的核心所在,就可以在这个领域逐步引入DDD。然后,根据持续的反馈,您将改进您的代码,添加回归测试,并将您的领域代码与最终用户代码隔离开来。
有关DDD的技术介绍以及mORMot如何帮助您实现此设计,详见领域驱动设计。
使用mORMot,您的软件解决方案永远不会陷入死胡同。您将能够始终适应客户的需要,并最大化您的投资回报率。

浙公网安备 33010602011771号