id":99,
"customerId":1,
"orderItems":[
{
"productId":27,
"price":32.45,
"productName":"NoSql Distilled"
}
],
"shippingAddress":[{"city":"Chicago"}]
"orderPayment":[
"ccinfo":"1000-1000-1000"
"txnId":"abelif879rft",
"billingAdress":{"city":"Chicago"}
],
}
这个模型有两个主要聚合:客户(Customer)和订单(Order)。客户数据包含一个账单地址(billing address)列表,订单数据包含订单项(order item)列表,收货地址(shipping address)和付款信息(payment),而付款信息有又含它所对应的账单地址。
同一逻辑地址(账单地址和收货地址所共用的“Chicago”)在示例数据中出现了三次,但是此处我们不用ID来指代,而是直接复制这个地址字符串。如果收货地址和账单地址都不会改变,这么做就很合适。在关系型数据库中,这种情况意味着Address表中的ID=77的那行数据保持不变。若要改变某个地址,需要在该表中创建新行。使用聚合模型后,我们就可以把整个地址结构复制到所有的聚合模型中。(?)
考虑到上述模型与关系型数据库要做权衡一样,这里我么也可以采用非常规的模式,将产品名称直接写入订单项。这种做法在聚合模型中很常见,我们希望在数据交互时尽量减少所需访问的聚合个数。实际上,我么也可以采用另一种方式,把客户下的全部订单放到客户聚合中。
// in customers
{
"id":1,
"name":"Martin",
"billingAddress":[{"city":"Chicago"}],
“orders”:[
{
"id":99,
"customerId":1,
"orderItems":[
{
"productId":27,
"price":32.45,
"productName":"NoSql Distilled"
}
],
"shippingAddress":[{"city":"Chicago"}]
"orderPayment":[
"ccinfo":"1000-1000-1000"
"txnId":"abelif879rft",
"billingAdress":{"city":"Chicago"}
],
}
]
}
}
这里就必须谈到如何划分聚合边界的问题(就是上面红字部分涉及的地方),其实在建模过程中,边界的划分并没有标准答案。如果想一次性访问客户的全部订单,那就应该把它们放在一个大的聚合里面,反之,若是每次只想专门处理一笔订单,则应将“客户”和“订单”分离开来。这个都视情况而定,而有的时候这个边界很难区分出来,在有的时候这种聚合反而会带来更多的问题,这时聚合就不适宜了。
面向聚合的影响:
虽然关系映射也能很好的处理各种数据元素之间的关系。上述示例说明:订单由订单项、收货地址及付款信息组成。在关系模型中,可以用“外键”来表示表与表之间的关系,但是这样做无法区分某个关系是否你能表示聚合。因此,数据库无法使用聚合结构来帮助其存储于分布数据。
选用面向聚合模型的决定因素,在于它非常适合在集群中运行。这正是NoSql的关键之处。在集群上运行时,需要把采集数据所需要的节点数降到最低。如果数据库中明确包含聚合结构,加上知道哪些数据要聚合一起操作,这些数据就可以放在一个节点中。(这在前期设计数据库分散在不同节点上非常重要。例如:集群中部署MongoDB收集不同机器上的的日志信息,因为不同机器上收集的日志文件不同,这时候就需要考虑上述说的问题,哪些数据可以放在一个节点上收集更有利于聚合,在实际运用很关键)
关系型数据库支持事务处理,即“ACID事务”,是关系型数据库中的关键中的重点。那么在NoSql中事务处理适合吗?通常情况下,面向聚合的数据库不支持跨越多个聚合的ACID事务。取而代之的是,每次只能在一个聚合结构上执行原子操作,也就是说。如果我们想以原子方式操作多个聚合,那就必须自己写应用代码(这不是废话吗?)。
键值与文档数据模型
键值与文档数据库都是面向聚合的。这些数据库主要通过聚合来构建,且这两类数据库都包含大量的聚合,每个聚合中都包含获取数据所用的键或ID。
两种数据模型的区别:
键值数据库:聚合不透明(数据结构不知道其内部实现细节即可以为外部程序使用)。基本上都是通过键来搜索聚合内容,查询得到全部数据,不能仅仅查询或获取其中的一部分。优势在于:聚合中可以存储任意数据,数据库可能会限制聚合的总大小。
文档数据库:可以看到其结构。提交查询的关键词往往是基于文档的内容,可以查询并获取其中一部分数据,它也许是通过键也许不是通过键获取数据。优势:数据库中虽然限制了其中存放的内容,定义了自己的结构与数据类型,这样的好处,可以更灵活的访问数据。
列族存储模型
早期NoSql数据库是Google的BigTable。BigTable模型的出现影响后来的HBase和Cassandra等NoSql 数据库。在起先,大部分数据库都以行为单元存储数据,尤其在需要提高写入性(写入操作多)的情况下。然而,有些情况下写入操作执行的很少,却常常需要一次性读取行中的很多列。在这种情况下,将所有行的某一组列作为基本数据存储单元,效果会更好。于是“列存储数据库”的命名就来了。
BigTable和它的继承者都遵循“以一组列(就是列族)来存储”的概念。后来大部分的NoSql数据库都可以称为“列族数据库”或“列式数据库”==“图数据库”。
理解列族模型最好的方式可以将其视为两级聚合结构(two-level aggregate structure)。与“键值存储”相同,第一个键通常代表行标识符,用来获取想要的聚合。其于“键值存储”的区别在于,其“行聚合”本身又是一个映射,其中包含更为详细的值。
列族数据库将列组织为列族。每一列都必须是某个列族的一部分,而且访问数据的单元也得是列。这样设计的前提是,某个列族中的数据经常需要一起访问。把聚合分为列族,让数据库将其视为行聚合内的一个数据单元。

图 列族结构表示客户信息
1、 面向行(row-oriented):每一行都是一个聚合(例如:ID-123456的顾客就是一个聚合),该聚合内部包含一些有用数据块(客户信息、订单记录)的列族。
2、 面向列(column-oriented):每个列族都定义了一种记录类型(例如:客户信息),其中每行都表示一条记录。(这里的“行”,指上述图中的行,其实对应数据库中的列族)
面向列,反映了“列”在列族数据库中的重要性。由于数据库了解这些数据通常的分组方式,在存储及访问时利用此信息()。由于列族中可以随意添加列,所以在建模项目清单时,可以把其中每个项目都表示为单独的列。
总结:
上面,大致介绍了三种不同风格的面向聚合的数据模型。三者共同点:集群上运行,聚合是中心环节,因为数据库必须保证将聚合内的数据存放在同一个节点上。聚合是“更新”操作的最小数据单位,对事务控制来说,以聚合为操作单元,大小正好。