DDD中的数据访问层设计

Posted

技术标签:

【中文标题】DDD中的数据访问层设计【英文标题】:Data access layer design in DDD 【发布时间】:2011-11-13 23:38:04 【问题描述】:

对不起,我的英语很差。

好的,我现在正在考虑 DDD 方法,这听起来不错,但是... 关于它有一个小问题。 DDD 表示域模型层与数据访问层(以及所有其他层)完全解耦。因此,当 DAL 将保存一些业务对象时,它将只能访问该对象的公共属性。现在的问题:

我们如何(通常)保证一个对象的一组公共数据 我们只需要稍后恢复对象吗?

示例

我们有以下业务规则:

    必须在创建时为业务对象提供用户和域。 创建对象后无法更改用户和域。 业务对象具有类似于“user@domain”的电子邮件属性。

这是一个描述这些规则的纯 POCO:

public class BusinessObject

    private string _user;
    private string _domain;

    public BusinessObject(string user, string domain)
    
        _user = user;
        _domain = domain;
    

    public string Email
    
        get  return _user + "@" + _domain; 
    

所以在某个时刻,DAL 会将此对象保存到外部存储(即 SQL 数据库)。显然,DAL 会将“Email”属性保存到 DB 中的相关字段中。在我们要求 DAL 恢复对象之前,一切都会正常工作。 DAL 如何做到这一点?该对象必须至少具有“电子邮件”字段的公共设置器。类似的东西

public string Email

    set
    
        string[] s = value.Split("@");
        _user = s[0];
        _domain = s[1];
    

实际上,该对象将为“用户”和“域”字段以及 GetEmail() 方法提供公共 getter/setter。但是停下来。我不希望我的 POCO 有这样的功能!它没有商业规则。必须这样做才能仅保存/恢复对象。

我看到了另一种选择。可以要求作为 DAL 一部分的 ORM 存储恢复对象所需的所有私有字段。但是,如果我们想让域模型与 DAL 分离,这是不可能的。 DAL 不能依赖业务对象的某些私有成员。

我能看到的唯一解决方法是拥有一些系统级工具,它可以为我们创建对象的转储,并可以随时从该转储中恢复对象。除了对象的公共属性外,DAL 还必须将此转储存储到存储中。因此,当 DAL 需要从存储中恢复对象时,它将为此使用转储。当 DAL 执行不需要实例化对象的操作(即大多数 link2sql 查询)时,可以使用保存到存储中的公共属性。

我做错了吗?我需要阅读更多内容吗?关于一些模式,也许是 ORM?

【问题讨论】:

【参考方案1】:

我认为你把这部分弄错了:

我看到了另一种选择。作为 DAL 一部分的 ORM 可以是 要求存储恢复对象所需的所有私有字段。 但如果我们想保持领域模型分离,这是不可能的 来自 DAL。 DAL 不能依赖于 业务对象。

域模型不依赖于 DAL。相反,DAL 依赖于域模型。 ORM 对领域对象有深入的了解,包括私有领域。这绝对没有错。事实上,这是在 DDD 中实现持久无知的最佳方式。这就是 Domain 类的样子。请注意,

字段可以是私有的和只读的 public Constructor 仅供客户端代码使用,DAL 不使用。 不需要属性获取器和设置器 业务对象几乎 100% 不了解持久性问题

DAL/ORM 唯一需要的是私有无参数构造函数:

public class BusinessObject 
    private readonly string _user;
    private readonly string _domain;

    private BusinessObject()

    public BusinessObject(string user, string domain) 
        _user = user;
        _domain = domain;
    

    public string Email 
        get  return _user + "@" + _domain; 
    

奇迹发生在 ORM 中。 Hibernate 可以使用这个映射文件从数据库中恢复这个对象:

<class name="BusinessObject" table="BusinessObjects">
    ...
    <property name="_user" column="User" />
    <property name="_domain" column="Domain" />
    ...
</class>

持久性无知域代码的另一个方面是DDD Repository:

定义:Repository 是一种封装存储的机制, 模拟对象集合的检索和搜索行为。

Repository 接口属于Domain,应尽可能基于Ubiquitous Language。另一方面,存储库实现属于 DAL (Dependency Inversion Principle)。

【讨论】:

所以,也就是说,ORM 使用了一些“肮脏的技巧”(例如反射)来让您在域代码中实现持久性无知。 感谢您的回答!现在我看到我必须更多地了解不同的 ORM。但我仍然认为这种方法还不够好。每次我向 POCO 添加额外的私有字段时,我都必须考虑如何存储它。我什至认为最好在业务对象中维护这种依赖关系(例如,使用基于属性的映射),因为它首先出现在那里。但也许我只是想做比实际更复杂的事情。 @akakey:基于属性的映射违背了持久性无知的目的。添加新字段很可能需要更改域、DAL 和 UI。但这并不意味着您必须将所有这些问题捆绑在一个类中。除非您想遵循此建议:***.com/questions/7365309/… 问题是当我阅读 DDD 时,我没有看到 UI 或任何其他组件依赖于域的 internal 实现。它们仅依赖于 public 接口。没关系。唯一真正依赖于域内部实现的组件是 DAL。无论此依赖项存在于何处(在 XML 文件或属性属性中)。 @Dmitry:现在我知道你是对的。 DAL 依赖于域的内部实现,就像它依赖于任何其他公共接口一样。因此,当公共接口发生变化时,该接口的所有客户端也必须更改其逻辑。【参考方案2】:
public class BusinessObject

    private string _user;
    private string _domain;

   public BusinessObject(string email)
   
      string[] s = value.Split("@");
      _user = s[0];
      _domain = s[1];    
    

   public BusinessObject(string user, string domain)
    
        _user = user;
        _domain = domain;
    

    public string Email
    
        get  return _user + "@" + _domain; 
    

一个简单的解决方案是让您的 DAL 调用 new BusinessObject(email)

【讨论】:

我认为这是相同的东西。现在我可以创建提供电子邮件的业务对象,但我仍然没有合适的业务规则。

以上是关于DDD中的数据访问层设计的主要内容,如果未能解决你的问题,请参考以下文章

大话领域驱动设计——基础设施层

DDD领域设计

DDD学习笔记1——分层架构

设计数据访问层

DDD“查看对象”?

Spring Boot - 构建数据访问层