Repository Add and Create methods
Asked Answered
C

3

7

Why are repositories' .Add method usually implemented as accepting the instance of entity to add, with the .Id already "set" (although it can be set again via reflection), which should be repo's responsibility?
Wouldn't it be better to implement it as .CreateAndAdd?

For example, given a Person entity:

public class Person
{
    public Person(uint id, string name)
    {
        this.Id = id;
        this.Name = name;
    }

    public uint Id { get; }
    public string Name { get; }
}

why are repositories usually implemented as:

public interface IRpository<T>
{
    Task<T> AddAsync(T entity);
}

and not as:

public interface IPersonsRpository
{
    Task<Person> CreateAndAddAsync(string name);
}
Chancelor answered 25/3, 2019 at 17:12 Comment(0)
F
6

why are repositories usually implemented as...?

A few reasons.

Historically, domain-driven-design is heavily influenced by the Eric Evans book that introduced the term. There, Evans proposed that repositories provide collection semantics, providing "the illusion of an in memory collection".

Adding a String, or even a Name, to a collection of Person doesn't make very much sense.

More broadly, figuring out how to reconstitute an entity from a set of a parameters is a separate responsibility from storage, so perhaps it doesn't make sense to go there (note: a repository often ends up with the responsibility of reconstituting an entity from some stored memento, so it isn't completely foreign, but there's usually an extra abstraction, the "Factory", that really does the work.)

Using a generic repository interface often makes sense, as interacting with individual elements of the collection via retrieve/store operations shouldn't require a lot of custom crafting. Repositories can support custom queries for different kinds of entities, so it can be useful to call that out specifically

public interface IPersonRepository : IRepository<Person> {
    // Person specific queries go here
}

Finally, the id... and the truth of it is that identity, as a concept, has a whole lot of "it depends" baked into it. In some cases, it may make sense for the repository to assign an id to an entity -- for instance, using a unique key generated by the database. Often, you'll instead want to have control of the identifier outside of the repository. Horses for courses.

Factory answered 25/3, 2019 at 18:17 Comment(1)
what if I want to use an incremental int as Id? Who's able to generate a valid one before inserting the entity?Chancelor
S
4

There already is a great answer on the question, I just want to add some of my thoughts. (It will contain some duplication from the previous answer, so if this is a bad thing just let me know and I'll remove it :) ).

The Responsibility of ID generation can belong to different part of an organization or a system.

Sometimes the ID will be generated by some special rules like a Social Security Number. This number can be used for ID of a Person in a system, so before creating a Person entity this code will have to be generated from a specific SSNGenerator Service.

We can use a random generated ID like a UUID. UUIDs can be generated outside of the Repository and assigned to the entity during creation and the Repository will only store it (add, save) it to the DB.

IDs generated by databases are very interesting. You can have Sequential IDs like in RDBMS, UUID-ish like in MonogoDB or some Hash. In this case the Responsibility of ID generation is assigned to the DB so it can happen only after the Entity is stored not when it's created. (I'm allowing myself freedom here as you can generate it before saving a transaction or read the last one etc.. but I like to generalize here and avoid discussing cases with race conditions and collisions). This means that you Entity does't have an identity before the save completes. Is this a good thing? Of course It depends :)

This problem is a great example of leaky abstractions.

When you implement a solution sometimes the technology used will affect it. You will have to deal with the fact that for example the ID is generated by your Database which is part of your Infrastructure (if you have defined such a layer in your code). You can also avoid this by using s UUID even if you use a RDBMS, but then you have to join (again technology specific stuff :) ) on these IDs so sometimes people like to use the default.

Instead of having Add or AddAndCreate you can have Save method instead that does the same thing, it's just a different term that some people prefer. The repository is indeed often defined as an "In memory collection" but that doesn't mean that we have to stick to it strictly (It can be a good thing to do that most of the time but still...).

As mentioned, if you database generates ID's, the Repository seems like a good candidate to assign IDs (before of after storing) because it is the one talking to the DB.

