When designing an aggregate and entities, it would be good to provide the consumer with only the public properties and methods of the aggregate. But it happens that in order to change some property, it is necessary to perform a complex business logic case.
For example, we have a user aggregate and it has an email property. To change the email, you need to make sure that there are no other registered users with the same email. This means that you need to run a complex script with a database search. Embedding the repository (or the repository interface) into the method of the aggregate itself is a bad practice, we get out-of-process dependencies. So you need to put this code in DomainService or AppService (depends on the approach). This means that the property will be internal or public and it can be changed within or outside the assembly. This means that anyone can change it by mistake, bypassing the business rules described in the service.
public class User : IAggregateRoot
{
public int Id { get; }
public string Email { get; set; }
private User(int id, string email)
{
Id = id;
Email = email;
}
}
public class UserService
{
private IUserRepository repository;
public UserService(IUserRepository repository)
{
this.repository = repository;
}
public async Task ChangeEmail(int userId, string email)
{
var exist = await repository.GetByEmailAsync(email);
if (exist != null)
throw new DomainException("Email is already in use");
var user = await repository.GetByIdAsync(userId);
user.Email = email;
await repository.SaveAsync(user);
}
}
It would be nice for the property to be private for modification. Access from other classes would only be through the ChangeEmail method.
public string Email { get; private set; }
This problem could be solved if friendly classes existed in C#, then the service could be given access to private properties and methods of an aggregate or entities.
This problem could also be solved if the assemblies could refer to each other, something like in the picture, but cyclic dependencies are prohibited in C#. (Each square in the drawing is a separate project)
You could try using a nested class that has access to private properties and methods, but a nested class cannot be the same for both the aggregate and the entities associated with the aggregate. For example, Order is an aggregate and OrderItem is an entity. It cannot be nested in two classes at once.
(edited) Of course, you can try to use a similar scheme, but there are a lot of projects and a large number of links between projects. This could be easily simplified if cyclic dependencies were possible.
You could use an interface that is only implemented outside the domain (application layer I guess ) and tells you exactly what is needed. This prevents developers from just putting in a string value:
And then have a method in de domain object:
Somewhere in the application you create the email and set it: