DDD - 实体状态转换

时间:2010-02-10 23:28:48

标签: c# java .net design-patterns domain-driven-design

考虑以下简化示例:

public class Ticket
{
   public int Id;
   public TicketState State;

   public Ticket()
   {
      // from where do I get the "New" state entity here? with its id and name
      State = State.New;
   }

   public void Finished()
   {
      // from where do I get the "Finished" state entity here? with its id and name          
      State = State.Finished;
   }
}

public class TicketState
{
   public int Id;
   public string Name;
}

类状态直接在域对象票证中使用。在故障单生命周期的后期可能会设置其他状态。

故障单将持久保存到Ticket表以及TicketState。因此,在数据库中,票证将具有票证状态表的外键。

在我的实体中设置适当的状态时,如何从数据库加载状态实例?我是否必须将存储库注入实体?在这种情况下,我是否需要使用像城堡这样的框架?或者是否有更好的解决方案,可能是从国外传递状态?

public class Ticket
{
   //...
   public ITicketStateRepository stateRep; //<-- inject

   public Ticket()
   {
      State = stateRep.GetById(NEW_STATE_ID);
   }
   //...
}

有最佳做法吗?到目前为止,我没有使用任何依赖注入框架或任何东西,并保持任何持久性的东西超出我的域..

另一个问题:

public class Ticket
{
   //...

   public Ticket(NewTicketState newTicketState)
   {
      State = newTicketState;
   }
   public void Finished(FinishedTicketState finishedTicketState)
   {
      State = finishedTicketState;
   }
   //...
}

3 个答案:

答案 0 :(得分:4)

Ticket没有对存储库的引用。它与TicketState具有一对一的关系,TicketRepository只是执行JOIN并将值映射到Ticket。

当我创建模型对象时,我通常不会让他们知道它们是否是持久的,因此它们不会注入存储库。存储库处理所有CRUD操作。

有些人反对这一点,说它会导致anemic domain model;也许你就是其中之一。如果是这种情况,请将存储库注入Ticket对象,但只需要求它执行JOIN并返回填充了其状态的Ticket。插入或更新时,您必须将两个表修改为一个工作单元,因此请务必打开事务。

我喜欢在域模型对象之外使用CRUD操作的原因是它通常不是唯一参与用例或事务的域对象。例如,您的简单“买票”用例可能会有一个Ticket对象,但也可能还有一些其他对象涉及预订和座位以及总帐和行李库存以及各种其他事项。您真的希望将多个模型对象作为单个工作单元持久化。只有服务层才能知道模型对象何时自行运行,何时它是更大,更宏伟的计划的一部分。

更新:

我不喜欢用DAO注入模型对象以便它可以处理持久性任务的想法的另一个原因是层的废弃和它引入的循环依赖。如果保持模型清除对持久性类的任何引用,则可以使用它们而无需调用其他层。这是一种单向依赖;持久性知道模型,但模型不知道持久性。

将持久性注入模型,它们彼此依赖。没有另一个,你永远不能使用或测试任何一个。没有分层,没有分离关注点。

答案 1 :(得分:1)

这个答案希望继续来自duffymo's。

在世界的DDD视图中,您的TicketState是一个实体,它是Ticket聚合的一部分(票证是聚合根)。

接下来,您的TicketRepository处理Tickets和TicketStates。

当您从持久层检索故障单时,您允许TicketRepository从数据库中检索状态并在故障单上正确设置它。

如果您正在新建一张票,那么(我认为)您还不需要触摸数据库。当票证最终被持久化时,您可以获取票证的新状态并正确保留它。

您的域类不需要了解处理状态的数据库模型,他们应该只需要了解状态的域模型视图。然后,您的存储库负责此映射。

答案 2 :(得分:0)

对我来说,一个表示数据库中状态(或任何持久性介质)的简单键值对不需要在域中建模。在域中,我将使TicketState成为枚举,并让ITicketRepository负责知道如何将其映射到数据库模式的要求。

在故障单存储库中,您可以拥有一个在TicketState上键入的故障单状态ID缓存,该缓存从数据库延迟加载到静态变量(只有一种方法)。票证存储库会将Ticket.State值映射到该缓存中的ID以进行插入/更新。

namespace Domain {
  public class Ticket {
    public Ticket() { State = TicketStates.New; }
    public void Finish() { State = TicketStates.Finished; }
    public TicketStates State {get;set;}
  }

  public enum TicketState { New, Finished }
}

namespace Repositories {
  public class SqlTicketRepository : ITicketRepository {
    public void Save(Ticket ticket) {
      using (var tx = new TransactionScope()) { // or whatever unit of work mechanism
        int newStateId = TicketStateIds[ticket.State];
        // update Ticket table with newStateId
      }
    }
  }

  private Dictionary<TicketState, int> _ticketStateIds;
  protected Dictionary<TicketState, int> TicketStateIds{
    get {
      if (_ticketStateIds== null) 
        InitializeTicketStateIds();
      return _ticketStateIds;
    }
  }

  private void InitializeTicketStateIds() {
    // execute SQL to get all key-values pairs from TicketStateValues table
    // use hard-coded mapping from strings to enum to populate _ticketStateIds;
  }
}