DDD中的数据访问层设计

4

对不起,我的英语不够好。

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

通常情况下,我们如何确保一组公共数据足以后期恢复对象?

例子

我们有以下业务规则:

  1. 必须为创建的业务对象提供用户和域。
  2. 创建后不能更改用户和域。
  3. 业务对象具有类似于"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该如何做呢?对象至少必须有一个公共的"电子邮件"(Email)字段设置器。就像这样:

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

实际上,该对象将具有“User”和“Domain”字段的公共getter/setter以及方法GetEmail()。但是,停一下。我不想让我的POCO具备这样的功能!这并没有任何业务规则。这只能为了保存/恢复对象的能力而完成。

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

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

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

2个回答

5

我觉得您误解了这部分内容:

我还有一种选择。数据访问层中的ORM可以被要求存储恢复对象所需的所有私有字段。但是,如果我们想将领域模型与数据访问层分开,则这是不可能的。数据访问层不能依赖于业务对象的某些私有成员。

领域模型不依赖于数据访问层。事实上,相反,数据访问层依赖于领域模型。ORM对领域对象有深入的了解,包括私有字段。这没有任何问题。实际上,这是在DDD中实现持久性无知的最佳方法。下面是领域类的样子。请注意:

  • 字段可以是私有且只读的
  • 公共构造函数仅由客户端代码使用,而不是数据访问层使用。
  • 不需要属性的getter和setter
  • 业务对象几乎完全不知道持久性问题

数据访问层/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。另一方面,Repository实现属于DAL (依赖倒置原则)。


所以,也就是说,ORM使用一些“不太光彩的技巧”(例如反射)来让你在领域代码中实现持久性无知。 - driushkin
谢谢你的回答!现在我明白了,我只需要更多地学习不同的ORM。但我仍然认为这种方法还不够好。每次我向POCO中添加附加私有字段时,我都必须考虑如何存储它。我甚至认为,在业务对象内维护此依赖关系可能更好(例如使用基于属性的映射),因为它首次发生在那里。但也许我只是试图把事情搞得比实际要复杂。 - akakey
1
@akakey:基于属性的映射违背了持久性无知的初衷。增加一个新字段很可能需要更改域、数据访问层和用户界面。但这并不意味着你必须将所有这些问题捆绑在一个类中。除非你想遵循这个建议:https://dev59.com/MFvUa4cB1Zd3GeqPqR95#7365438 - Dmitry
问题在于,当我了解领域驱动设计时,我并没有看到 UI 或任何其他组件依赖于内部的领域实现。它们只依赖于公共接口,这是可以的。唯一真正依赖于领域内部实现的组件是数据访问层 (DAL)。无论这种依赖关系存在于 XML 文件还是属性特性中。 - akakey
@akakey:这很重要,因为XML文件是DAL的一部分,使域完全无持久性。而属性存在于域中,使其更依赖于DAL/ORM。 - Dmitry
1
@Dmitry:现在我明白你是对的了。数据访问层(DAL)依赖于域的内部实现,就像它依赖于任何其他公共接口一样。因此,当公共接口发生变化时,该接口的所有客户端也必须改变他们的逻辑。 - akakey

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

一个简单的解决方案是让你的数据访问层调用 new BusinessObject(email)

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接