BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

在领域驱动设计中对聚合进行设计与存储

| 作者 Jan Stenberg 关注 37 他的粉丝 ,译者 邵思华 关注 3 他的粉丝 发布于 2014年12月19日. 估计阅读时间: 3 分钟 | AICon 关注机器学习、计算机视觉、NLP、自动驾驶等20+AI热点技术和最新落地成功案例。

在使用领域驱动设计的过程中,使用者们对于如何创建设计良好的聚合(aggregate)这一模式始终知之甚少。在Vaughn Vernon近期发布的两篇文章中,他为读者介绍了组合聚合边界的相关指南,并且介绍了在对聚合进行存储的时候如何使用ORM的替代技术

《实现领域驱动设计》(Implementing Domain-Driven Design)一书的作者Vaughn为了简化聚合的组合,定义了四条他认为很有帮助的规则:

  • 在一致性边界之内确保不变性,这也暗示着在一个事务中应该只对一个聚合进行变更。
  • 设计尽量小的聚合,尽可能实现一个实体这样的最小聚合。
  • 只通过唯一标识的方式对其它聚合进行引用,意味着不应当存在在某个聚合中直接访问另一个聚合的情况出现。
  • 在一致性边界之外使用最终一致性方式。实现这一点可以使用领域事件进行通知,领域中的其它部分可以在一个新的事务之中对其它聚合进行变更。

Vaughn强烈建议首先从第二条规则开始实施,让每个实体成为一个聚合根,不存在任何例外的情况。接下来再为该聚合添加所有字段与属性,以使该聚合在创建时保证处于某个有效的状态下。

Vaughn所建议的下一步骤,是与领域专家进行协同工作,以找中哪些聚合是必须在同一个事务中进行变更的,以及哪些聚合是可以由某些变更进行触发的。Vaughn在这里提出了一点警告,领域专家都倾向于声明所有实体都是必须在同一个事务中进行变更的,而这种情况其实并不多见,很有可能是由于这些专家之前的工作经验都是与数据库密集型系统打交道所导致的。

Vaughn一直在寻找某种比对象-关系/映射(ORM)框架更好的方法对聚合进行保存,他曾考虑以JSON格式对聚合进行序列化,并将结果保存在某个文档数据库中。在对领域事件进行操作时,这些事件必须在聚合进行变更时保存在同一个事务中,否则一旦存储机制出错,整个系统的数据就有可能产生不不一致。但使用文档数据库的一个问题在于,多数文档数据库都不支持ACID的事务功能,对于那些无法接受不一致的系统来说,这实际上就断绝了使用文档数据库的可能性。因此Vaughn转而寻找某种支持事务与JSON的数据存储系统,目前他所找到的一个产品是PostgreSQL 9.4的beta版本,他已在一些小型示例中成功地应用它作为聚合的存储机制了。

Vaughn还撰写了关于《高效聚合设计》的一系列共三篇文章。

在早些时候,Julie Lerman也在文章中介绍了如何在边界上下文之间共享数据的话题。

查看英文原文:Designing and Storing Aggregates in Domain-Driven Design

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

翻译的不全,有误导的嫌疑 by Ethan Liu

翻译的不全,有误导的嫌疑,推荐大家还是看原文,vaughnvernon.co/?p=926

建议大家还是看原文 by Ethan Liu

建议大家还是看原文,聚合设计的部分翻译不全会误导人的

相比Aggregates,Reporsitory,我有另外一个实践——构建DomainContext by Martin Qian

我在Domain Framework中定义了IDomainContext接口,并实现了对Entity对象的基本访问与操作,比如Get,Find,Add,Delete等等。然后再用具体的持久化技术去实现IDomainContext,因为我项目使用的EntityFramework(简称EF),所以我就用EF对IDomainContext进行了实现。采用这种方式,你既避免了持久化技术对领域层的浸入,又可以彻底摆脱Aggregates和Repository所带来的困惑。下面是我基于LinQ定义的IDomainContext和EF版的简单实现(实际项目中会比这个强大),大家可以参考:
public interface IDomainContext
{
/// <summary>
/// Check if the context has been relased.
/// </summary>
bool IsDisposed { get; set; }

/// <summary>
/// Explicitly mark the entity has been added in the context
/// </summary>
/// <typeparam name="TEntity">entity type</typeparam>
/// <param name="entity">entity instance</param>
TEntity Add<TEntity>(TEntity entity) where TEntity : class;

/// <summary>
/// Explicitly mark the entity has been removed form the context
/// </summary>
/// <typeparam name="TEntity">entity type</typeparam>
/// <param name="entity">entity instance</param>
void Remove<TEntity>(TEntity entity) where TEntity : class;

/// <summary>
/// Get entity set
/// </summary>
/// <typeparam name="TEntity">entity type</typeparam>
/// <returns>Entity set supporting LinQ</returns>
IQueryable<TEntity> EntitySet<TEntity>() where TEntity : class;

/// <summary>
/// Get entity set
/// </summary>
/// <typeparam name="TEntity">entity type</typeparam>
/// <param name="predicate">predicate expression</param>
/// <returns>Entity set supporting LinQ</returns>
IQueryable<TEntity> EntitySet<TEntity>(Expression<Func><TEntity, bool>> predicate) where TEntity : class;

/// <summary>
/// Find entity by key values
/// </summary>
/// <typeparam name="TEntity">entity type</typeparam>
/// <param name="keyValues">key values</param>
/// <returns>if exists return the entity instance, otherwise return null</returns>
TEntity Find<TEntity>(params object[] keyValues) where TEntity : class;

}

public class DomainContextWithEF : De.Framework.Domain.IDomainContext, IDisposable
{
private DbContextEx _dbContext;
protected DbContextEx DbContext
{
get { return _dbContext; }
}


public DomainContextWithEF(DbContextEx dbContext)
{
_dbContext = dbContext;
}


/// <summary>
/// Add entity into the context
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <param name="entity">entity instance</param>
/// <returns>entity instance of added</returns>
public TEntity Add<TEntity>(TEntity entity)
{
return _dbContext.Set<TEntity>().Add(entity);
}



/// <summary>
/// Remove entity from the context
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <param name="entity">entity instance</param>
public void Remove<TEntity>(TEntity entity)
{
_dbContext.Set<TEntity>().Remove(entity);
}

/// <summary>
/// Obtain a set of entities that support LinQ to SQL syntax.
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <returns>Querable entities</returns>
public IQueryable<TEntity> EntitySet<TEntity>()
{
return _dbContext.Set<TEntity>();
}

/// <summary>
/// Obtain a set of filtered entities that supports LinQ to SQL statement.
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <param name="predicate">Filter condition</param>
/// <returns>Querable entities</returns>
public IQueryable<TEntity> EntitySet<TEntity>(System.Linq.Expressions.Expression<Func><TEntity, bool>> predicate)
{
return _dbContext.Set<TEntity>().Where(predicate);
}

/// <summary>
/// Find entity by entity key that can be a key or a composite key
/// </summary>
/// <typeparam name="TEntity">Entity type</typeparam>
/// <param name="keyValues">Key or composite key</param>
/// <returns>Entity instance or null</returns>
public TEntity Find<TEntity>(params object[] keyValues)
{
return _dbContext.Set<TEntity>().Find(keyValues);
}

/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose()
{
_dbContext.Dispose();
}
}

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

3 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT