在DDD中具有单独的域模型和持久性模型

时间:2014-07-11 17:58:36

标签: c# orm architecture domain-driven-design domain-model

我一直在阅读有关域驱动设计以及如何使用代码优先方法生成数据库的方法。根据我的阅读和研究,围绕这个主题有两种观点:

  1. 有1个同时作为域模型和持久性模型的类

  2. 有2个不同的类,一个实现域逻辑,另一个用于代码优先方法

  3. 现在我知道意见1)据说可以简化域和持久性模型之间没有太多差异的小解决方案,但我认为它打破了单一责任原则,并且当ORM的约定干扰时会引入很多问题DDD

    令我感到意外的是,有很多关于如何实施意见的代码示例1)。但是还没有找到一个如何实现意见的例子2)以及如何映射这两个对象。 (可能有这样的例子,但我找不到C#)

    所以我尝试自己实现一个例子,但我不确定这是否是一个很好的方法。

    假设我有一个票务系统,票证有到期日。我的域名模型将如下所示:

    /// <summary>
    /// Domain Model
    /// </summary>
    public class TicketEntity
    {
        public int Id { get; private set; }
    
        public decimal Cost { get; private set; }
    
        public DateTime ExpiryDate { get; private set; }
    
        public TicketEntity(int id, decimal cost, DateTime expiryDate)
        {
            this.Id = id;
            this.Cost = cost;
            this.ExpiryDate = expiryDate;
        }
    
        public bool IsTicketExpired()
        {
            if (DateTime.Now > this.ExpiryDate)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
    

    使用Entity Framework作为ORM的持久性模型看起来几乎相同但随着解决方案的增长,情况可能并非如此

    /// <summary>
    /// ORM code first Persistence Model
    /// </summary>
    public class Ticket
    {
        [Key]
        public int Id { get; set; }
    
        public decimal Cost { get; set; }
    
        public DateTime ExpiryDate { get; set; }
    }
    

    到目前为止,一切看起来都很棒。现在我不确定哪个是从存储库中获取Ticket持久性模型的最佳位置,以及如何将其映射到TicketEntity域模型

    我在应用程序/服务层完成了这项工作。

    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();
        }
    }
    

    我的问题是:

    1. 是否有任何理由意见1)除了加快开发和重用代码外,更倾向于意见2)。

    2. 我的模型映射方法有什么问题吗?当解决方案增长时,我是否遗漏了会引发问题的事情?

3 个答案:

答案 0 :(得分:22)

  

是否有任何理由意见1)除了加快开发和重用代码之外,更倾向于意见2)。

选项1只是因为纯粹的懒惰和想象的增加的开发速度。确实,这些应用程序将更快地构建版本1.0。但是当这些开发人员达到应用程序的3.0版本时,他们认为维护应用程序并不是很有趣,因为由于ORM映射器,他们必须在域模型中做出妥协。

  

我的模型映射方法是否存在任何问题?当解决方案增长时,我是否遗漏了会引发问题的事情?

是。存储库应负责隐藏持久性机制。它的API应该只适用于域实体而不是持久性实体。

存储库负责与域实体进行转换(以便能够持久化它们)。 fetch方法通常使用ADO.NET或ORM(如Entity Framework)来加载数据库对象/实体。然后将其转换为正确的业务实体,最后将其返回。

否则,您将强制每个服务都了解持久性和使用您的域模型,因此有两个职责。

如果按照DDD定义使用应用程序服务,则可能需要查看可替代应用程序服务的命令/查询分隔模式。代码变得更清晰,您还可以获得包含域模型的更轻量级的API。

答案 1 :(得分:10)

今年我在一个正在进行的大项目中遇到了这个困境,这是一个非常艰难的决定......我想在几个小时内谈论这个话题,但我会为你重新开始我的想法:

1)持久性和域模型是一样的

如果你在一个新项目中,数据库设计为零,我可能会建议这个选项。是的,域名和您对它的了解会不断变化,这将要求重构会影响您的数据库,但我认为在大多数情况下它是值得的。

使用Entity Framework作为您的ORM,您可以几乎使用fluent mappings使您的域模型完全免于ORM问题。

好的部分:

  • 快速,简单,美观(如果数据库是针对该问题而设计的)

错误的部分:

  • 也许开发人员开始三思而后行在域中进行更改/重构,担心它会影响数据库。这种担心对域名不利。
  • 如果域名从数据库开始出现太多分歧,那么维护域名与ORM协调将面临一些困难。越接近域,配置ORM就越困难。越接近ORM,域名越脏。

2)持久性和域模型为两个分开的东西

它可以让您随意使用您的域名做任何事情。不用担心重构,也不担心ORM和数据库的限制。对于处理遗留或设计糟糕的数据库的系统,我会推荐这种方法,这可能会导致你的域名搞乱。

好的部分:

  • 完全免费重构域名
  • 很容易深入研究DDD的其他主题,如Bounded Context

错误的部分:

  • 在图层之间进行数据转换的工作量更大。开发时间(也许也是运行时)会变慢。

  • 但校长,相信我,会有什么伤害更多:你将失去使用ORM的主要好处!tracking changes。也许你最终会使用像GraphDiff这样的框架,甚至放弃ORM,转到纯ADO.NET。

  

我的模型映射方法是否存在任何问题?

我同意@jgauffin:“它应该在存储库中进行映射”。这样,您的Persistence模型永远不会从Repository层中退出,首选任何人都不应该看到这些实体(除非存储库本身)。

答案 2 :(得分:3)

  

是否有任何理由意见1)会优先考虑意见2)   除了加快开发和重用代码之外。

我可以看到一个很大的问题(未来的看法):没有&#34;持久性模型&#34;。您所拥有的只是数据库中的关系模型和域对象模型。两个之间的映射是一组操作,而不是数据结构。更重要的是,这正是ORM应该做的事情。

大多数ORM现在支持他们应该从一开始就提供的内容 - 一种直接在代码中声明这些操作而不触及域实体的方法。例如,实体框架的流畅配置允许您这样做。

您可能会认为没有持久性模型=违反SRP并践踏DDD,因为您可以找到许多实现。但它并非必须如此。