6

I've seen around the Internet and Github, implementations for the design pattern Repository that knows about database table and column names. I was think, if I want to work with the database as a plugin, that I can unplug and plug another respecting Open/Closed for the rest of my code, my repository should not know about the column names of the database I'm using. So how to implement this pattern in a way that it can transform the result from the database into a Entity of my domain, without knowing about the database table and column names?

As my main language is PHP, I saw that in Doctrine\ORM you can easily pass different yamls or xmls config files, mapping the column names to property names on the Entity, but... I cant be a hostage of a library implementation, if I want to implement raw PDO, or any other library for my repositories that doesn't make this hydration out-of-the-box, my implementation should do, so how?

Update
Illustration

Update 2
Please, take a look at: http://leocavalcante.github.io/patterns/2014/07/11/repository-pattern-and-database-schema.html

2 Answers 2

4

The purpose of the repository is to provide an adapter around the database. It "knows about the database" so the rest of your code doesn't have to.

So it seems quite reasonable to me that if you change your database schema, you'll have to change (or altogether replace) your repositories. The point is that you only have to replace the repositories, because the implementation is hidden from the application by the interface.

If you have lots of aggregates and a repository for each of them, so replacing them wholesale is hard, you might be able to get help from an ORM, depending on the technologies you're using. A generic Repository<T> seems to be pretty common in C#, for example.

Of course, even if you're using an ORM, there still has to be some code, somewhere, which knows about the database schema. But this way it's usually tucked away in an XML mapping file (which of course has its own advantages and disadvantages).


Edit in response to your comments:

I don't really see what you're trying to achieve. In your design, it looks like the 'Hydration' objects take on the responsibility of communicating with the database and returning a domain object. So if you change your database schema, you still have to change all the Hydration objects. Your proposal does not solve your perceived problem: there has to be some code somewhere which knows about the database schema.

But your design is so much more complicated: there's an extra layer (the Repository itself) which basically does nothing.

public class FooRepository
{
    private IFooHydration _fooHydration;

    public FooRepository(IFooHydration fooHydration)
    {
        _fooHydration = fooHydration;
    }

    public Foo Get(int id)
    {
        return _fooHydration.Get(id);
    }

    public void Save(Foo entity)
    {
        _fooHydration.Save(entity);
    }
}

It's much simpler to use an IRepository, and write different implementations of that for your different persistence methods. In short, the Repository is what you plug-in when you change databases.

10
  • And what you think about delegating the responsibility of hydrating (not sure if this is the appropriate term) to a new participant, a Service. Something like: yuml.me/1f5c9114 Commented Jul 10, 2014 at 22:28
  • 1
    If I've understood your sketch correctly, you seem to have a IFooHydration interface with an implementation for each database, all of which is internal to the FooRepository itself. I don't really see how this is different to just having an IFooRepository with a different implementation for each database, except that it's way more complicated. Commented Jul 10, 2014 at 22:36
  • 1
    If your domain model is so complex that loading an aggregate is too much work for one class, you probably need to refactor your domain model. If it's still too complex then maybe you should enlist an ORM. Commented Jul 10, 2014 at 22:39
  • 2
    You seem pretty attached to your idea, so I don't think I'll be able to convince you that it's a bad one. Nonetheless, I've updated my answer. Commented Jul 11, 2014 at 10:03
  • 2
    I'm going to back up @BenjaminHodgson here. The repository is supposed to be the one place that your app is coupled to the data retrieval specifics, which in this case is a relational database schema. The point of a repository is to get the data out of the data source and 'hydrate' the domain objects for use by the rest of the app. Adding an extra layer of abstraction at that level is ... pointless.
    – Eric King
    Commented Jul 11, 2014 at 15:43
3

What you're talking about here is dependency inversion and encapsulation. You consuming code wants to hold a dependency on some means of getting hold of domain entities, without needing to know how that happens. It wants a set of methods on a black box, which it expects to give some inputs and get some outputs: an interface.

Instead of having your code depend on some concrete implementation of data access, you rely on an abstraction: the interface. So long as your calling code knows only about the abstraction, then it doesn't matter how you fulfill that interface, so long as you do it correctly.

Taking the Doctrine and raw PDO examples from your question, you might define an interface as:

interface FooRepositoryInterface {
    public function getAFoo($id);
}

With the interface in place, you can implement it in any way you see fit:

public class DoctrineFoo implements FooRepositoryInterface {
    public function __construct(EntityManager $em){ ... }
    public function getAFoo($id){ 
        return $this->em->find("\Entity\Foo", $id);
    }
}

public class PDOFoo implements FooRepositoryInterface {
    public function __construct(PDO $pdo){ ... }
    public function getAFoo($id){ 
        $pdo->prepare("sql ...");
        $row = $pdo->fetchOne();
        return $this->makeFooFromRow($row);
    }     
}

This allows your other modules to happily depend on data acess in terms of the interface:

class FooBazer {
    public function doBaz(FooRepositoryInterface $repository, $id) {
        $foo = $repository->getAFoo($id);
        $foo->baz();
    }
}
9
  • The problem is about the database interface (column names) in my code. For example, how do you implement makeFooFromRow? And how do you prepare your "sql ..." without knowing about the table name? If I pass a PDO dependency that connects to a database that have table and column names different from the implemented, you PDOFoo class will broke, it will violate open/close principle, because I will need to change that for every new PDO connection that connects to databases with different table and column names.Your PDOFoo class has a dependency on the database interface (table and column names) Commented Jul 8, 2014 at 16:21
  • 3
    I think you're thinking about the repository too generically. At some point or other, your code MUST know about what's in the database schema. This is what your repository implementations are for, and what the interface is meant to hide rest of your code from.
    – Andy Hunt
    Commented Jul 8, 2014 at 16:35
  • The solution that came across my mind is to have a repository implementation for each entity/dataAccess/database. If I have entities Foo and Bar, using Doctrine and Pdo access layers, with two MySQL database schemas, I will have MySQLA\Doctrine\FooRespository, MySQLA\Doctrine\BarRepository, MySQLA\Pdo\FooRepository and so on... But doesn't sounds nice, there must be a better way. Commented Jul 8, 2014 at 17:59
  • 2
    This is the way it should be done, yes. A repository for each of your entity types (roughly speaking. There's way more to this topic than a comment can hold). We do this to help us break up the responsibilities in our code. Each repository is responsible for the data access of one entity type.
    – Andy Hunt
    Commented Jul 8, 2014 at 18:25
  • The problem isn't one repository per entity, the problem is one repository per entity, per data access layer, per database schema. Using the previous example, I'll have 4 repositories per entity, one for each datalayer/database combination (MySQL1-Doctrine, MySQL2-Doctrine, MySQL1-PDO etc...) that doesn't sound right. Commented Jul 8, 2014 at 23:35

Not the answer you're looking for? Browse other questions tagged or ask your own question.