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

时间:2018-01-16 09:17:52

标签: domain-driven-design entity-framework-core

我正在处理的项目涉及非常复杂的业务规则,因此我尝试应用DDD。不幸的是,我必须使用我无法摆脱的遗留数据库,而且我无法保持干净的域设计。

让我们说一些实体,有一些ValueType作为主键,这是必需的。这可以在DDD中设计,如下所示:

public class Entity
{
    public Entity(ValueType key)
    {
        Key = key;
    }

    public ValueType Key { get; }
}

现在,假设此键实际存储为字符串表示形式,可以对其进行解析以构造ValueType。我可以做这样的事情,使其与Entity Framework一起使用:

public class Entity
{
    private Entity()
    {
        //Private empty ctor for EF
    }

    public Entity(ValueType key)
    {
        StoredKey = key.ToString();
    }

    public ValueType Key => ValueType.Parse(StoredKey);

    //DB representation of the key, setter for EF
    private string StoredKey { get; set; }
}

这样,我觉得我有点因为存储问题而污染我的域名设计。对于域所关心的内容,实体可以只保留在内存中,因此这个字符串内部表示感觉很奇怪。

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

3 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:1)

在这种情况下你可以实现持久的无知。你的直觉是正确的,摆脱你的领域模型中的所有持久性问题,将它们完全移到你所属的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):

  /* 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!*/
      );
    }
  }

这里的外卖是你 需要做某种映射。这几乎是不可避免的,除非您的域名非常简单并且您的ORM非常华丽,但即便如此,我建议您将ORM模型与您的域模型分开,因为它们解决了2个不同的问题(ORMS提供了数据库模型的代码版本, DDD正在为您提供商业模式的代码版本。如果您正在破坏您的域模型(即,使属性公开设置)以满足您的DAL,那么请退后一步并重新评估。显然,在适当的情况下妥协,但意识到这意味着您在应用程序层中引入(隐含的)依赖性。

下一个对性能的影响(但映射速度太慢)由Constantin Galbenu回答,对列表,搜索有单独的“读取”模型和reposistories。你是否真的需要撤回1000个商业模式只是为了填充搜索结果列表(然后有时间添加商业模式无关的属性,因为'搜索页面需要这一点数据用于finaince人' )。你应该只是在做某种商业行为时拔出我们的领域模型,否则一些不错的贫血只读视图就是你的朋友。

答案 2 :(得分:0)

正如评论中所建议的那样, CQRS是复杂业务规则的不错选择。它具有很大的优势,你可以为每一面提供不同的模型(写/命令和读/查询)。通过这种方式,您可以分开关注点。这也非常好,因为写侧的业务逻辑与读侧不同,但与CQRS的优势相当。

  

......不幸的是,我必须使用遗留数据库,我无法摆脱......

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