【问题标题】:How is Change Tracking an issue when separating the Domain Model and Persistence Model?分离领域模型和持久性模型时,变更跟踪是如何成为问题的?
【发布时间】:2018-06-13 07:51:29
【问题描述】:

我在这里阅读了这个问题:Having Separate Domain Model and Persistence Model in DDD

并专门看这段代码:

public class ApplicationService
{
    private ITicketsRepository ticketsRepository;

    public ApplicationService(ITicketsRepository ticketsRepository)
    {
        this.ticketsRepository = ticketsRepository;
    }

    public bool IsTicketExpired(int ticketId)
    {
        Ticket persistanceModel = this.ticketsRepository.GetById(ticketId);
        TicketEntity domainModel = new TicketEntity(
            persistanceModel.Id,
            persistanceModel.Cost,
            persistanceModel.ExpiryDate);

        return domainModel.IsTicketExpired();
    }
}

此代码表示存在单独的域模型和持久性模型。我试图了解使用这种方法的局限性是什么。在整个互联网上,我读到了使用 NHibernate 时更改跟踪是一个问题,但我不明白为什么。更改跟踪是在域模型映射回持久性模型之后处理的。变更跟踪如何成为问题?更改跟踪如何成为问题的一个实际示例将对我有所帮助。

更新 请看下面的代码:

//Repository
public Ticket GetTicket(int ticketId)
{
    return this.ticketsRepository.GetById(ticketId);
}

我在应用程序服务中这样做:

//Application Service
Ticket ticket = applicationService.GetTicket(1);
ticket.Cost = .....
TicketEntity ticketEntity = AutoMapper.Map<TicketEntity>(ticket);
ticketEntity.DomainMethod();
ticket = AutoMapper.Map<Ticket>(ticketEntity);

Q1) ORM 的好处是否在此代码中丢失了,例如变更跟踪?请注意,持久性对象是从存储库返回的,然后映射到域对象,然后又回到同一个持久性对象。

Q2) NHibernate 如何跟踪更改,即它如何知道 Ticket(persistence object) 是数据库中的票据 1。我想这不仅仅是ID。

【问题讨论】:

  • 也许晚了,但是如果有人有兴趣看到一个使用 DDD 与单独的域和持久性模型的示例,同时仍然保持 EntityFramework 的更改跟踪功能的好处,您可以参考我写的这篇文章:Change Tracking while doing DDD源代码位于:GitHub 是的,我知道它不是 NHibernate,但它可能对 EntityFramework 的用户有所帮助。

标签: c# nhibernate domain-driven-design


【解决方案1】:

更改跟踪没有问题。领域与持久性的融合是。 “域模型”主要是一些易于映射到表的数据结构。在许多领域中,您可能会处理 99% 的数据结构并附加一些规则。在这些情况下,您的域模型看起来与持久性模型几乎相同。

但在更抽象的层面上,领域模型主要建模业务行为,状态(数据)只是一个工件。此外,它从业务角度(功能)看待事物。

持久性模型是关于存储状态,即以易于检索的方式结构化的数据。对于涉及许多概念及其特定模型和用例特定业务规则的复杂功能域,生成的模型与持久状态有很大不同。

ORM如何跟踪变化只是一个实现细节,与DDD无关,但是如果领域足够丰富,那么简单的CRUD解决方案,尤其是领域模型的心态 = state+behaviour aka classes 成为一个障碍。有或没有 ORM。

对于 Domain = 98% Persistence Models 的应用,没有问题,你可以使用任何你想要的 ORM。

【讨论】:

  • 请查看此链接(其中一个)。它说:“您无法从内置的更改跟踪中受益”(orm)。你能对此发表评论吗?谢谢。
  • @w0051977 更改跟踪是 ORM 的一项功能。例如,如果您使用 CQRS,则永远不需要该功能。与事件溯源相同。当您处理主要是 CRUD 域时,ORM 效果最好。
  • +1 参考 CQRS 和事件溯源。您能解释一下为什么您不需要与他们一起进行变更跟踪吗?
  • 使用 CQRS 我相信这是因为您有返回持久性对象的存储库和返回域对象的存储库。那是对的吗?如果你想从数据库中提取,对对象执行一些领域逻辑,然后写回数据库。我想我的编辑在这种情况下是相关的?
【解决方案2】:

@MikeSW 回答的大部分细节都是正确的;我只不同意变更跟踪。我的答案更多是关于 NHibernate 而不是 DDD。

是的,更改跟踪将是一个问题,但这取决于ISession 的管理方式。此外,不仅更改跟踪,它还会影响 NHibernate 的其他功能,如会话级缓存、延迟加载等。

让我们假设ISession 在请求级别进行管理,即每个请求一个ISession。下面提到的所有活动都是一个请求的一部分。

public TicketEntity GetTicket(int ticketId)
{
    Ticket persistanceModel = this.ticketsRepository.GetById(ticketId);
    TicketEntity domainModel = new TicketEntity(
        persistanceModel.Id,
        persistanceModel.Cost,
        persistanceModel.ExpiryDate);

    return domainModel;
}

