Domain driven design 使用复杂的数据库避免实体中的存储问题

Domain driven design 使用复杂的数据库避免实体中的存储问题,domain-driven-design,entity-framework-core,Domain Driven Design,Entity Framework Core,我正在从事的项目涉及相当复杂的业务规则,因此我尝试应用DDD。不幸的是,我不得不使用一个无法摆脱的遗留数据库,而且我很难保持一个干净的域设计 假设某个实体具有某个ValueType作为主键,这是必需的。这可以在DDD中设计,如下所示: 公共类实体 { 公共实体(ValueType键) { 钥匙=钥匙; } public ValueType键{get;} } 现在,假设该键实际上存储为字符串表示形式,可以对其进行解析以构造ValueType。我可以这样做,使其与实体框架一起工作: 公共类实体 {

我正在从事的项目涉及相当复杂的业务规则,因此我尝试应用DDD。不幸的是,我不得不使用一个无法摆脱的遗留数据库,而且我很难保持一个干净的域设计

假设某个实体具有某个ValueType作为主键,这是必需的。这可以在DDD中设计,如下所示:

公共类实体
{
公共实体(ValueType键)
{
钥匙=钥匙;
}
public ValueType键{get;}
}
现在,假设该键实际上存储为字符串表示形式,可以对其进行解析以构造ValueType。我可以这样做,使其与实体框架一起工作:

公共类实体
{
私人实体()
{
//EF专用空ctor
}
公共实体(ValueType键)
{
StoredKey=key.ToString();
}
public ValueType Key=>ValueType.Parse(StoredKey);
//键的DB表示形式,EF的setter
私有字符串StoredKey{get;set;}
}
这样,我觉得我的存储问题在某种程度上污染了我的域设计。域关心的是,实体可以仅在内存中持久化,所以这个字符串的内部表示感觉很奇怪


这是一个非常简单的示例场景,但实际情况可能会变得更糟。我想知道是否有任何方法可以通过这个简单的示例在模型中实现持久性忽略,因此我可以稍后开始考虑如何设计更复杂的场景。

域模型不需要遵循实体框架结构。您可以做的是创建两种类型的模型。一个纯域模型,当将其传递到存储库以持久化时,将其转换为实体框架模型。获取模型时,您可以进行逆变换。

域模型不需要遵循实体框架结构。您可以做的是创建两种类型的模型。一个纯域模型,当将其传递到存储库以持久化时,将其转换为实体框架模型。在获取模型时,您可以进行逆变换。

正如评论中所建议的那样,CQRS对于复杂的业务规则来说是一个不错的选择。它的最大优点是,您可以为每一方使用不同的模型(写入/命令和读取/查询)。通过这种方式,您可以分离关注点。这也是非常好的,因为写端的业务逻辑不同于读端的业务逻辑,但充分利用了CQR的优势

…不幸的是,我不得不处理一个无法摆脱的遗留数据库


您的新写模型Aggregate将负责处理命令。这意味着遗留模型将免除此责任;它将仅用于查询。为了使其保持最新,您可以创建一个订阅新聚合生成的所有域事件的
LegacyReadModelUpdater
,它将以最终一致的方式将它们投影到旧模型。

正如评论中所建议的那样,CQRS对于复杂的业务规则来说是一个很好的选择。它的最大优点是,您可以为每一方使用不同的模型(写入/命令和读取/查询)。通过这种方式,您可以分离关注点。这也是非常好的,因为写端的业务逻辑不同于读端的业务逻辑,但充分利用了CQR的优势

…不幸的是,我不得不处理一个无法摆脱的遗留数据库


您的新写模型Aggregate将负责处理命令。这意味着遗留模型将免除此责任;它将仅用于查询。为了使其保持最新,您可以创建一个订阅新聚合生成的所有域事件的
LegacyReadModelUpdater
,它将以最终一致的方式将它们投影到旧模型。

在这个实例中,您可以实现持久性忽略。你的直觉是正确的,从你的领域模型中去掉所有持久性的关注点,将它们完全转移到你的dal中它们所属的地方

DB.sql:

  create table entity {
    id  nvarchar(50)    not null primary key,
    fields  nvarchar(max)  /*Look mum, NoSql inside sql! (please dont do this) */
  }
Domain.dll:

  class Entity {
    /*optional - you are going to need some way of 'restoring' a persisted domain entity - how you do this is up to your own conventions */
    public Entity(ValueType key, ValueObjects.EntityAttributes attributes) {Key=key;Attributes=attributes;}
    public ValueType Key { get; }
    public ValueObjects.EntityAttributes Attributes { get; }
    /* domain functions below */
  }
  IEntityRepository { 
    public Update(Domain.Entity enity);
    public Fetch(ValueType Key);
  }
现在所有的坚持工作都可以进入你的DAL,包括翻译。我已经有一段时间没有做EF了,所以请处理以下问题 仅作为sudo代码

DAL(EF):

/*该类位于DAL中,可以是私有的,其他项目不需要了解该类*/
类实体:{
公共字符串EntityId{get;set;}
公共字符串字段{get;set;}
} 
类EntityRepository:BaseRepository,Domain.EntityRepository{
公共实体报告(DBContext){
base.Context=Context;
}
公共域。实体获取(ValueType键){
字符串id=key.ToString();
var efEntity=base.Context.Entitys.SingleOrDefault(e=>e.Id==Id);
返回MapToDomain(efEntity);
}
/*注意:根据需要处理映射,这只是一个示例*/
私有域.实体映射域(EF.Entity-efEntity){
if(efEntity==null)返回null;
返回新域。实体(
ValueType.Parse(efEntity.Id),
SomeSerializer.Deserialize(efEntity.Fields)/*每次你这样做,小狗就会伤到它的爪子*/
);
}
public Domain.Entity更新(Domain.Entity domainEntity){
字符串id=key.ToString();
var efEntity=MapToEf(域实体);
base.Context.Entities.Attach(efEntity);
base.Context.Entity(efEntity).State=EntityState.Modified;
base.Context.SaveChanges();
}
private Domain.Entity MapToEf(Domain.Entity domainEntity){
返回新的EF.Entity(
Id=domainEntity.Key.ToString(),
Fields=SomeSerializer.Serialize(domainEntity.Attributes)/*stahp*/
);
}
}
这里的要点是,您需要进行某种映射。这几乎是不可避免的,除非你的领域
  /* this class lives in your DAL, and can be private, no other project needs to know about this class */
  class Entity :{
    public string EntityId {get;set;}
    public string Fields {get;set;}
  } 
  class EntityRepository : BaseRepository, Domain.IEntityRepository {
    public EntityRepository(DBContext context) {
      base.Context = context;      
    }
    public Domain.Entity Fetch(ValueType key) {
      string id = key.ToString();
      var efEntity = base.Context.Entitys.SingleOrDefault(e => e.Id == id);
      return MapToDomain(efEntity);
    }
    /*Note: Handle mapping as you want, this is for example only*/
    private Domain.Entity MapToDomain(EF.Entity efEntity) {
      if (efEntity==null) return null;
      return new Domain.Entity(
        ValueType.Parse(efEntity.Id),
        SomeSerializer.Deserialize<ValueObjects.EntityAttributes>(efEntity.Fields) /*every time you do this, a puppy hurts its paw*/
      );
    }
    public Domain.Entity Update(Domain.Entity domainEntity) {
      string id = key.ToString();
      var efEntity = MapToEf(domainEntity);
      base.Context.Entities.Attach(efEntity);
      base.Context.Entity(efEntity).State=EntityState.Modified;
      base.Context.SaveChanges();
    }
    private Domain.Entity MapToEf(Domain.Entity domainEntity) {
      return new EF.Entity(
        Id = domainEntity.Key.ToString(),
        Fields = SomeSerializer.Serialize(domainEntity.Attributes) /*stahp!*/
      );
    }
  }