我正在尝试实施领域驱动设计(DDD),感觉已经有所了解,但是我也遇到了一些问题。
在我的90%领域对象中,我想知道最后一个更改它的用户。我不需要完整的审计跟踪 - 对于我的需求来说,这是过度的。
所有我的类都实现了包含以下内容的抽象基类:
public abstract class Base
{
public User LastChangedBy { get; set; }
public DateTime LastChangedDate { get; set; }
}
选项1:遵循DDD原则,但不够优雅
永远不要让对象进入无效状态。
public abstract class Base
{
public User LastChangedBy { get; protected set; }
public DateTime LastChangedDate { get; protected set; }
}
public SomeObject
{
.....
SomeBehaviorThatChangesObject(User changedBy, ...)
AnotherBehaviorThatChangesObject(User changedBy, ...)
}
我需要将所有的setter设置为private,基类的setter设置为protected。每次修改对象都必须通过一个带有(User changedBy)参数的方法完成。
虽然非常安全,但由于用户可能会对对象进行六次更改,因此我必须为每个更改提供User对象。实际上,在我的领域模型中,几乎每个方法都需要提供这个对象...
选项2:
引入一个bool IsValid字段。在所有的setter中,我将IsValid设置为false。创建一个AcceptChanges(User changesAcceptedBy)方法来将这个字段设置为true。记得在存储对象之前始终检查对象是否有效。
public abstract class Base
{
public bool IsValid {get; protected set;}
public User LastChangedBy { get; protected set; }
public DateTime LastChangedDate { get; protected set; }
}
public SomeObject
{
public Object Propery{get; set{IsValid = false; ...}}
.....
SomeBehaviorThatChangesObject(...)
{
//change the object
IsValid = false;
}
}
选项3: 在我看来最实用但不是非常DDD
可以在UnitOfWork中的持久层或者像repository.SaveChanges(User changedBy)这样的仓库中完成。但是,我或者其他实现这部分的人可能会忘记这一点,导致对象处于无效状态...
public SomeRepository
{
public void Update (User changedBy)
{...}
}
“能够看到最后更改实体的人是很正常的,但我还没有看到任何好的例子来实现DDD。你怎么做到的?
更新 回应jgauffins的解决方案: 谢谢,Base确实实现了一个接口,并且我简化了很多内容,以免过多地负担信息。我感觉这不太优雅,因为每个方法都需要我的用户对象作为每次更改的参数,而且我突然间必须将所有的setter设置为私有的……
将它放在repo中可能会起到作用,但上面的论点是正确的,我没有想到这一点,所以这就是我问的原因。我实际上正在计划一个可能需要更改一些对象的服务。
这是一个很大的变化,改变了数百个方法和属性,所以我想要确定。而且我最终将得到相当多的方法来设置一个字段,就像我在这篇文章中提到的那样,我不需要一个方法,因为“set”已经足够描述用户知道改变的内容……然而我的直觉告诉我你是对的,只是想看看是否有其他的方式来做同样的事情。”
“最终更新:”
“阅读所有的评论、问题和建议解决方案,我意识到我可能需要重新思考我如何看待LastChangedBy和LastChangedDate字段。对于某些对象,这实际上与我认为属于域的某些东西有关。对于许多其他对象,我真正寻找的是审计。我没有意识到这种差异。”
“例如,文档对象可以被除创建者以外的人更改,这将与某些行为相关联(通知创建者等)。在这些情况下,我感兴趣的不是最后一个更改文档对象的人,而是最后一个编辑文档内容的人。”
“与其将此与LastChangedBy混合在一起,不如为LastEditedBy创建字段。如果需要跟踪每次更改,这甚至可以是编辑器列表。”
“当然,大部分时间这将是相同的用户作为LastChangedBy,但看看这种情况:创建者进去并更改属性以锁定文档以防止进一步编辑。那么文档就没有被真正编辑,但是我希望出于审计原因跟踪它。如果我用相同的字段来跟踪编辑,那么这样做是错误的。我可以这样做,因为现在的要求是创建者可以进去看到谁最后一次更改了文档,但这不是正确的解决方案。”
“为了实现审计,我做了以下几件事:”
“首先,我将我的基类分成需要实现的接口。LastChangedBy和LastChangedDate在IAuditable接口中定义,需要进行审计的对象必须实现该接口。”
“然后像这样覆盖DbContext.SaveChanges():”
public override int SaveChanges()
{
if(ChangeTracker.Entries<IAuditable>().Any())
throw new InvalidOperationException
(
"Tried to save changes on an object that is needs to be Audited. Please provide the User that makes the changes!"
);
return base.SaveChanges();
}
public int SaveChanges(User changedBy)
{
var entries = ChangeTracker.Entries<IAuditable>().Where(entry=>entry.State == EntityState.Modified);
foreach (var dbEntityEntry in entries)
{
dbEntityEntry.Entity.Audit(DateTime.Now, changedBy);
}
return base.SaveChanges();
}
当然,还有其他方法可以做到这一点,但这至少是一个开始。
现在,我意识到我的问题可能可以更好地表达,但是老实说,我没有意识到问题的一部分是我想要在不同对象上由于不同原因跟踪更改。上面文档的示例仅是更多对象中的一个,其中LastChangedBy并不是我需要在我的领域中设置或跟踪的真正内容,但可以按照上面的方式重写以变得更加具体。