I don't know which option is most suitable between those 2.
Option 1: Use aggregateroot or entity (aggregate sub-entity) to map table.
public class User : AggregateRoot
{
string Name { get; }
string Email { get; }
// Logic ...
}
And now configure EF Core entity:
public class EntityTypeConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
// ... map props.
}
}
Option 2: duplicate code and create 2 classes but with own scope.
public class User : AggregateRoot
{
string Name { get; }
string Email { get; }
// Logic ...
}
public class UserTable
{
Guid Id { get; }
string Name { get; }
string Email { get; }
// No logic here ...
}
public class EntityTypeConfiguration : IEntityTypeConfiguration<UserTable>
{
public void Configure(EntityTypeBuilder<UserTable> builder)
{
// ... map props.
}
}
With second option, I can keep my entities in same folder for example EFCoreEntities and code should be cleaner, but with lots of duplicated code.
With second approach, I have to write extra mappers between UserTable and User (maybe using mapster / automapper)
For example, this would be IUserRepository with method:
// Here parameter is an User but have to map into UserTable
Add(User user)
{
var entity = user.MapToUserTable();
dbContext.Users.Add(entity)
// ...
}
Which option do you use, any advantages and disadvantages?
This is a very good question that people rarely talk about. Many will tell you to abandon DDD altogether.
I've worked on a relatively large system using Option 2 with separate entities and domains. The following is my experience.
Option 2 PROS ✅:
Option 2 CONS :
Option 1 is a cleaner approach BUT with two caveats
First: Your domain becomes restricted by the database schema. Make sure your database schema is well-designed with proper normalization. If your team doesn't have a good background in database design, the domain becomes harder to change as it gets complex.
Second: Referencing the 1-to-many navigational properties within itself. For example, if use the collection navigational properties for validation and you forget to load them, your validations will silently fail as there is no way to know from the domain side that the navigational properties are either loaded or not loaded. This adds a level of coupling between the domain and persistence, and the domain isn't supposed to care about persistence.
The solution for the second caveat is either
IEntityTypeConfiguration.Both loading options have different performance implications when it comes to scaling, but there are ways to mitigate this. Example: When fetching large data, apply projections when needed, use paginations, and consider pre-computations stored in the database.
If you ever decide to enable lazy loading, preemptively catch any "1-N select problem" as soon as they happen. Setup a performance monitoring, integration, and load testing
Lastly, with option 2, explore shadow properties and only expose fields that need to be exposed. Apply proper encapsulation so your entities/domain stays nice and pretty.
Its really about the trade offs.