I have an entity called User
, and it has a method to change the user's email address. I'm using a strongly typed object for representing the email address.
public class User: BaseEntity
{
// Some other behaviors and state
public Result ChangeEmailAsync(EmailAddress emailAddress)
{
// Validate that a user with a same email does not exist.
// Change state.
}
}
If I'm correct, the business logic for duplicate verification and change of email should happen inside thie domain model. And my application layer does not need to check for duplication of emails. But now, I have to call to some data source or service to verify that the user does not change his email into an invalid state.
Then I defined a interface within my domain layer for this task like this:
public interface IEmailExistenceReporter
{
Task<bool> IsAlreadyExistsAsync(EmailAddress emailAddress, CancellationToken cancellationToken = default);
}
and change the method signature of User
entity to accept an instance of IEmailExistenceReporter
.
public async Task<Result> ChangeEmailAsync(EmailAddress emailAddress, IEmailExistenceReporter emailExistenceReporter, CancellationToken cancellationToken = default)
{
var isExists = await emailExistenceReporter.IsAlreadyExistsAsync(emailAddress, cancellationToken);
// Modify email if not exists, otherwise return failure result with a message.
}
And this new interface, along with the other repository interfaces will be implemented by the Repository implementation which our domain layer do not need to care about. So I can resolve IEmailExistenceReporter
from the application layer using dependency injection and pass it into the ChangeEmailAsync
method.
The method seems unit testable too as I can mock the IEmailExistenceReporter
. But I feel like, I'm doing something wrong as I'm providing a something like a service into the ChangeEmailAsync
method to accomplish its behavior. Is this a bad design or is there a better way to do this?
ChangeEmailAsync
method?UpdateEmail
method would return a new instance ofUser
with new email field. That's how functional programming does things. But you don't have to stick to it, updating the object is fine as well.