1

Until this moment, I have seen a lot of variations and combinations of the Repository pattern, implementations that simply queried the required information, some used something like a mapper, some used something called Units of Work, and some used something called CQRS that separated the reads from the writes, updates and deletes.

A common thing was that the interface of the repository was defined in the domain/entity layer of the application and implemented in the persistence layer, BUT… There are some things I couldn’t get my head around, because almost if not all of these tutorials / articles I’ve read on this topic, involved the using of a framework or something that I wasn’t familiar with.

I’m aware that the example is stupid simple and in a real life scenario it will be an overkill to use this, or perhaps any pattern, but I’ll try to keep this short not to waste anyone’s time with complex examples that could lead to ambiguity and interpretation. I’m also aware of the fact that the answer depends on the complexity of the domain problem; we are talking general here. Another thing is that we are doing a DDD-style implementation, so you should make some assumptions about the architecture of this application based on DDD.

So with that said let’s imagine a simple application with an layered-architecture, perhaps an simple API that lists the products inside an warehouse for example, let’s say we have an Item entity with its corresponding Value Objects (like ID, Name, Description), the point of this API is that it lists the items along with the attributes in maybe a JSON format and it provides a way to select specific data depending on some filters/conditions.

With that out of the way the questions: Imagine we have an “ IItemRepo ” interface implemented by the Persistence mechanism.

Where this object will be instantiated?

Who will have knowledge of this object?

How the information that it provides will end up be used by the Domain/Application layer? Factories, but how exactly?

Will this object be passed around from layer to layer? Aren’t DTO’s the only objects allowed this kind of behavior?

There may be also other questions based on your answers.

5
  • In general, aggregate root is the only reference that is passed around. In DDD other entities without aggregate root aren't referable in business. it also means whenever you load/retrieve an information from persistence layer, you should have a way to map it to the aggregate root. Jun 7, 2021 at 16:00
  • Aggregates are the basic element of transfer of data storage - you request to load or save whole aggregates. Transactions should not cross aggregate boundaries. You can refer to more documentation here martinfowler.com/bliki/DDD_Aggregate.html Jun 7, 2021 at 16:04
  • Related: Which layer do DDD Repositories belong to?
    – Doc Brown
    Jun 7, 2021 at 16:06
  • See also: How does “Composition Root” DI apply to Domain Driven Design?. In short, the "Composition Root" is the place where a concrete repository gets instantiated.
    – Doc Brown
    Jun 7, 2021 at 16:10
  • Worth to note - Repository Pattern usually uses other methods - read: other patterns - to do a lot of the work. You rarely can use a single pattern alone in a vacuum. Most often than not, they are mashed up and combined with other patterns to get a bigger job done. So, it's fairly usual that you see a lot of people implementing Reps on very different manners.
    – T. Sar
    Jun 9, 2021 at 15:20

3 Answers 3

1

If you are looking at "repository" in the context of domain-driven-design or cqrs, you should probably familiarize yourself with the definition from Domain Driven Design: Tackling Complexity at the Heart of Software, by Eric Evans. He describes common lifecycle management patterns in chapter six.

In summary: the REPOSITORY pattern provides an illusion of an in-memory collection of all instances of some aggregate root.

The ideal is to hide all the inner workings from the client..., so that the client code will be the same whether the data is stored in an object database, stored in a relational database, or simply held in memory.


Where this object will be instantiated?

Your repository will normally be instantiated by your composition root (aka your startup code). In frameworks with use a Unit of Work pattern, you may access an instance of the repository via the unit of work instead. The difference is largely a matter of how your system handles transaction control.

Who will have knowledge of this object?

The primary client is going to be the application code that attempts to integrate new information with your domain model; the unit of work code may also need to know about it. Your domain entities and values typically won't know about it.

How the information that it provides will end up be used by the Domain/Application layer? Factories, but how exactly?

Your application code will request instances of your domain entities (more precisely, the "globally accessible" domain entities, aka the root entities of your "aggregates") and pass information to them.

Not factories, usually -- you are more likely to see an implementation of a repository use a factory than the other way around. For instance, the repository might know how to make a local copy of information from your database, but it will normally delegate the "build all the objects" part of the work to a factory.

Will this object be passed around from layer to layer?

Not usually?


If you can read Java code, you may find a review of the Cargo Shipping example to be useful. I'd recommend starting from CargoAdminController::register and working your way down.

(Note: because the example relies on the Spring framework to initialize the program, the work of the composition root is obscured by the framework. If you are already familiar with Spring, it shouldn't be too surprising).

2

Your questions are confusing. perhaps because your example is so lightweight

IItemRepo
{
   Item GetItem(id)
   List<Item> GetItemsByName(name)
   List<Item> GetItemsBySpecifedFiltersAndConditions(colour, weight, whatever)
   ...
}

Api
{
   start()
   {
      //repo is initialised in startup for DI where required
      diContainer.Register<IItemRepo>(new ItemRepo(dbconnection))
   }
}

ItemController
{
   GetItem(id)
   {
       //api controllers / application layer will know about the underlying data layer
       //api layer uses repo by reference passed in from di
       return _repo.GetItem(id);
   }

   ...
}

SomeDomainApplications
{
    ShipItems(itemIdsToShip)
    {
       foreach(id in itemIdsToShip)
       var i = apiClient.GetItem(id) //item retrieved from API. Item is an AR domain object, its fine to pass domain objects around you dont need dtos and the repo is not passed around
       if(somelogic)
           ShippingDomainLogic.Ship(i) //business log shares item object but doesnt see repo

       apiClient.DeleteItem(i);
    }

}
1

There is more than one way to skin a cat.

Your question is asking how something is done, but it's hard to provide an accurate answer when there are many possible different approaches. I've decided to pick the most commonly used one to answer with. Keep in mind that this is not the only possible approach.

Imagine we have an “IItemRepo” interface implemented by the Persistence mechanism.

  • Where this object will be instantiated?

Services (i.e. objects that aren't DTOs) are generally instantiated by the DI container. You don't actually have to do anything here, other than register the concrete type to the interface:

services.AddScoped<IItemRepository, MyItemRepository>();

In short, what this config line does is the same as telling the DI container:

Hey, whenever you are creating an object, and its constructor asks for an IItemRepository parameter, I want you to pass it an instance of MyItemRepository.

You effectively do this registration for each and every service in your codebase. Then, when you want to instantiate your top level service (e.g. web controller), the DI container is capable of providing each related service, all the way down to the bottom of the call stack, with all of the dependencies that they need injected.

  • Who will have knowledge of this object?

Of the object, not type? That depends on how you configure your DI container. In the example above, the method name specifies the approach.

  • AddTransient means that all injected IItemRepository dependencies will always get a freshly generated MyItemRepository instance, it is never reused.
  • AddSingleton means that all injected IItemRepository dependencies will refer back to the same singular MyItemRepository instance.
  • AddScoped behaves just like AddSingleton, but on a smaller level. Instead of reusing it across the entire runtime, it reuses it in a smaller scope. For web based applications, that scope is defined as the incoming web request. For a given web request, the same service instance will be reused, but a different web request will get a different service instance. Because of that, AddScoped is the most commonly preferred option.

As to the "who has knowledge" part of the question, it's essentially anyone who has a dependency on the given type.

  • How the information that it provides will end up be used by the Domain/Application layer? Factories, but how exactly?

This question is unclear. I suspect that you're thinking of factories in terms of creating the services (in this case the repository instance).

The DI container I've been speaking of is a kind of factory. It's just a really intelligent and customizable one, which is able to generate an instance of any registered type.

  • Will this object be passed around from layer to layer?

Yes and no. Yes, some of the things you observe are correct, but no, your conclusion does not follow.

Yes, it is possible for services from multiple layers to both have a dependency of the same type. Yes, when that dependency is not configured to be transient, that means that both of these services from multiple layers will receive the same instance of that dependency.
On a larger scope, yes, the DI container and its configuration must contain a whole lot of information on all of your layers, aggregated together. That is the top-level application's responsibility (e.g. your Web project).

No, this does not count as "passing around data". Object initialization is different from object usage, when dealing with dependency injection.
In any application, to some degree all of its classes are connected into a single web of dependency/references. If there were two completely separated webs in your codebase, you'd have two completely different applications.
When using dependency injection, that "web" is defined using the constructor parameters. Therefore, the constructors no longer conceptually count as the data highway, but rather as application architecture.

Aren’t DTO’s the only objects allowed this kind of behavior?

You're inverting the logic here. It's not that DTO's are the only one allowed to cross the layer boundary. It's that we call something a DTO when it crosses the layer boundary.

But similar as mentioned above, when using dependency injection, constructors are part of the architecture club in your codebase, which excludes their usage as being considered data-related, but rather architecture-related. And that's a different ballgame with different rules.

2
  • Thank you for your detailed reply, up until now I had the ambition to implement DDD without any framework ( in php ), but the more I learn about it the more I come to the conclusion that I really need them ( DI container, ORM etc... ) I'm still a bit confused about the DTO, a DTO does not have any behavior except for storage, retrieval, serialization and deserialization of its own data, so from this definition, a repository object can't be a DTO because a repository have behavior (accessing the persistence mechanism), can you clarify that?
    – qUneT
    Jun 10, 2021 at 6:30
  • @qUneT: This is actually quite hard to write a hard and fast rule about, but it's quite intuitive. Compare the difference in expectation between these two classes, purely by their name: Person and MailSender. On a very basic level, you expect Person's main purpose to be to contain data which will be passed around, whereas MailSender's main purpose is to do something, when others tell it to do so. A DTO is, oversimplified, a "data carrying" object. Note, though that usually only the layer-crossing data carrying objects are called DTOs, not the layer-internal data objects.
    – Flater
    Jun 10, 2021 at 7:50

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.