2

It's very common to see this use of repository in projects using clean architecture:

interface Hero { }


interface HeroRepository {
    findById(id: number): Hero;
}


class FetchHeroUseCase {
    constructor(private heroRepository: HeroRepository) {
    }

    execute(id: number) {
        return this.heroRepository.findById(id)
    }
}

class MongoHeroRepository implements HeroRepository {
    constructor(private database: MongoDatabase) { }

    findById(id: number): Hero {
        return this.database.find(id);
    }
}


So, use cases import this HeroRepository, so it belongs to the entity layer by the Dependency Rule. But, what makes me confused is this MongoHeroRepository. Even though it's database-specific (what would do it a class from the frameworks layer), it's doing the work of an adapter(just adapting the fetch logic format of MongoDatabase to another format easier to use), even if it depends on an interface from a framework (MongoDatabase).

Look that in this code here we depend on Request and Response interfaces from express, but we still consider that class and controller from the adapter layer:

class HeroController {
    constructor(private fetchHeroUseCase: FetchHeroUseCase) { }

    execute(request: Request, response: Response) {
        const hero = this.fetchHeroUseCase.execute(request.params.id);
        response.json(hero);
    }
}

So, what layer MongoHeroRepository belongs to??

3
  • 1
    Why is that low vote for? At least give me a feedback here in the comments Jan 31 at 11:58
  • I have to agree. Pay no attention to the down-voters. This question is a fine for for this site. Jan 31 at 21:26
  • 1
    Hi @VitorFigueredoMarques! Welcome to the site and interesting question! I would like to suggest you to improve a little bit the wording and grammar of the title. I think this would help the community to better understand your question, thanks!
    – blunova
    Jan 31 at 23:15

2 Answers 2

2

Here is where practice meets reality. And surprisingly, R.C.Martin didn't address this question in his book despite some code examples like that.

Where's the repository interface?

So repository is an abstraction that allows to access entities by using a collection like interface:

  • It would indeed be in the inner entity circle of the architecture (even if closer to the border, since the repository depends itself on the entity).
  • Some could argue that repositories may bes tailored to an application logic, with all these findByXXX() methods, and that they should therefore belong to the use-case circle (application-specific vs. enterprise-wide).

In the end it all depends if you reuse the same repository between different applications. Considering the reuse potential, I'd be more sympathetic for the first interpretation.

Where's the repository implementation?

THe repository implementation is for adapting the repository to a specific context. It would therefore be in the interface adapter circle. To quote R.C.Martin: "If the database is a SQL database, then all the SQL should be restricted to this layer, and in particular to the parts of this layer that have to do with the database"

To elaborate on his quote, you'd probably have in the same layer some kind of general interface for accessing the database technology you're using. Maybe a generic Sql connector. And the implementation of this Sql connector to Oracle, SQL Server, or SQL Lite would then be the outer circle of Framework and Drivers.

In the case of Mongo, it's slightly different, since there is only one Mongo. So your Mongo connector is in reality completely dependent of its only implementation. The outer layer is for things that you want to change. If you want to change Mongo to another document database, you should build the repository using a generic document DB interface that would be independent of any specific DB. THen all the mongo stuff would really be interchangeable. But it would be an additional overhead for your project.

Note on the dependency rule

The dependendy rule of R.C.Martin is not pointing strictly (<)to the inner circles (but <=). He says:

Nothing in an inner circle can know anything at all about something in an outer circle.

So you can as well depend on something that is in the same circle.

2

A repository lives in the data layer. That is the short and simple answer.


it's doing the work of an adapter(just adapting the fetch logic format of MongoDatabase to another format easier to use)

Using that definition of an adapter, arguably all classes in a well-abstracted codebase act as adapters.
Every class inherently couples ("adapts") its internal implementation to its public interface. In classes where the workload is not complex, it's possible to end up with a counterintuitively disproportionate amount of the effort being spent on the adaptation and not any other "real" logic.

That is perfectly fine, and is very common in codebases with proper layer separation. Not every layer is used to its fullest extent in every single one of its implementations.
For example, a data layer may have some complex queries that have meat on their bones; but at the same time most codebases also tend to contain very basic CRUD operations that don't require much logic to execute. Fetching an entity by its identifier, which is your example, is such a case of "trivial logic".

This is further compounded by Typescript allowing you to return any object of any type, as long as it fulfills the interface requirements. This is different in more strongly typed languages (e.g. C#) where your object needs to be of a specific class that explicitly implements the given interface.

In the case of C#, you tend to spend more effort on actually mapping from one object to another. Typescript bypasses this by dynamically checking the structure of the object directly (as opposed to C# which checks the structure of the type of the object), which leads to not needing to worry about the specific type of the objects.


As an aside, and probably biased because I'm a C# dev:

I'm not a big fan of this Typescript behavior. I prefer explicitly requiring that your entity type implements the given interface, because without doing so you have two separate definitions (the interface and the entity) which happen to be structurally similar, but it's very easy to make these go out of sync by adjusting one and not the other.

The benefit of explicitly marking the entity type as implementing the interface is that it becomes more obvious that you've adjusted one and forgot the other; which IMHO prevents silly bugs.


In short, because you are dealing with a trivial CRUD operation, coupled with the fact that Typescript almost entirely negates the need to remap your entity; leaves your class with little to do other than return the entity.

This does not mean the repository is superfluous. Its existence is justified in that it properly abstracts the responsibility of fetching the hero data from your mongo database. Trivial as it may be today, it may become more complex tomorrow. The repository is there to ensure that when and if this gets to be more complex, you are only required to make changes to the repository implementation and not your domain logic directly.

Some examples of such changes may include:

  • Changing your underlying database provider
  • Changing your underlying data entity structure (e.g. storing the data across multiple tables), without wanting to change your domain entity structure itself.

It's very common to want to do away with mostly passthrough layers in an attempt to be more efficient, but this is the most common trap that leads to bad practice and inflexible codebases.
You should instead judge the value of an abstraction by its responsibility, and not by the size of its implementation. The repository has a valid role to play in your codebase, even though that role is tiny at the moment.

3
  • This is where I'm at so I appreciate you sharing this, "to want to do away with mostly passthrough layers in an attempt to be more efficient". I'm very new to these ideas so to have someone experienced share these exact thoughts helps me to stay true to this idea. I just wish I had a program that showed this, somewhat minimally, yet thoroughly, which also provided some "gotchas" or caveats. I'm trying this with Flutter. Thanks again.
    – tazboy
    Apr 11 at 23:45
  • @tazboy Examples are difficult because there is no one-size-fits-all solution, and you run the risk of any example being dogmatically assumed to be such a solution. This is better addressed by a senior developer on your team as they can account for project-specific considerations.
    – Flater
    Apr 12 at 8:22
  • There is no senior developer. I'm ridin' solo. I'm exhausted by theories, I need to see more code, like a senior developer could provide.
    – tazboy
    Apr 13 at 0:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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