DDD 中具有独立的领域模型和持久化模型

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

  1. 有1个既作为域模型又作为持久性模型的类

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

现在我知道意见1)被认为是简化了领域和持久模型之间没有太多区别的小型解决方案,但是我认为它打破了单一责任原则,并且当 ORM 的约定干扰 DDD 时引入了很多问题。

令我惊讶的是,有大量的代码示例说明如何实现意见1)。但是还没有找到一个单独的例子来说明如何实现意见2以及如何映射这两个对象。(可能有这样的例子,但我没有找到一个 C # 1)

因此,我试图自己实现一个示例,但我不确定这是否是一个好方法。

假设我有一个票务系统,票务有到期日期:

/// <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;
}
}
}

使用实体框架作为 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. 我的模型映射方法有什么问题吗?有没有什么我没注意到的东西会在解决方案生长时引起问题?

11076 次浏览

Are there any reasons opinion 1) would be preferred to opinion 2) other than speeding up development and reusing code.

Option 1 is just because of pure laziness and imagined increased development speed. It's true that those applications will get version 1.0 built faster. But when those developers reach version 3.0 of the application, they do not think it's so fun to maintain the application due to all compromises that they have had to do in the domain model due to the ORM mapper.

Are there any issues in my approach of mapping the models? Is there something I missed that would bring up issues when a solution grows?

Yes. The repository should be responsible of hiding the persistence mechanism. It's API should only work with domain entities and not persistence entities.

The repository is responsible of doing conversions to/from domain entities (to be able to persist them). A fetch method typically uses ADO.NET or an ORM like Entity Framework to load the database object/entity. Then convert it to the correct business entity and finally return it.

Otherwise you would force every service to have knowledge about persistence AND working with your domain model, thus having two responsibilities.

If you work with application services per the DDD definition you will probably want to look at the Command/Query separation pattern which can be a replacement of the application services. The code gets cleaner and you also get a much more lightweight API wrapping your domain model.

Are there any reasons opinion 1) would be preferred to opinion 2) other than speeding up development and reusing code.

I can see a big one (opinionated stuff ahead) : there is no "Persistence Model". All you've got is a relational model in your database, and a Domain object model. Mapping between the two is a set of actions, not data structures. What's more, this is precisely what ORM's are supposed to do.

Most ORM's now support what they should have provided from the start -- a way to declare these actions directly in code without touching your domain entities. Entity Framework's fluent configurations for instance allow you to do that.

You may be under the impression that no persistence model = violating SRP and trampling on DDD, because many implementations you can find out there do. But it doesn't have to be like that.

I got into this dilemma this year in a big project I was working at and it was a really tough decision to make... I would like to talk about this topic during hours, but I'll resume my thoughts for you:

1) Persistence and Domain model as the same thing

If you are in a new project with a database designed from zero for it I would probablly suggest this option. Yes, the domain and your knowledge about it will change constantly and this will demand refactoring that will affect your database, but I think in most cases it's worth it.

With Entity Framework as your ORM you can almost keep your domain models entirely free of ORM concerns using fluent mappings.

Good parts:

  • Fast, easy, beautiful (if the database is designed for that problem)

Bad parts:

  • Maybe the developers starts to think twice before to do a change/refactoring in the domain fearing that it will affect the database. This fear is not good for the domain.
  • If the domain starts to diverge too much from the database you will face some difficulties to maintain the domain in harmony with the ORM. The closer to the domain the harder to configure the ORM. The closer to the ORM the dirtier the domain gets.

2) Persistence and Domain model as two separated things

It will get you free to do whatever you want with your domain. No fear of refactorings, no limitations provinients from ORM and database. I would recomend this approach for systems that deal with a legacy or bad designed database, something that will probably end messing up your domain.

Good parts:

  • Completely free to refactor the domain
  • It'll get easy to dig into another topics of DDD like Bounded Context.

Bad parts:

  • More efforts with data conversions between the layers. Development time (maybe also runtime) will get slower.

  • But the principal and, believe me, what will hurt more: You will lose the main beneffits of using an ORM! Like tracking changes. Maybe you will end up using frameworks like GraphDiff or even abandon ORM's and go to the pure ADO.NET.

Are there any issues in my approach of mapping the models?

I agree with @jgauffin: "it's in the repository that the mapping should take place". This way your Persistence models will never get out from the Repository layer, in preference no one should see those entities (unless the repository itself).