写点什么

Starcounter 和 ORM&DDD 比较

  • 2016-09-22
  • 本文字数:5724 字

    阅读完需:约 19 分钟

本文要点

  • 由于对象 - 关系阻抗失配,传统的 N 层设计会引发问题;
  • 修复 ORM 和数据库问题的最佳方式是移除那些层;
  • 访问数据库对象可以和访问普通的.NET 类一样简单;
  • 没有传统限制的数据库可以提供设计自由;
  • 类似 Starcounter 这样的混合数据库让这一点成为可能。

本文将重点介绍当前供软件开发人员和独立软件供应商使用的 MVC 框架所存在的一些问题,然后利用内存内应用程序引擎 Starcounter 提供了解决方案。为了尽可能地真实,几个源于实际项目的例子使用了 ASP.NET+Entity Framework(数据库优先和代码优先),并以 MS-SQL Server 或 MySQL 作为数据库引擎。本文末尾有一个简单的入门项目,不要读过就完了,还要亲自测试一下。

在开发业务应用程序时,你会发现,几乎每个应用程序都需要一致性管理及在多个并发用户之间共享数据。通常,该功能是由数据库管理系统(DBMS)提供的。早在 70 年代初,大部分 DBMS 就实现了标准数据操作语言 SQL。于是,软件开发人员需要根据每个系统的优缺点选择一种数据存储方法。然而,所有的应用程序差不多都采用了同样的设计:应用程序逻辑在这里,数据库在那里。

1.ORM

SQL 数据库引擎按照表、列和行来操作数据。使用任意 OOP 语言编写的应用程序则按照类、属性和实例来操作数据。

这种失配会导致应用程序和数据库之间的通信层产生大量的冗余代码。为了减少冗余代码、Bug 和开发时间,ORM 库(对象关系映射)被发明出来。

ORM 在应用程序和数据库之间又加了一层。ORM 充当了表、列、行和类、属性、实例概念之间的翻译。

ORMs 旨在:

1.1. 移除应用程序中的所有 SQL 代码;

1.2. 为访问数据库表提供和访问普通的应用程序类一样的方式;

1.3. 为不修改应用程序代码切换数据库引擎提供可能。

这可能听上去很好,但遗憾的是,没有这样的好事。下面是当前使用 ORM 的实际情况:

1.1. 减少应用程序中的 SQL 代码;

1.2. 为访问数据库表提供和访问普通的应用程序类类似的方式;

1.3. 为少修改应用程序代码切换数据库引擎提供可能。

出现这种情况不是因为 ORM 不好,而是因为数据库引擎的架构。让我们看下究竟是什么限制了 ORM。

1.1. 移除应用程序中的所有 SQL 代码

在任何 SQL 数据库引擎中,SQL 查询都是唯一的数据操作方式。ORM 应该将表类实例上的每个动作转换成 SQL 查询,并在数据库里运行。

ORM 尚不够智能,无法选择最好的 SQL 语法。大型复杂查询还是应该由开发人员利用自己的 SQL 知识来编写。

有些动作,如触发器,只能在数据库引擎内执行,而该引擎只支持 SQL。

1.2. 为访问数据库表提供和访问普通的应用程序类一样的方式

类实例的属性值和实例本身都保存在内存中。表类的属性值保存在数据库引擎中,可能是 HDD 或内存的另一部分。为了访问表类实例,我们必须将所有的属性值一起加载,或者按需加载每个属性(延迟加载)。这两种方法都可能会导致性能问题。

在访问表类的实例时,开发人员应该牢记数据库的限制。

我们都知道“计算属性(calculated properties)”有多重要,但遗憾的是,ORM 不支持。例如,考虑下下面的类和查询:

复制代码
public class OrderItem {
public int Quantity;
public decimal Price;
public decimal Amount {
return this.Price * this.Quantity;
}
}
var items = db.OrderItems.Where(x => x.Amount > 1000);

