webdevilopers / php-ddd

PHP Symfony Doctrine Domain-driven Design
201 stars 11 forks source link

Implementing the Specification Pattern with Doctrine Repositories #17

Open webdevilopers opened 8 years ago

webdevilopers commented 8 years ago

There are some nice tutorials out there:

For Zend Framework too by @UFOMelkor:

Another implementation by @mbrevda:

And @maximecolin:

But there is also a implementation for Doctrine by @Happyr @Nyholm @cakper:

Anybody else with other implementations? What are your experiences? Any best practices? Thanks for the feedback!

mbrevda commented 8 years ago

My implementation is not implemented, that repo was to TRY to and implement the pattern... wasn't very successful.

webdevilopers commented 8 years ago

Tried any of the others @mbrevda ? What was missing for success?

Nyholm commented 8 years ago

The HappyDoctrineSpecification works. I've been using in in prod for over a year. I will soon tag version 1.0.

mbrevda commented 8 years ago

It wasn't so much the spec part, as much as I wanted a Specification that can be turned in a query. That proved to be quite difficult and I ran out of time.

On Wed, Jun 22, 2016, 7:14 PM Tobias Nyholm notifications@github.com wrote:

The HappyDoctrineSpecification works. I've been using in in prod for over a year. I will soon tag version 1.0.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/webdevilopers/php-ddd/issues/17#issuecomment-227795606, or mute the thread https://github.com/notifications/unsubscribe/AAR4jP1GbTEHNnWUzA60lKsoEDAHXGOXks5qOV9SgaJpZM4I7tPY .

mablae commented 8 years ago

@Nyholm Can you say something about your directory/namespacing schema for the custom specs?

webdevilopers commented 8 years ago

Another Bundle I found is RulerZ by @K-Phoen:

Using RulerZ, the two previous repositories can be refactored:

<?php
interface CompanyRepository
{
    public function save(Company $company);
    public function find($slug);
    public function matchingSpec(Specification $spec);
}

class DoctrineCompanyRepository extends EntityRepository implements CompanyRepository
{
    // ...

    public function matchingSpec(Specification $spec)
    {
        $qb = $this->createQueryBuilder('c');

        return $this->rulerz->filterSpec($qb, $spec);
    }
}

class InMemoryCompanyRepository implements CompanyRepository
{
    private $companies = [];

    // ...

    public function matchingSpec(Specification $spec)
    {
        return $this->rulerz->filterSpec($this->companies, $spec);
    }
}

It is also possible to use the Specs for filtering directly inside Symfony Forms:

<?php
class CompanySearchType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $terms = $builder
            ->create('who')
            ->addModelTransformer(
                new SpecToStringTransformer(Spec\CompanyName::class, 'terms')
            );

        $location = $builder
            ->create('where')
            ->addModelTransformer(
                new SpecToStringTransformer(Spec\CompanyLocation::class, 'location')
            );

        $builder
            ->add($terms)
            ->add($location);
    }

    // ...
}

Personally I prefer using a Query passing the data to a Handler. Then the Handler creates the Specifications to pass them to my repository. The Handler then returns the Set from the Collection.

webdevilopers commented 8 years ago

A word on "coupling business logic with the specification pattern":

I can understand that looking at these concrete Doctrine implementations make you think, that business logic is linked to persistence. But coming from a DDD approach you would create a Base Specification like "filterGroup". Then you create a Doctrine (or Mysql, Mongo, InMemory) class based on that Spec and add only the stuff that will be related to the same Persistence storage: DoctrineUserRepository or InMemoryRepository. What you want is the coupling of the Business Logic to the Specification. And then you build your infrastructure around it.

http://www.whitewashing.de/2013/03/04/doctrine_repositories.html#comment-2751511485

I think @timglabisch also prefers this kind of approach? http://www.whitewashing.de/2013/03/04/doctrine_repositories.html#comment-1280738273

Coming from this nice article by @beberlei: http://www.whitewashing.de/2013/03/04/doctrine_repositories.html#comment-2751511485

Which was an inspiration on the @Happyr Doctrine-Specification btw..

webdevilopers commented 8 years ago

There is also an bundle by @rikbruil inspired by @Happyr Doctrine-Specification implementing the Doctrine Paginator: https://github.com/rikbruil/Doctrine-Specification

webdevilopers commented 8 years ago

BTW: Doing my first steps with @Happyr now:

/**
 * Class DoctrineContractRepository
 *
 * @package Rewotec\Contract\Infrastructure\Persistence\Doctrine
 */
class DoctrineContractRepository
    extends EntityRepository
    implements ContractRepository
{
    use EntitySpecificationRepositoryTrait;

Works like a charm @Nyholm!

Testing some more complex stuff now: https://github.com/Happyr/Doctrine-Specification/issues/128

webdevilopers commented 8 years ago

Where do you guys suggest to put the Specifications?

Normally I think they would go into the Domain / DomainModel namespace. But since the examples are used with Doctrine the are coupled to the infrastructure.

At least they should go into Acme\Infrastructure.

I've seen people adding a Domain namespace for this:

Thoughts?