Clean Architecture – Accessing Multiple Entities in Repository

androidclean codedomain-driven-designrepository-pattern

I have a API call that returns a list of Payments, each Payment contains a User. I need to call it, save the results in the database, fetch all the payments and users and return them all to the presenter. On the UI each payment row will also contain info about the user. So the Payment and User entities are highly coupled in my case.

My idea was creating PaymentRepository and GetPaymentsUseCase. The repository in this case has access to both Payment and User tables, so it can query them both and insert into both. However (correct me if I'm wrong) the clean architecture principles state that every repository should be responsible for one entity.

But in my case it really makes no sense to separate this call between 2 repos. If I decide to do so, the PaymentRepository and UserRepository should not access each other, so the use case would have to call both of them and pass data between them after mapping it back and forth, which looks terrible in my opinion.

Is my solution acceptable or does it break the clean architecture principles? If so, are there any suggestions on how to implement this?

Best Answer

Your repository doesn't break the "clean architecture", which identifies entities as the core, requires dependency inversion, but doesn't impose to use repositories.

But your repository seems to break the clean code principles, and in particular:

  • the single responsibility principle: you pack together things that have different reasons to change: Payment (which could change for example because of new commercial practices or new banking standards) and User (subject to internal policy, GDPR concerns, or identity provider requirements);
  • the interface segregation principle: you force the clients of your repository to know Payment's and User's interface, even if they do not need them (e.g. if a later use case would produce a report on the total of payments per day the last month).

Your approach seems not either in line with Domain Driven Design principles, which suggest a repository for a single Aggregate :

  • In DDD you identify Entities, and group them into independent Aggregates that are objects that have to always be accessed via a single Aggregate Root.

  • But in your case, you have two independent Entities that each constitutes its own Aggregate:

    • It is not possible to see Payment as an aggregate containing a dependent User, since the latter may exist prior to having made any payment.
    • It is not either possible to consider User as an aggregate with dependent Payments, because this would mean that you could identify a payment only in relation to a user, which is not a realistic case for payment systems.

Edit: additional infos

The repository concept intends to provide a level of abstraction that lets you handle persistent objects as if they were in a collection in memory, and without carrying about the db.

With this architectural pattern, you'd rather reference other aggregates by identity (value). The typical scenario is to get Payments by querying the PaymentRepository. Then the payment's UserId would be used to get the user with UserRepostitory.getById(). Or alternatively you'd just use the PaymentId to query a user of a payment with by invoking UserRepostitory.getByPaymentId().

The advantage of the identity is that it allows to decouple Entities in very complex models: passing all possible kind of objects to a repository (instead of a simple id) would cause an explosion of interface dependencies. Similarly, if related objects would systematically be loaded by the repository (as you think to expect), then you could have a snowball effect: Imagine you'd have entities representing nodes and edges of a graph. Whatever node you'd get from the repository, you'd end-up with the full graph in memory!

This being said, I don't know your model nor your constraints and I do not judge. It's up to you to decide what's the most suitable architecture. You may perfectly deviate from the rules if you have valid arguments and understand the consequences. By the way, this question about repository performance may interest you.