amantinband / clean-architecture

The ultimate clean architecture template for .NET applications 💪
MIT License
1.4k stars 221 forks source link

What are private parameterless constructors for? #21

Closed suugbut closed 6 months ago

suugbut commented 6 months ago

I noticed that your entities have private parameterless constructors. What are they for?

amirhessampourhossein commented 6 months ago

Hi @suugbut , I think it's there because certain packages like EF Core, which use reflection to instantiate objects, rely on these parameterless constructors. As you can see in the capture below, this is what happens when the parameterless constructor for Reminder is removed👇

2024-01-20_14-34-07

suugbut commented 6 months ago

Thank you very much. I am still learning CA and wondering why your entity models looks so smart and complicated. Someone told me that entity models should be made dumb or simple.

amirhessampourhossein commented 6 months ago

This is mostly related to domain modeling rather than CA. @amantinband has a couple of videos on this topic in his YouTube channel, in addition to a comprehensive course about domain driven design. you can check them out if you are interested.

suugbut commented 6 months ago

Dear @amantinband, I think I understood now why you need to add non-default parameterless constructor. It is actually unnecessary if we make our entity classes as dumb as possible (explained later at the bottom).

EF Core cannot instantiate entity model classes when it cannot find a matching constructor. For example, we define parameter constructors that do not conform to the conventions used by EF Core.

public sealed class Student
{
    public int Id { get; init; }
    public string Name { get; set; } = default!;
    public bool IsActive { get; set; }

    // non-matching parameter constructor
    public Student(int idx, string name)
    {
        Id = idx; // does NOT match
        Name = name; // does match
    }
}

In this case, explicit parameterless constructor must be declared.

public sealed class Student
{
    public int Id { get; init; }
    public string Name { get; set; } = default!;
    public bool IsActive { get; set; }

    // non-matching parameter constructor
    public Student(int idx, string name)
    {
        Id = idx; // does NOT match
        Name = name; // does match
    }
    private Student() { } // <============ Mandatory
}

The above non-matching constructor is only useful for us, not for EF Core. We usually invoke the non-matching constructor in creation endpoint.

var student = new Student(Guid.NewGuid(),"Thomas"){IsActive=true};
db.Students.Add(student);
db.SaveChanges();

Note

It is not necessary for a matching constructor to have parameters that cover all properties. For example, IsActive is not included in the matching constructor. EF Core will invoke the matching constructor first and then populate the remaining properties.

public sealed class Student
{
    public int Id { get; init; }
    public string Name { get; set; } = default!;
    public bool IsActive { get; set; }

    // matching parameter constructor
    public Student(int id, string name)
    {
        Id = id;
        Name = name;
    }
}

Better Approach

Most people suggest that the entity model classes should be made as dumb as possible as follows.

public sealed class Student
{
    public int Id { get; init; }
    public string Name { get; set; } = default!;
    public bool IsActive { get; set; }
}

We usually set the primary key Id as init or private init because it must be immutable after creation. And let EF Core generate the Id automatically.

 b.Property<Guid>("Id")
     .ValueGeneratedOnAdd()
     .HasColumnType("TEXT");

Extra

Your base entity already has a matching constructor, so non-default parameterless constructor is no longer mandatory. https://github.com/amantinband/clean-architecture/blob/09605637026f5e05375969b4fedb6c797dabe736/src/CleanArchitecture.Domain/Common/Entity.cs#L22

amantinband commented 6 months ago

hey @suugbut!

The reason why the entities are not "dumb" or "simple" follows the "rich domain models" principle from Domain-Driven Design, which basically means - put as much business logic inside the entities.

The motivation behind making the properties as private and readonly as possible also comes from Domain-Driven Design, where we want to limit access to the underlying data and focus on exposing behavior.

Once you follow this approach, your fields will often not have private setters, and a private paramaterless constructor will be needed to allow EF Core to populate the fields via reflection.

You can definitely follow Clean Architecture and use your implementation (I actually have a video coming out this Monday (29.1) about this topic). DDD practices aren't required, and I only chose a few principals for my template since I think it makes the systems better in the long run.