看着NDC12呈现从吉米博加德(“手工艺邪恶的领域模型”后http://ndcoslo.oktaset.com/Agenda ),我是游荡如何坚持那种域模型。
这是从演示示例类:
public class Member
{
List<Offer> _offers;
public Member(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
_offers = new List<Offer>();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public IEnumerable<Offer> AssignedOffers {
get { return _offers; }
}
public int NumberOfOffers { get; private set; }
public Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc)
{
var value = valueCalc.CalculateValue(this, offerType);
var expiration = offerType.CalculateExpiration();
var offer = new Offer(this, offerType, expiration, value);
_offers.Add(offer);
NumberOfOffers++;
return offer;
}
}
所以有包含在该域模型的一些规则:
- 会员必须有姓和名
- 报价数量不能改变外
- 会员负责创建新的报价,计算其价值和分配
如果如果试图将此映射到像实体框架或NHibernate的一些ORM,它不会工作。 那么,什么是对这种模型与ORM映射到数据库最好的方法?
例如,如何从数据库加载AssignedOffers如果没有二传手?
唯一的事情,它对于我来说是使用命令/查询体系结构意义:查询始终与DTO做的结果,而不是域实体,并命令在域模型来完成。 此外,事件采购是非常适合的领域模型的行为。 但这种CQS架构不适合,也许每一个项目,特别是棕地。 或不?
我知道这里类似的问题,但找不到具体的例子和解决方案。
其实,这是一个很好的问题,这是我已经考虑。 这是潜在的困难,以创建一个完全封装(即无属性setter)正确的域对象,并使用ORM直接建立域对象。
在我的经验,有解决这个问题的3种方式:
- 正如卢卡已经提到,NHibernate的支持映射到私人领域,而不是财产setter方法。
- 如果使用EF(我不认为支持以上),你可以使用Memento模式来恢复状态,以你的域对象。 如您使用实体框架来填充“纪念品”的对象,你的域实体接受设置自己的私人领域。
- 正如你所指出的那样,使用CQRS与事件采购解决了这个问题。 这是我的首选各具特色的完美封装领域对象,也有事件采购的全部增值收益的方法。
旧线。 但是,有一个更近的职位 (2014年底)由沃恩弗农,解决只是这种情况下,特别是关于实体框架。 鉴于我有点难以找到这些信息,也许可以帮助它张贴在这里为好。
基本上后倡导的Product
结构域(汇总)反对裹ProductState
对所关注的事物的“数据包”边EF POCO数据对象。 当然域对象仍然会通过添加特定域的方法/存取所有的富领域的行为,但它会采取内部数据对象时,它必须获得/设置其属性。
从后复制片段直:
public class Product
{
public Product(
TenantId tenantId,
ProductId productId,
ProductOwnerId productOwnerId,
string name,
string description)
{
State = new ProductState();
State.ProductKey = tenantId.Id + ":" + productId.Id;
State.ProductOwnerId = productOwnerId;
State.Name = name;
State.Description = description;
State.BacklogItems = new List<ProductBacklogItem>();
}
internal Product(ProductState state)
{
State = state;
}
//...
private readonly ProductState State;
}
public class ProductState
{
[Key]
public string ProductKey { get; set; }
public ProductOwnerId ProductOwnerId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<ProductBacklogItemState> BacklogItems { get; set; }
...
}
库会为了从DB-坚持版本实例(负载)的实体实例使用内部构造。
在一位我可以补充自己,是很可能Product
领域对象应该多一个访问只是通过EF持久性的目的而弄脏 :在同是作为new Product(productState)
允许从数据库加载的域实体,相反的方式应通过类似被允许:
public class Product
{
// ...
internal ProductState State
{
get
{
// return this.State as is, if you trust the caller (repository),
// or deep clone it and return it
}
}
}
// inside repository.Add(Product product):
dbContext.Add(product.State);
对于AssignedOffers:如果你看一下代码,你会看到AssignedOffers从现场返回的值。 NHibernate的可以填充该字段是这样的:地图(X => x.AssignedOffers).Access.Field()。
使用CQS同意。
在做DDD第一件事情,你忽略了持续性的担忧。 所以这是一个持续关注ORM是tighlty连接到RDBMS。
一个ORM模型的持久性结构不是域。 基本上库必须“转换”接收的总根于一个或多个持久性实体。 该限界上下文是相当重要的,因为总根根据什么是你想完成以及变化。
比方说,你要保存的会员中分配一个新的报价的情况下。 然后你就会有这样的事情(当然这只是一种可能的情形)
public interface IAssignOffer
{
int OwnerId {get;}
Offer AssignOffer(OfferType offerType, IOfferValueCalc valueCalc);
IEnumerable<Offer> NewOffers {get; }
}
public class Member:IAssignOffer
{
/* implementation */
}
public interface IDomainRepository
{
void Save(IAssignOffer member);
}
接下来的回购将获得仅仅是为了改变NH实体所需的数据,这一切。
有关事件采购,我认为你必须看它是否适合你的域名,我看不出有任何问题,使用事件采购仅用于存储域聚合根而其余的(主要是基础设施)可以存储在普通的方式(关系表)。 我想CQRS让你在这个问题上很大的灵活性。