public void SaveTicket(TicketEntity ticketEntity)
{
    Ticket ticket = //Here, you have to map TicketEntity to Ticket
    this.ticketsRepository.Save(ticket);
}

现在,以下是同一请求中应用程序某处的代码:

TicketEntity ticketEntity = applicationService.GetTicket(1);
ticketEntity.Cost = .....
.....
.....
applicationService.SaveTicket(ticketEntity);

NHibernate 有能力跟踪Ticket 中发生的变化,但这种能力在这里没有用。从GetTicket 返回时Ticket 丢失,在SaveTicket 时创建新的Ticket。即使ISession 处于请求级别并且能够看到发生的更改,NHibernate 的更改跟踪功能也根本没有使用。

以下代码(绕过域模型)将正确跟踪更改:

public Ticket GetTicket(int ticketId)
{
    return this.ticketsRepository.GetById(ticketId);
}

以下是获取和修改Ticket的方法:

Ticket ticket = applicationService.GetTicket(1);
ticket.Cost = .....
.....
.....

现在,你不要打电话给SaveTicket;取而代之的是 Flush ISession 在应用程序中检测到 EndOfRequest 的位置。

在这种情况下,NHibernate 的更改跟踪会跟踪对 Ticket 所做的更改并自动刷新这些更改。

通过将持久性模型翻译到域模型,我们绕过了 NHibernate 的这种能力,因为持久性模型永远不会改变。

每种方法都有优点和缺点。参考this问题。

编辑:(用于您的更新

Q1):如果相同的持久性模型实例被修改并且对相同的ISession 可见,则新代码将受益于更改跟踪。在这种情况下,它还将利用会话级缓存。在使用 AutoMapper 进行映射时,NHibernate 将加载引用的实体(如果可能不需要)。不过,这取决于每个用例。

Q2):实际上,这应该是一个不同的问题,过于宽泛,无法在此答案中回答。无论如何,请参考this

【讨论】:

  • 我可以看到更改跟踪如何与您的第二种方法一起使用(其中存储库返回持久性对象而不是域对象)。我想我可以从存储库中返回一个持久性对象用于 CRUD 操作,并返回一个域对象用于域操作?
  • 我个人自己使用第一种方法(单独的域模型),当然域逻辑将进入域模型。使用第二种方法,即使我们从 NHibernate 的特性中受益,也存在许多其他问题,包括域逻辑应该去哪里。请参阅我在回答中提到的问题。
  • 谢谢,关于第二季度;我昨天看到了你的链接问题。我试图了解 NHibernate 如何知道我的对象:票证与 ISession 中的 Object[] 相关。
  • @w0051977:我对此进行了一些研究。它是持久性模型的标识符属性(用id 属性装饰)。 NHibernate 不允许将具有相同标识符的两个(或更多)持久模型附加到相同的ISession;在这种情况下,它会抛出 NonUniqueObjectException。这还涉及许多其他概念,例如“脏检查”、“持久性上下文”等。持久性上下文根据ISession 维护。在一个答案中涵盖所有这些内容太广泛了。
【解决方案3】:

顺便说一句,我不建议将域模型类与持久性对象分开。

您可能想看看 Vaughn Vernon 为他在 DOT.NET 上的应用程序做了什么

他是著名的《实施领域驱动设计》(IDDD) 一书的作者。我向所有认真对待 DDD 的开发者推荐的必读。

https://github.com/VaughnVernon/IDDD_Samples_NET/tree/master/iddd_identityaccess/Domain.Model/Identity

【讨论】:

  • 你能解释一下为什么吗?您正在处理一个非常复杂的项目吗?
  • 当我第一次尝试将域对象与持久性对象分开时,我在编写 Translators 类(或转换器)以在层之间来回转换时遇到了很多麻烦。我最近在Java上工作的项目,Hibernate可以通过XML映射来映射实体。我知道它并不性感,但至少它尊重域对象,不使用大量注释进行入侵。我也赞成拥有“纯”域对象,例如不受持久性信息的污染,这就是为什么我喜欢使用 XML 映射来映射我的域对象的六边形架构。
  • 当我的意思是麻烦时,我的意思是额外的工作,额外的测试编写,更多的维护,根本没有增加商业价值。
  • 我不同意“拥有单独的类需要更多的工作,因为要编写更多的代码和测试”。编写代码并不慢,复杂性不仅仅以代码行数来衡量。减慢开发和维护速度的原因是糟糕的代码设计,您最终会在一个具有多种职责和依赖关系的类中编写数千行代码。这最终会使代码更难理解、测试和修改。
  • @AdrianHristov:同意,这实际上导致了另一个问题。越接近 DDD,就越难利用 ORM 特性。我个人认为这是一种脱节。一个人必须选择一个而不是另一个,并根据手头的每个用例尝试平衡两者。
猜你喜欢
  • 1970-01-01
  • 2012-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-15
  • 1970-01-01
  • 1970-01-01
  • 2018-08-30
相关资源
最近更新 更多