2015-10-25 94 views
3

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

作为一个例子,假设我有一个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或其他任何后端?

回答

8

如果您将规范声明为域中的接口并在基础结构中实现它,那么您正在将业务规则移至基础结构。这与DDD所做的相反。

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

Specification用于验证对象,工作得很好。问题来了,当用于选择一个对象来自集合,在这种情况下,从Repository,由于在内存中的大量对象可能。

为了避免嵌入业务规则到Repository和泄露SQL细节到Domain,埃里克·埃文斯在他的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.埃文斯指出,这种情况很可能只会在这种情况下使用。

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

我明白两者之间的原因。没有一个是'理想'的,但是维护域/模型中业务逻辑的需求似乎要求以这种方式进行设置。感谢您的建议。 –

+0

如何有效处理复合规格?规格的优点之一是它们易于组合。我有一些想法,但我很想听听你的想法。 – plalx

+0

@plalx请按照此链接https://github.com/dddinphp/ddd/pull/3/files与基于Fowler和Evans论文的php实现 – martinezdelariva