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

Adr*_*tov 46 c# architecture orm domain-driven-design domain-model

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

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

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

现在我知道意见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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用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; }
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切看起来都很棒 现在我不确定哪个是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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:

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

  2. 我的模型映射方法有什么问题吗?当解决方案增长时,我错过了哪些会带来问题?

jga*_*fin 24

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

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

我的模型映射方法有什么问题吗?当解决方案增长时,我错过了哪些会带来问题?

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

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

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

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

  • 嗨,我很高兴有人分享我的信念:)。我仍然有一些不清楚的事情:如果我有一个实体和一个值对象,但在数据库中我想将它们呈现为两个不同的表怎么办?例如“客户端”和“地址”,您可能有 2 个地址并将它们呈现为值对象,但将它们放在同一个数据库表中将导致非规范化。域和持久性模型(现在有两个)应该如何映射以及映射到哪里?因为通常我会有一个 `ClientsRepository` 和 `AddressesRepository` (2认同)
  • @jgauffin,+1“所以应该在存储库中进行映射。”。您能否确认您的意思是:1)存储库从数据库获取数据并将其放入持久对象中,2)在存储库方法内部;持久化对象映射到域对象,3) 域对象由方法返回。谢谢。 (2认同)

fab*_*tto 12

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

1)持久性和域模型是一回事

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

使用Entity Framework作为您的ORM,您几乎可以使用流畅的映射使您的域模型完全不受ORM问题的影响.

好的部分:

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

坏的部分:

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

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

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

好的部分:

  • 完全免费重构域名
  • 可以很容易地挖掘出像Bounded Context这样的DDD的另一个主题.

坏的部分:

  • 在层之间进行数据转换的更多努力.开发时间(也许还有运行时)会变慢.

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

我的模型映射方法有什么问题吗?

我同意@jgauffin:"它应该在存储库中进行映射".这样,您的Persistence模型永远不会从Repository层中退出,优先,没有人应该看到这些实体(除非存储库本身).

  • 第一个选项没有好的部分。甚至**“如果数据库是针对该问题而设计的”**,因为有一天一些 DBA 可能会说“为了速度,我要对这两个表之间的关系进行非规范化!!” 对于这个简单的更改,您必须重构您的**域模型**。持久性模型更改不应强制域模型更改,它是域驱动设计的最大支柱之一。 (2认同)
  • 是的@KeremBaydoğan,这是您在选择选项 1 时所面临的风险,因为我在该选项的不好的部分中指出了这一点(“如果域开始与数据库偏离太多……”)。但这可能(即使很少见)永远不会发生。即使它发生了,在它发生之前你也将拥有该选项的优势(主要是简单性)。就我个人而言,我不太喜欢这种选择,并且在大多数情况下可能不会鼓励它,但它是一个有效的选择,由您决定是否值得冒险。 (2认同)