Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Domain driven design and ORM limitations

Most of the DDD examples I see are written in Java and vast majority use Hibernate for persisting and fetching entities. I really don't have any experience with both and I'm assuming Hibernate is a tool good enough to resolve dependencies, deal with value objects and so on. My ORM of choice is Doctrine2 and as far as I know this is the best tool PHP currently has, but it isn't enough in my opinion to support DDD principles.

Here's an example of domain layer:

/**
 * Simple value object
 */
class ProductId
{
    private $value;

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

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

/**
 * Example dependency
 */
class Dependency
{
    public function doNothing()
    {

    }
}

/**
 * Game class done in a DDD manner
 */
class Game
{
    /**
     * @var ProductId
     */
    private $id;

    /**
     * @var string
     */
    private $title;

    /**
     * @var Dependency
     */
    private $dependency;

    function __construct(ProductId $id, $title, Dependency $dependency)
    {
        $this->id    = $id;
        $this->title = $title;

        // Validation
        Assertion::minLength(25, $title);
    }

    /**
     * @return ProductId
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    public function someBehavior()
    {
        $this->dependency->doNothing();
    }
}

Now with Doctrine you could use XML or YAML mappings to map Game to some table. However upon calling $gameRepository->productOfId($someId); what you would get is a malformed object. Here's why:

  1. Since Doctrine2 doesn't support value objects when calling getId() you would get a plain int, since its mapping will be pointing to an integer column. Newest beta kinda supports them but still it isn't very flexible, nor easy to configure in more complex scenarios.
  2. Since Doctrine2 create proxy objects for retrieval upon calling someBehavior() on a fetched entity you'd get a fatal error because proxy objects don't resolve constructor dependencies. This can be actually overcome by creating some DomainRegistry singleton and getting dependencies from there but I REALLY don't like that's implied on me. However we're still skipping validation because of constructor not being called and I wouldn't rely on database integrity only.

How should I overcome that? I'd rather not use some if is int return ProductId(int) things inside my domain model since I want my domain layer to be persistence-ignorant.

One thing that came to my mind is (assuming that i'll stick to ORM, not just DBAL) to treat Doctrine entities like DTOs (I hope I use that term correctly here) and assemble domain objects from them. So besides mentioned classes I'd have something like that:

/**
 * Doctrine entity treated like a pure value container
 */
class GameDTO
{
    private $id;
    private $title;

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }
}

class DoctrineGameRepository
{
    /**
     * @var Dependency
     */
    private $dependency;

    /**
     * Doctrine's entity manager
     */
    private $em;

    function __construct(Dependency $dependency, $em)
    {
        $this->dependency = $dependency;
        $this->em = $em;
    }

    public function productOfId($id)
    {        
        /** @var GameDTO $gameDto */
        $gameDto = $this->em->find($id);

        return new Game($gameDto->getId(), $gameDto->getTitle(), $this->dependency);        
    }
}

That way I am overcoming an infrastructure limitations, but it adds another layer of complexity. Also how should I handle changes to the object that need to be also reflected to my DTO so I can update my database?

My second thought was to adapt CQRS, but that's just a buzzword for me since I haven't researched it too much yet. I know it principles but I don't know which classes and repositories should I model for that.

How would you handle all of this?

like image 414
acid Avatar asked Oct 20 '22 04:10

acid


1 Answers

I think the question raises a good point.

Should limitations of the ORM affect your DDD design ?

I code in java most of the time and I am aware of how hibernate has evolved, 3-4 years back even hibernate did not support value objects,the support for embedded objects was also quite naive and it looks like Doctrine2 is at that stage

I think the magic of ORM is less required once you lean towards CQRS, I think that CQRS and DDD go hand in hand, CQRS is pretty simple if implemented on its own, its only when ES is added into the equation that it gets complicated (ES has its benefits).

So i would advocate that you go with basic CQRS for all projects you want to use DDD concepts on. So from here we can assume that you have a Finder/Query class to look after your queries, these should probably be straight SQL's

Not lets get down to the meaty bit the repository. Once we have the queries out the repository, the repository is actually simple, the interface it exposes will be real tight here is an example.

public interface Repository<T> {

    T load(Object aggregateIdentifier);    
    void add(T aggregate);
    void update (T aggregate);
}

You can have an implementation for each aggregate you have in the system.

Here you can take care of marshalling your aggregate out of your result set the way you seem fit. This might be some effort, but the cost can be amortized across projects and you are free from all the ORM black magic :)

like image 64
Sudarshan Avatar answered Oct 24 '22 12:10

Sudarshan