域实体跟踪数据的最佳实践?基类还是组合



大多数大型项目的一个常见方面是需要许多域实体上的通用跟踪数据。例如,在大多数大型项目中,跟踪许多域实体的以下属性:

DateTime DateCreated
User CreatedBy
DateTime LastModified
User LastModifiedBy

这些数据非常不言自明,这些数据用于跟踪谁在什么时候对Domain对象做了什么。

问题是,在为大型应用程序设计域模型时,处理这些跟踪数据的最佳方式是什么。

经典的方法是使用基类,然后让相关的域类从该基类继承。但这引发了我对组合而非继承的好感。我做过的大型项目越多,我就越拒绝立即继承,但这并不是说在某些情况下它是最好的选择,例如在这种情况下。另一种继承解决方案是使用接口,但尽管这种解决方案的耦合性较低,但我在域实体上没有看到很多使用这种方法的例子。

第二种方法是使用composition向每个域实体添加某种跟踪对象。唯一的问题是,必须特别指示数据层不要将它们表示为单独的表。这是一项小任务,但如果没有回报,就很难证明这是合理的。

处理跟踪数据的最后一种方法是将数据层配置为透明地执行此操作。我认为使用实体框架可能可以做到这一点,但由于过去没有实施此解决方案,这将是最耗时的前期解决方案。很难预见这种解决方案是否值得这么麻烦。

虽然这个问题看起来很客观,但这实际上是大多数大型项目必须以某种方式处理的一项常见任务。

设计域模型和/或跟踪元数据的大型项目的最佳方式是什么?

最佳实践通常是主观的,它可以解决多少问题就造成多少问题。你应该什么时候继承?你应该什么时候作曲?学者们花了数年时间争论问题的细节。具有简单接口的基本继承是实用和有效的。如果它是所有实体的标准功能,那么继承可能是更好的选择。

我有一个带有审核属性的基类,并为这些属性实现了一个接口。我用以下代码截获了对context.SaveChanges()的调用。它很简单而且有效。如果任何被跟踪的实体没有实现IAudit接口,它可能会被扩展为失败。

    public override int SaveChanges()
    {
        var entities = this.GetChangedAuditDataEntities();
        foreach (var entity in entities)
        {
            this.SetModificationInfo(entity);
        }
        return base.SaveChanges();
    }
    private IEnumerable<IAuditData> GetChangedAuditDataEntities()
    {
        return (
            from entry in _context.ChangeTracker.Entries()
            where entry.State != EntityState.Unchanged
            select entry.Entity)
            .OfType<IAuditData>();
    }
    private void SetModificationInfo(IAuditData entity)
    {
        entity.lastModifiedBy = _currentUser.Name;
        entity.lastModified = System.DateTime.Now;
    }

这是一个不会自动得到"正确"答案的问题。如果Jon Skeet回答这个问题,那么这将被认为是最佳实践。唯一的另一个正确答案必须确认你的特定偏见,或者至少要达到正确的智力水平。

几年前,继承盛行,"价值构成高于继承"的咒语是众多反对者之一。很好,但继承有它的位置。

我建议,任何具有许多相同类型对象的体系结构层,例如域对象("域对象"一词意味着公共层(,都可以通过具有公共基类来大大增强。System.Object就是一个很好的例子。我再举一个例子。当我们为解决方案定义异常装饰器时,我们决定扩展ToString((方法来创建一个唯一标识对象的值。

public override string ToString()
{
    if (this is IAuditData)
    {
        IAuditDataidentifiable = this as IAuditData;
        return string.Format(@"{0} {{ id: {1}, ETag: {2} }}",
            identifiable.GetType().Name,
            identifiable.id,
            identifiable.ETag);
    }
    else return base.ToString();
}

7行代码-实用、简单、有效。从你的问题来看,你听起来完全反对必须由许多烧伤的手指继承的遗产。我也是;-(但我仍然认为,在这种情况下,这是更好的选择。

我喜欢qujck在他的回答和评论中所说的,但我想补充一点,如果跟踪信息代表一个基础设施概念,我会把它们放在负责审计或日志记录等的某个基础设施层中。。,否则,如果它们是域概念,如果它们属于域模型层,我真的很想保留它们,在这种情况下,我会使用一个复杂的对象(值对象(来表示它们,并减轻我自己在创建被跟踪对象时手动设置它们的负担,我的观点是集中变更,并将概念保留在它们所属的位置。

最新更新