实施规范模式

时间:2015-10-25 14:49:19

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

尝试使用规范模式并遇到使其在不同实现中工作的问题(例如,在内存,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(规范$规范)将此规范传递给我的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或其他任何东西?

1 个答案:

答案 0 :(得分:10)

如果您将“规范”声明为域中的接口并在基础架构中实施,则会将业务规则移至基础架构。这与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完全匹配。 虽然这种查询是可以接受的,但埃文斯指出我们最有可能只会在这种情况下使用。

<强> 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放入存储库。
  • 规范控制应该使用的查询。
  • 过滤器设置从存储库返回(部分或全部)。