如何跟踪在DDD中对对象进行更改的最后一个用户?

时间:2012-10-23 22:34:31

标签: c# design-patterns domain-driven-design n-tier-architecture

我正在尝试实施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私有化,Base类的setter被保护。对对象所做的每个更改都需要通过将(User changedBy)作为参数的方法来完成。

非常安全,但由于用户可能会对对象进行6次更改,因此我必须为每个对象提供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中的persistance层或repository.SaveChanges(User changedBy)等存储库中执行此操作。我,或实施此部分的人,可能会忘记这一点,它会使对象处于无效状态......

public SomeRepository
    {
       public void Update (User changedBy)
       {...}
    }

能够看到谁最后改变了一个实体是很正常的,但是我没有看到它实现DDD的任何好例子。你是怎么做到的?

更新 回应jgauffins解决方案: 谢谢,Base实际上实现了一个接口,并且我非常简单地说明不要重载信息。我觉得它不优雅因为每个方法都需要我的用户对象作为每个变化的参数,我突然不得不让所有的setter私有化......

把它放在回购中可以完成这项工作,但上面的论点是有效的,我没有想到这一点,所以这正是我问的原因。我实际上正在计划一个可能需要更改某些对象的服务。

这是一个很大的变化,改变了数以百计的方法和属性,所以我想要保持真实。而且我最终会设置一些方法来设置我在this post中提到的一个字段,在那里我不需要一个方法因为“set”描述足以让用户知道改变的作用......然而,我的直觉表明你是对的,只是想知道是否有改变方法来做同样的事情。

最终更新:

阅读所有评论,问题和建议的解决方案我意识到我可能不得不重新思考我如何查看LastChangedBy和LastChangedDate字段。对于某些对象,这实际上与我认为属于域的内容有关。对于其他许多人来说,这真的是我正在寻找的审计。我没有意识到差异。

即。 Document对象可以由创建者以外的其他人更改,并与某些行为(通知创建者等)相关联。在这些情况下,并不是最后一个人改变了我感兴趣的文档对象,但它是最后一个编辑文档内容的人。

我没有将它与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不是真正我需要在我的域中设置或跟踪的对象中的一个,但它可以被重写为更加特殊,如上所述。

2 个答案:

答案 0 :(得分:3)

我从Thread.CurrentPrincipal开始。但是,如果您要切换到异步处理(例如使用我描述here之类的命令),以后就无法使用Thread.CurrentPrincipal

但首先是一个根本问题:

public abstract class Base
{
   public User LastChangedBy { get; protected set; }
   public DateTime LastChangedDate { get; protected set; }
}

那不是基类。它不会添加任何功能。您应该改为使用名为ITrackChanges或类似名称的接口。

public SomeObject
{
   .....
   SomeBehaviorThatChangesObject(User changedBy, ...)
   AnotherBehaviorThatChangesObject(User changedBy, ...)
}

这就是我做事的方式。为什么你认为它不优雅?管理员或后台服务可以使用该方法代表用户执行操作。

<强>更新

  

我觉得它不优雅,因为每个方法都需要我的用户对象作为每个更改的参数

好。这是一项业务要求,对吗?您必须更改LastChangedBy属性。因此必须提供。

DDD中不禁止公共设置者。您可以使用它们,但在这种情况下,必须为每次更改更新LastChangedByLastChangedDate。并且该逻辑应该由模型本身强制执行,而不是由调用代码强制执行。因此,不可能使用公共设定者。

答案 1 :(得分:2)

...或者您可以将用户设置为Thread.CurrentPrincipal,而不是将其作为任何界面的一部分。

这适用于任何值得它的身份模型,无论是在具有自定义或AD身份验证的网站或桌面应用上。

要进行扩展,如果未设置用户,您的存储库等可以抛出,并且您仍然可以实现角色或基于声明的权限,以根据用户的身份根据用户的身份阻止/允许特定类型的更新