Where 查询会被转换成 SQL,然后失败,因为数据库没有提供 Amount 属性。

1.3. 为不修改应用程序代码切换数据库引擎提供可能

有许许多多不同的数据库引擎,每一种都有自己独一无二的特性、优点和不足。如果不检查并重写应用程序某些部分的代码,就不可能切换数据库引擎。上一个引擎的独特特性必须删除。

尝试将一个项目从 MS-SQL 迁移到 MySQL,从 MS-SQL 迁移到 MS-SQL CE,或者反过来,你就了解所面临的挑战了:不同的数据类型,不同的“跳过 N 行”和“取前 N 行”实现、不同的子查询、视图、存储过程实现和限制。

2. 领域驱动设计(DDD)

将业务应用程序分成两部分(应用和数据库)会导致一些重大问题。为了解决那些问题,领域驱动设计被发明出来。

下面列出了 DDD 旨在解决的部分问题:

2.1. 项目中相同的功能,其实现方式相同或者在不同的地方不同;
2.2. 同一个项有不止一个对象;
2.3. 有的对象的属性实际上不是那个对象的基本属性;
2.4. 相关项之间有一个糟糕的关系;
2.5. 无法通过查看对象了解整个应用。

这里有一篇介绍 DDD 的文章:领域驱动设计——开始之前先明确思想

现在,我们将详细介绍上面列出的每一条内容,理解下都是什么情况。

2.1. 项目中相同的功能,其实现方式相同或者在不同的地方不同

这个问题的原因无疑是应用被分成了不同的部分。下面是一个简单的例子:

有个购物车,里面有一些物品。下面是两个场景。

a)用户向购物车添加或从购物车删除一个物品。总价会在用户保存修改之前自动计算。这个计算是由应用程序完成。
b)管理员按照总价顺序列出了所有购物车。总价是由数据库计算的。

大多数开发人员会倾向于在购物车中添加一个 TotalAmount 属性。一旦购物车里的物品发生了变化,TotalAmount 属性就会更新。这解决了第一个问题,但导致了上面列出的第三个问题。

2.2. 同一个项有不止一个对象

应用程序和数据库之间的通信速度是最大的性能缺陷之一。为了减少传输的数据量,有些大表可能会有两个或两个以上的 ORM 类。

一个简单的例子,下表:

复制代码
CREATE TABLE Cars (
ID int,
Driver varchar,
PlateNumber varchar,
Brand varchar,
Model varchar,
Kit varchar,
Color varchar,
Engine decimal,
Travelled int,
Year int,
Cost decimal,
FuelConsumption decimal
)

可能会产生两个场景:

a)显示所有汽车的列表——需要所有字段;
b)在一个自动完成字段中显示汽车列表——只需要 ID、Driver 和 PlateNumber 值。

如果这两种情况使用相同的 ORM 类,那么,即使汽车类的所有字段值不会都用到,也会全部从数据库加载到应用程序。这意味着传输了更多的数据、消耗了更多的时间,最终就要承担客户不满意的风险。

如果这两种情况使用不同的 ORM 类,就会违反 DDD 模式。当应用程序被分成不同的部分,就会导致问题。如果表 Cars 位于应用程序之内,这个问题就不存在了。

2.3. 有的对象的属性实际上不是那个对象的基本属性

通常,深度嵌套需要冗余属性来提升性能。

这是一个简单且常见的模型。现在假设,我们有一项调查,并且需要查询调查的所有问题。

SQL 的写法如下:

复制代码
SELECT Question.*
FROM Question INNER JOIN Section ON Question.SectionID = Section.ID
INNER JOIN Page ON Section.PageID = Page.ID
INNER JOIN Survey ON Page.SurveyID = Survey.ID
WHERE Survey.ID = ?

LINQ 查询的写法如下:

复制代码
Context.Questions.Where(q => q.Section.Page.SurveyID = ?);

