Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

To keep it simple, let's suppose an application which has Accounts and Users. Each account may have any number of users. There's also 3 consumers of UserRepository:

  • An admin interface which may list all users
  • Public front-end which may list all users
  • An account authenticated API which should only list it's own users

Assuming UserRepository is something like this:

class UsersRepository extends DatabaseAbstraction {
    private function query() {
        return $this->database()->select('users.*');
    }
    public function getAll() {
        return $this->query()->exec();
    }
    // IMPORTANT:
    // Tons of other methods for searching, filtering,
    // joining of other tables, ordering and such...
}

Keeping in mind the comment above, and the necessity to abstract user querying conditions, How should I handle querying of users filtering by account_id? I can picture three possible roads:

1. Should I create an AccountUsersRepository?

class AccountUsersRepository extends UserRepository {
    public function __construct(Account $account) {
        $this->account = $account;
    }
    private function query() {
        return parent::query()
            ->where('account_id', '=', $this->account->id);
    }
}

This has the advantage of reducing the duplication of UsersRepository methods, but doesn't quite fit into anything I've read about DDD so far (I'm rookie by the way)

2. Should I put it as a method on AccountsRepository?

class AccountsRepository extends DatabaseAbstraction {
    public function getAccountUsers(Account $account) {
        return $this->database()
            ->select('users.*')
            ->where('account_id', '=', $account->id)
            ->exec();
    }
}

This requires the duplication of all UserRepository methods and may need another UserQuery layer, that implements those querying logic on chainable way.

3. Should I query UserRepository from within my account entity?

class Account extends Entity {
    public function getUsers() {
        return UserRepository::findByAccountId($this->id);
    }
}

This feels more like an aggregate root for me, but introduces dependency of UserRepository on Account entity, which may violate a few principles.

4. Or am I missing the point completely?

Maybe there's an even better solution?


Footnotes: Besides permissions being a Service concern, in my understanding, they shouldn't implement SQL query but leave that to repositories since those may not even be SQL driven.

share|improve this question

3 Answers 3

I would keep all User related data-access in the UserRepository. It is not a good idea to make repositories depend on each other but it's perfectly ok to have a service depend on multiple repositories. So in this case you could have an AccountService which uses both UserRepository and AccountRepository.

The third option you have considered is something I've also used and it has the benefit that now the user list is loaded lazily (although this can be accomplished by other means as well). The huge drawback is that it will be very hard to serialize that object to send it over wire for example and it also couples the data-access layer with your object model.

share|improve this answer
    
Hmmm, one of our problems is that there are a lot of conditional joins, complex query combinations and they are mostly based on user input. We are already trying to figure out how we can achieve that elegantly either by decoration or specification pattern. I suppose that wrapping it all with a Service may add even more complication. –  vFragosop Oct 26 '13 at 0:49

There are a few principles in play here that may affect your design:

Aggregates - if Account is your top level item (aggregate root) for this aggregate, that should be your repository. You don't necessarily need one repository per table, as that breaks any domain encapsulation that you're trying to achieve. Based on this, I'd consider a single AccountRepository, which has options to pull Users by Account (ex: findUsersInAccount(Account $account) ) Your concern about joins here is what makes me suspect this is the case.

Bounded Contexts - Be careful not to stuff everything into a single bounded context. Often, admin and public interfaces are performing entirely different business goals, and they can be separated as such. Mind you, if both are trutly just findAll() calls, ignore this, but I doubt this is the case. I don't know your application, but perhaps the admin side acts on multiple Accounts, whereas the public side only acts on a single one (the user's account). Having two different subdomains (or bounded contexts) is often better than one perfect one that is skewed trying to fit each.

Reality - Now, assuming what you have still satisfies the above and you are running into problems, wrapping both in a service is probably the way to go. That service can hide all of the repository access (from multiple repositories).

However, in the effort of good OO design, and especially DDD, I'd advise against the following:

  • Extending repositories from each other UNLESS it's abstract. Interfaces are often more appropriate here if you want to re-use patterns.
  • Building your repositories around your SQL schema. Don't assume a SQL back-end, as that mapping will be evident in your domain model; exactly where it's not allowed to be.

Anyway, without understanding your domain fully, it seems something like this should satisfy your needs:

interface AccountRepository
{
    function findUsersInAccount(Account $account);
}

interface UserRepository
{
    function findAll()
}

Your domain model can expose multiple interfaces for its needs, while your actual database implementations of those repositories can be mashed together if needed. Just focus on what your domain needs, and worry about how it's implemented later.

Finally, DDD is meant for applications with complex domains. Applying it where it's not needed can lead to a lot of confusion because many of the benefits don't necessarily apply.

share|improve this answer
    
A lot have evolved in my mind since I've first asked this and I agree with almost everything you say. But I still have one question: Isn't it better to have a findByAccountId(id) method on UserRepository or even a match(UserCriteria criteria) and let controllers handle permissions? –  vFragosop Dec 16 '13 at 17:03
    
Depends - are permissions a domain-specific constraint, or are they generic application-level permissions? (Really, it depends how far you want to apply DDD, because there is no right answer). –  Adrian Schneider Dec 16 '13 at 17:31

If I got it correctly, I would suggest you to model both Account and User as aggregate roots (with own their repositories). So, if you need all account's users you could do it this way:

interface UserRepository
{
    public function findAll();
    public function findByAccount(Account $account);
}

You are free to "recycle" your code inside infrastructure layer (implementation side), you could do it this way:

class UserRepositoryImpl extends BaseRepository implements UserRepository
{
    public function findAll()
    {
        return $this->query('user');
    }

    public function findByAccount(Account $account)
    {
        // ...
    }
}

class AccountRepositoryImpl extends BaseRepository implements AccountRepository
{
    public function findAll()
    {
        return $this->query('account');
    }
}

abstract class BaseRepository
{
    protected function query($type = 'user')
    {
        // common part 1
        if ($type == 'account')
            $q .= 'AND account_id = ...';
        // common part 2
    }
}

Just remember one thing, model (including repository interface) shouldn't deal with concerns like this. It's always a bad idea to change your model according to DB access needs.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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