If you are using events the way you generate ID's can affect things. For example lets say you want to have UserRegisteredEvent with the UserID as s property. If you are using the DB to generate ID's you will have to store the User first and then create and store/dispatch the event or do something of the sort. On the other hand if you generate the ID beforehand you can save the event and the entity together (in a transaction or in the same document doesn't matter). Sometimes this can get tricky.

Background, experience with technologies and framework, exposure to terminology in literature, school and work affects how we think about things and what terminology sounds better to us. Also we (most of the time) work in teams and this can affect how we name things and how implement them.

Stander answered 18/4, 2019 at 15:42 Comment(5)
Your answer is a great one too! And I really don't see any reason for removing something from it.Raimund
I don't see any reason you should remove information either. If you can take what someone else wrote, and make a better answer from it, then you should do that.Factory
Thanks! I didn't want to leave the impression that I'm taking credit for some else's answer :)Stander
so there will be some repositories which will just Insert their Entities with their Ids already set (calculated with a specific logic before the insert) and some others exposing something like my CreateAndAdd? Sorry if I'm missing something but otherwise I cannot figure out how to use an incremental int as Id and choose it before executing the insert.Chancelor
Yes, some Repositories will just insert their Entities. It's important to note here that UUID's are unique and do not depend on previously generated UUID's. Incremental integer ID's have a dependency, the current ID depends on the previous ID. If you do set them before saving the entity, in a system with lots of inserts there will be a lot of collisions, so it's better to leave the database to generate them. You can have the CreateAndAdd method or Add method. Just keep in mind that the integer ID of the entity will not be valid before the Add operation completes.Stander
I
2

Using Martin Fowler's definition:

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer

A Repository gives an Object Oriented view of the underlying Data (which may be otherwise stored in a relational DB). It's responsible for mapping your Table to your Entity.

Generating an ID for an object is whole different responsibility, which is not trivial and can get quite complex. You may decide to generate the ID in the DB or a separate service. Regardless of where the ID is generated, a Repository should seamlessly map it between your Entity and Table.

ID generation is a responsibility of its own, and if you add it to the Repository, then you are moving away from Single Responsibility Principle.


A side note here that using GUID for an ID is a terrible idea, because they are not sequential. They only meet the uniqueness requirement of an ID but they are not helpful for searching through the Database Index.

Internode answered 21/4, 2019 at 23:27 Comment(11)
Hi, Hooman. +1 for your answer and, especially, for your last phrase. A good argument, making it very clear about where should an id not be created, e.g. in repository.Raimund
@dakis, thanks... SOLID principles are the basis of OO programming.Internode
so how can I generate a sequential ID and assign it to the entity before Inserting it into the Repository? Only using a GUID? What if I need an int?Chancelor
Can you use an AutoIncrement Id in the DB?Internode
yes, for example I want to do it with SQLite. but how can I assign it to my entity before inserting it in the repository? I have no way to know a valid int before actually executing the insert. That's why I was thinking about a CreateAndAdd method which inserts the entity into the DB, gets the valid Id and instantiates the entity with the valid Id in a single operationChancelor
When you use auto-increment, you assign the id after it is inserted in the DB (not before)... you need to pass 0 as ID and DB would understand that it has to generate a new IDInternode
ok, but if I create the entity with Id = 0 I would have an invalid Entity (until reflection sets the valid value), what if I want my entities to exist only in a valid state?Chancelor
Id zero means it's a new entity which is not inserted into DB, but it is not invalid... it is perfectly valid. You need to redefine what is valid and what is not valid.Internode
well, from my point of view an instance of an entity should be accessible only if it really exists, and as long as it's not persisted it exists in intention but not concretely, so the entity instance is created with a valid Id or it doesn't existChancelor
then don't use Auto increment... use a Unique Id Generator to generate IDs but keep in mind that generating unique ids sound like a simple task but in fact it is quite complex... With all respect, I don't agree with your argument above... a new entity should be added to DB... even if you generate an ID for it it does not exists in the DB before you save it.Internode
that's why I would use a NewMyEntity class with the only purpose of sending the body of the Entity to the DB, which will return the Id to the Repository, which will finally instantiate the valid Entity with body+Id. Alternative would be to have the CreateAndAdd method params to look like MyEntity ctor without the IdChancelor

© 2022 - 2024 — McMap. All rights reserved.