在域驱动设计(DDD)中建模查找表的实用方法是什么?

时间:2009-04-30 18:49:20

标签: domain-driven-design lookup

我刚刚学习DDD(Eric Evans的书在我面前开放),我遇到了一个我无法找到答案的问题。当你试图获得一个简单的查找记录列表时,你在DDD中做了什么?

实施例

员工ID:123
员工姓名:John Doe
州:阿拉斯加州(下拉列表)
郡:瓦西拉(下拉 - 将根据州进行过滤)。

例如,假设您有一个Employee域对象,一个IEmployeeRepository接口和一个EmployeeRepository类。 UI将使用它来显示员工列表和个人详细信息。在UI中,您希望使用员工所在州和郡的下拉列表。将根据选择的状态筛选可用县。

不幸的是,数据库表和UI看起来非常不同。在tblEmployees中,它包含州代码= AK和县代码= 02130,而不是州和县名。

旧方法(在我开始此DDD任务之前)将非常简单,只需创建2个查询并使用DataReader填充下拉列表。下拉列表中显示的下方是值,它会自动用于表单帖子。

但是,对于DDD,我不确定你应该如何做到这一点。我首先开始创建State和County对象以及存储库的存储库和接口。但是,编写4个类+2个接口以及hbm.xml文件和Employee Business对象中的管道对于2个下拉列表的2个查询来说似乎有些过分。必须有更好的方法,不是吗?我不会很快改变州或县表中的记录,即使我这样做,也不会通过这个应用程序。因此,如果我不需要,我真的不想为State和County创建业务对象。

我看到的最简单的解决方案是使用返回字典的方法创建一个辅助类,例如GetStatesAll(),GetState()和GetCounties()以及GetCounty(),但从DDD的角度来看,这感觉不对。

请帮忙。如何在没有过度设计的情况下使用DDD只进行几次简单的查找?

最终解决方案 我认为我终于通过经验找到了答案,即将GetStates()方法放入自己的Data Access类中,尽管不是存储库类。由于我只进行只读访问,因此我将其放入结构DTO中。由于数据库很小,我把它们完全扔进了一个类,就像下面描述的Todd一样。

我的结论:

  1. 查找表永远不是值对象,因为查找表总是具有标识。如果他们没有身份,你就会有重复,这没有多大意义。
  2. 只读查找表可以有一个存储库,但可能不需要存储库。存储库的目标是通过仅通过聚合强制访问来降低复杂性。通过聚合为您提供了一种确保可以实施业务规则的方法,例如,如果您没有汽车,则不会添加轮胎。
  3. 如果您在查找表上允许CRUD维护,那么查找表有自己的存储库是有意义的。
  4. 我最终将代码存储为结构的事实并没有使它们成为“值类型”。 Fowler在POEAA中说结构是一种价值类型。这是真的,结构是不可变的,这就是福勒说它们是“价值类型”的原因,但是我使用它们的方式不同。我使用结构作为一种轻量级方式来传递DTO,这些DTO在我们最初的创建之后并没有计划改变。事实上,我所使用的结构确实具有同一性,但由于它们是只读的,因此它们起到了结构的作用。
  5. 我一直在使用的一种模式,我在其他地方看不到的是使主键字段不可变。它们由构造函数设置,但它们是只读的(不是私有访问器),并且在创建对象后无法更改。

5 个答案:

答案 0 :(得分:6)

使用DDD我有类似的内容:

interface IAddressService
{
  IList<Country> GetCountries ();
  IList<State> GetStatesByCountry (string country);
  IList<City> GetCitiesByState (string state);
  // snip
}

Country,State和City是来自数据库中查找表的值对象。

答案 1 :(得分:6)

您可能希望了解Command Query Separation的概念。我不担心查找值的类型存储库,但我仍然可能使用DTO类型类而不是数据集等...

您可能希望花一些时间阅读Greg Young的博客,从this one开始到现在。他没有专门讨论填充查找数据,但他经常谈到不通过存储库中的类型化方法处理应用程序的读取/报告功能。

答案 2 :(得分:3)

我前段时间阅读了Mathias Verraes的一篇文章,讨论了here。他谈到了将模型中的值对象与为UI服务的概念分离。

当被问及是否将国家/地区建模为实体或价值对象时,引用该文章:

  

建模国家没有什么本质上的错误   实体并将它们存储在数据库中。但在大多数情况下,那   过度复杂的事情。国家不经常改变。当一个   国家名称的变化,实际上是出于所有实际目的,a   新国家。如果有一天国家不再存在,你就不能   只需更改所有地址,因为可能该国家被拆分   进入两个国家。

他提出了一种不同的方法来引入一种名为AvailableCountry的新概念:

  

这些可用的国家/地区可以是数据库中的实体,也可以是a中的记录   JSON,甚至是代码中的硬编码列表。 (这取决于   企业是否希望通过UI轻松访问它们。)

<?php

final class Country
{
    private $countryCode;

    public function __construct($countryCode)
    {
        $this->countryCode = $countryCode;
    }

    public function __toString()
    {
        return $this->countryCode;
    }
}

final class AvailableCountry
{
    private $country;
    private $name;

    public function __construct(Country $country, $name)
    {
        $this->country = $country;
        $this->name = $name;
    }

    /** @return Country */
    public function getCountry()
    {
        return $this->country;
    }

    public function getName()
    {
        return $this->name;
    }

}

final class AvailableCountryRepository
{
    /** @return AvailableCountry[] */
    public function findAll()
    {
        return [
            'BE' => new AvailableCountry(new Country('BE'), 'Belgium'),
            'FR' => new AvailableCountry(new Country('FR'), 'France'),
            //...
        ];
    }

    /** @return AvailableCountry */
    public function findByCountry(Country $country)
    {
        return $this->findAll()[(string) $country];
    }
}

所以似乎有第三种解决方案是将查找表建模为值对象和实体。

BTW请务必查看some serious discussions关于该文章的评论部分。

答案 3 :(得分:2)

州和县不是实体,而是价值对象。它们不是您系统的主题。你说你以前对待这些的方式是可以的。您何时可以根据域模型状态的变化更改数据库中的州或县记录?不,所以这些不需要存储库。

答案 4 :(得分:2)

如果你想学习如何做DDD而不过度复杂,那你就读错了书。 : - )

如果满足您的需求,您提出的最简单的解决方案就很好。在业务对象中封装地址数据可以像应用程序要求一样简单或复杂。例如,State对象与County具有一对多的关系,因此如果您选择以这种方式对其进行建模,那么Employee实际上只需要引用一个County。如果需要,我只会介绍那种复杂性。

此外,我不认为通过为您的存储库定义接口可以获得很多东西,除非您的对象可能有多个存储库。