【问题标题】:Implement specification pattern实现规范模式
【发布时间】:2015-10-25 14:49:18
【问题描述】:

尝试使用规范模式并遇到了让它在不同的实现中工作的问题(例如,在内存、orm 等中)。我的主要 ORM 是 Doctrine,这意味着我的第一选择是让规范使用 Criterias,因为它们适用于 ArrayCollections(用于 InMemory 实现)和 ORM。不幸的是,它们可以运行的查询种类相当有限(不能执行连接)。

例如,假设我有一个 UserHasBoughtProduct 规范,该规范在构造函数中被赋予了产品 ID。规范编写起来非常简单。

public function isSpecifiedBy(User $user)
{
    foreach ($user->getProducts() as $product)
    {
        if ($product->getId() == $this->productId)
        {
            return true;
        }
    }

    return false;
}

但是,如果我想找到所有购买过该产品的用户怎么办?我需要通过某种 findSpecifiedBy(Specification $specification); 将此规范传递给我的 UserRepository方法。但这在生产中不起作用,因为它必须检查数据库中的每个用户。

我的下一个想法是规范只是一个接口,实现由基础设施处理。因此,在我的 persistence\doctrine\user\ 目录中,我可能有一个 UserHasBoughtProduct 规范,而在我的 persistence\InMemory\user 目录中我还有另一个。这在某种程度上可行,但在代码中使用非常烦人,因为我需要我的所有规范都可以通过 DI 容器或某种工厂获得。更不用说,如果我有一个需要多个规范的类,我需要通过构造函数将它们全部注入。味道不好。

如果我可以简单地在一个方法中执行以下操作会更好:

$spec = new UserHasBoughtProductSpecification($productId);
$users = $this->userRepository->findSatisfiedBy($spec);
//or
if ($spec->isSatisfiedby($user))
{
//do something
}

有没有人在 PHP 中做过这方面的经验?您是如何设法实现规范模式,使其能够在现实世界中工作并在不同的后端(如 InMemory、ORM、纯 SQL 或其他任何东西)中使用?

【问题讨论】:

    标签: php doctrine-orm domain-driven-design specifications


    【解决方案1】:

    如果您将规范声明为域中的接口并在基础架构中实现它,那么您就是在将业务规则转移到基础架构中。这与 DDD 的作用相反。

    因此,Specification 业务规则必须放在域层中。

    Specification 用于验证 对象时,效果非常好。当用于从集合中选择对象时出现问题,在这种情况下,来自Repository,因为内存中可能存在大量对象。

    为了避免将业务规则嵌入Repository 并将SQL 详细信息泄露到Domain,Eric Evans 在他的 DDD 书中给了我们几个解决方案:

    1.双重调度 + 专门查询

        public class UserRepository()
        {
            public function findOfProductIdBought($productId)
            {
                // SQL
                $result = $this->execute($select);
    
                return $this->buildUsersFromResult($result);
            }    
    
            public function selectSatisfying(UserHasBoughtProductSpecification $specification)
            {
                return $specification->satisfyingElementsFrom($this);
            }
        }
    
    
        public class UserHasBoughtProductSpecification()
        {
            // construct...
    
            public function isSatisfyBy(User $user)
            {
                // business rules here...
            }
    
            public function satisfyingElementsFrom($repository)
            {
                return $repository->findOfProductId($this->productId);
            }
        }
    

    Repository 有一个专门的查询,与我们的Specification 完全匹配。 虽然这种查询是可以接受的,但 E. Evans 指出我们很可能只会在这种情况下使用这种查询。

    2。双重调度 + 通用查询

    另一种解决方案是使用更通用的查询。

    public class UserRepository()
    {
        public function findWithPurchases()
        {
            // SQL
            $result = $this->execute($select);
    
            return $this->buildUsersFromResult($result);
        }    
    
        public function selectSatisfying(UserHasBoughtProductSpecification $specification)
        {
            return $specification->satisfyingElementsFrom($this);
        }
    }
    
    
    public class UserHasBoughtProductSpecification()
    {
        // construct ...
    
        public function isSatisfyBy(User $user)
        {
            // business rules here...
        }
    
        public function satisfyingElementsFrom($repository)
        {
            $users = $repository->findWithPurchases($this->productId);
    
            return array_filter($users, function(User $user) {
                return $this->isSatisfyBy($user);
            });
        }
    }
    

    两种解决方案:

    • 将业务规则保存在一个地方,即域。
    • 将 SQL 放入存储库中。
    • 规范控制应该使用什么查询。
    • 过滤器集从存储库返回(部分或全部)。

    【讨论】:

    • 我理解两者之间的推理。没有一个是“理想的”,但是维护域/模型中的业务逻辑的需要似乎需要以这种方式设置。感谢您的建议。
    • 您将如何有效地处理复合规范?规范的优点之一是它们很容易组合。我有一些想法,但我很想听听你的想法。
    • @plalx 请按照此链接github.com/dddinphp/ddd/pull/3/files 使用基于 Fowler 和 Evans 论文的 php 实现
    • @martinezdelariva 我的意思是从存储库的角度来看。
    • 我很好奇你的第一个例子1. Double dispatch + specialized queryisSatisfiedBy 有什么用。我没有看到它在示例中使用。
    猜你喜欢
    • 2011-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多