那么,这个查询有什么问题呢?有性能问题。

看下下面这张图。这张图看上去要复杂一些,因为有四个冗余关系。

由于添加了其他的关系,SQL 查询变成了下面的样子:

复制代码
SELECT *
FROM Question
WHERE Question.SurveyID = ?

我们为什么需要那些冗余属性和关系呢?答案很简单:为了提升性能。如果第一种方法运行得足够快,我们还会添加那些关系吗?当然不会。

2.4. 相关项无法很好地协调

相互关联的两个表可以作为这个问题的一个例子。

复制代码
TABLE A (ID, Name, BID);
TABLE B (ID, Name, AID);

插入两行相互关联的记录需要三个操作:

复制代码
Insert row A.
Insert row B with AID.
Update BID of row A.

这段代码在 ORM 表类里是行不通的:

复制代码
var a = new A();
var b = new B();
a.Name = “Instance of A with B”;
a.B = b;
b.Name = “Instance of B with A”;
b.A = a;
Context.SaveChanges();

这还是因为类和表之间的转换。SQL 数据库引擎不支持同时插入两行或多行数据。

另一个例子是“多重选择(multi-select)”值。

复制代码
TABLE Motherboard (
ID int,
Brand varchar,
Socket varchar,
Ram varchar,
Name varchar
);
TABLE CPU (
ID int,
Brand varchar,
Socket varchar,
Name varchar
);
-- 主板支持的 CPU
TABLE MotherboardCpu (
ID int,
MotherboardID int,
CpuID int
);

当一定会选择多行时,例如,当需要查询所有主板及其支持的 CPU 时,当通过 CPU 名称过滤时,从 MotherboardCpu 表查询并处理数据会显著地降低性能。

开发人员通常会使用下面的数据模型替换上述模型:

复制代码
TABLE Motherboard (
ID int,
Brand varchar,
Socket varchar,
Ram varchar,
Name varchar,
-- 以 CSV/JSON 格式列出支持的 CPU 名称
Cpu text
);
TABLE Cpu (
ID int,
Brand varchar,
Socket varchar,
Name varchar
);

从领域设计的角度看,Motherboard 和 CPU 是两个相关的实体,但它们在数据库里没有任何关系。

2.5. 无法通过查看对象了解整个应用

下面的例子是一个项目中的实体图。

项目中的每张表都需要一个 ORM 类、一个 JSON 表示类和一个 JavaScript 类。每个表三个类,而且表也难以维护。开发人员决定将将所有的状态和类型存到一张表里,从而减少表的数量。最后,数据表图如下:

实体图很清晰,一目了然,而数据表图则相当难理解。框架的限制导致开发人员需要在数据库纯净度和代码纯净度之间进行取舍。

Starcounter

下图展示了 Starcounter 的架构。

该图展示了运行在数据库里的 Starcounter 应用程序,它可以直接访问保存在 RAM 中的数据库对象。

数据不需要在数据库和应用程序之间往返。应用程序类即数据库对象。有了 Starcounter,就不需要 ORM 或其他映射器了。

下面是一个数据库类定义。

复制代码
[Database]
public class Vehicle {
public string PlateNumber;
public string Brand;
public string Model;
}

没有上下文声明,没有表名或者属性名映射器,有的只是带有属性的 C#类。下面是另外一个带有外键的例子。

复制代码
[Database]
public class VehicleStatus {
public string Name;
}
[Database]
public class Vehicle {
public string PlateNumber;
public string Brand;
public string Model;
public VehicleStatus Status;
}

情况还是一样。没有外键声明,没有映射,有的只是 C#属性。现在让我们看下,Starcounter 解决了上述哪些问题?

1.1. 移除应用程序中的所有 SQL 代码

目前,Starcounter 仍然需要使用一些 SQL 从数据库中查询对象。不过,你需要编写的 SQL 数量大幅减少,因为你可以追踪属性,从一个对象到下一个对象,而不必使用联合或多重查询。

1.2. 为访问数据库表提供和访问普通的应用程序类一样的方式

是的,绝对的。

数据库对逻辑复杂的只读属性提供了完全支持。由于 C#对象即数据库对象,所以不存在任何数据类型问题。

1.3. 为不修改应用程序代码切换数据库引擎提供可能

不,代码修改和调整是必需的。Starcounter 使用一种与传统数据库引擎完全不同的方法。

2.1. 项目中相同的功能,其实现方式相同或者在不同的地方不同;

Starcounter 没有数据库端和应用程序端。数据库对象即应用程序对象,反之亦然。而且,大多数 Starcounter 应用程序在服务器和客户端之间共用相同的模型。JavaScript 库 PuppetJs 负责同步服务器和客户端之间的变化。

最后,只有一个模型类,一个地方编写逻辑,降低了在不同地方编写相同逻辑的可能性。

2.2. 同一个项有不止一个对象

不需要为数据库对象创建任何额外的类或映射器;整个项可以直接从内存访问。

2.3. 有的对象的属性实际上不是那个对象的基本属性

如果每个对象的每个属性都可以直接访问,而且很简单,就不需要创建额外的属性。CPU 类应该有一个 Motherboard 的引用,Motherboard 类应该有一个 PC Case 的引用,但 CPU 类不应该包含任何 PC Case 的引用。

2.4. 相关项无法很好地协调

与传统 SQL 数据库不同,Starcounter 允许你同时存储两个相关对象。下面的代码可以正常运行:

复制代码
Db.Transact(() => {
var a = new A();
var b = new B();
a.Name = "Instance of A with B";
a.B = b;
b.Name = "Instance of B with A";
b.A = a;
});

多重查询属性的问题更容易解决:

复制代码
[Database]
public class Motherboard {
public string Brand;
public string Socket;
public string Ram;
public string Name;
public IEnumarable<MotherboardCpu> MotherboardCpus {
get {
return Db.SQL<MotherboardCpu>("
SELECT o FROM MotherboardCpu o WHERE o.Motherboard = ?", this);
}
}
public string MotherboardCpuNames {
get {
return string.Join(", ", this.MotherboardCpus.Select(x => x.Name));
}
}
}
[Database]
public class Cpu {
public string Brand;
public string Socket;
public string Name;
}
[Database]
public class MotherboardCpu {
public Motherboard Motherboard;
public Cpu Cpu;
}

只读属性 MotherboardCpuNames 完全可以在数据库层面访问。下面的 SQL 查询是有效的!

复制代码
var motherboards = Db.SQL<Motherboard>(
"SELECT o FROM Motherboard o WHERE o.MotherboardCpuNames LIKE ?",
"%i7-5___%");

注意:字符串连接是一项性能代价很高的任务。使用务必要恰当。

下面这个查询是上述查询的另一个版本,速度比上述查询快 25 倍:

复制代码
var motherboards = Db.SQL<Motherboard>(
"SELECT c.Motherboard FROM MotherboardCpu c WHERE c.Cpu.Name LIKE ?",
"%i7-5%").Distinct();

2.5. 无法通过查看对象了解整个应用

Starcounter 提供了一种简洁的方式,让开发人员可以根据需要创建任意多的数据库类。使用什么样的设计模式,应用什么样的架构,完全由开发人员决定。

小结

对于那些经历过上述任何问题的开发人员、小型创业公司或者大型企业来说,现在有解决方案了。从我个人的经验来看,我用过 ASP.NET MVC,倾向于不使用 PHP,尊重 Ruby on Rails。现在,我无法想象切回其中任何一项技术并编写大量的胶水代码。

作为入门,这里有一个简单的项目,对ASP.NET MVC 和Starcounter 这两项技术做了很好地比较,你可以试一下。

关于作者

Kostiantyn Cherniavskyi是一名资深应用程序开发人员。他在 C#、ASP.NET、MVC、PHP、数据库建模、MS-SQL、MySQL 等方面有 6 年多的经验。

查看英文原文: Starcounter vs. ORM and DDD

2016-09-22 19:102487
用户头像

发布了 1008 篇内容, 共 373.7 次阅读, 收获喜欢 340 次。

关注

评论

发布
暂无评论
发现更多内容

悦数图数据库 v3.6.0 发布:支持 Zone 管理,提升业务安全性和连续性

悦数图数据库

图数据库 悦数 杭州悦数

pdf增强插件Enfocus PitStop Pro 2020 for Mac下载

iMac小白

多个云平台,撑着零售消费企业们向上爬坡中

ToB行业头条

如何获取item_search_guang API中与“爱逛街”相关的API接口?

技术冰糖葫芦

API 文档

MySQL大表设计

EquatorCoco

MySQL 数据库 数据库云

为中小企业发展按下“提速键”,华为云这款轻量应用服务器成双11爆款

YG科技

干货丨小程序开发秘籍,华为云这款服务器才是真香系列

YG科技

PDF Expert for mac(pdf编辑工具) v3.7.1永久激活版

mac

PDF Expert 苹果mac Windows软件 PDF编辑和阅读工具

OmniGraffle Pro for mac(思维导图软件)v7.22.4激活版

mac

苹果mac Windows软件 OmniGraffle Pro 图形设计工具

JD-GUI 反编译jar包

javaNice

Java

Nginx配置Websocket

EquatorCoco

HTTP websocket 协议解析

引爆双11消费新热潮!华为云高性能轻量应用服务器“C位”耀眼

平平无奇爱好科技

HarmonyOS NEXT如何解决不同多媒体技术开发难题

Geek_2d6073

人工成本高企?华为云耀云服务器L实例打通企业网站开发最后堵着

YG科技

小程序开发“巨坑”多,华为云这款轻量应用服务器轻松避坑

YG科技

CleanMyMac X for mac下载 优化清理软件

iMac小白

剧情继续:马斯克曝出OpenAI前员工举报信,董事会与奥特曼谈判回归

Openlab_cosmoplat

阿里云崩溃损失大?华为云耀云服务器L实例为企业保驾护航

YG科技

火山引擎ByteHouse:如何优化ClickHouse物化视图能力?

字节跳动数据平台

数据库 大数据 云原生

低代码:数字化转型趋势下的快速开发方式

互联网工科生

低代码 数字化

和鲸携手暨大经管,为复合型、应用型数据人才培养工作提供最佳实践

ModelWhale

人工智能 数据分析 经管 交叉学科 暨南大学

跨境选品工具推荐|这些选品神器,跨境卖家都在用!

Geek_ccdd7f

cad2024 mac版更新 最新AutoCAD 2024中文破解版下载

iMac小白

软件测试/测试开发丨人工智能时代软件测试的变化

测试人

人工智能 软件测试

一个工具让你明白“万丈高楼平地起”,拒绝重复造轮子!

伤感汤姆布利柏

编程 程序员 前端 低代码

IT打工人避雷针!华为云这款轻量应用服务器是网站开发“神器”

YG科技

HashMap HashTable ConcurrentMap 中key value是否可以为null

javaNice

Java

杭州悦数出席 2023 云栖大会计算巢专场,分享云上最佳实践

悦数图数据库

数据库 图数据库 悦数 杭州悦数

如何在淘宝的item_search_seller API中获取店铺列表?

技术冰糖葫芦

API 文档

idea如何新建一个多模块的springCloud项目

javaNice

Java SpringCloud

低代码PaaS开发平台

树上有只程序猿

低代码 PaaS 私有化部署

Starcounter和ORM&DDD比较_.NET_Kostiantyn Cherniavskyi_InfoQ精选文章