I did a couple of simple projects following Anemic Domain Model . Now I am trying to do the similar type of projects in Rich Domain Model. But I am confused in implementations. Previously , I used to have a domain model, entity and all of its logic used to reside in Services and persistence was made through repository, which contained all functions related to database persistence.
This is what my User model deals with now:
public class User : BaseEntity
{
private readonly UserRepository _userRepo;
private readonly PasswordHash _passwordHash;
private readonly USER_DETAIL _userDetail;
protected User() { }
public User(UserRepository userRepo, PasswordHash passwordHash, USER_DETAIL userDetail) : base()
{
_userRepo = userRepo;
_passwordHash = passwordHash;
_userDetail = userDetail;
}
public User(UserDto user_dto)
{
this.is_owner = user_dto.is_owner;
this.id = user_dto.user_id;
this.email = user_dto.email;
this.normalized_email = this.email.ToUpper();
this.is_active = user_dto.is_active;
}
private string _password, _registrationToken;
[DataType(DataType.EmailAddress)]
[MaxLength(100)]
[Required]
public string email { get; set; }
[MaxLength(100)]
[DataType(DataType.EmailAddress)]
[Required]
public string normalized_email { get; set; }
[MaxLength(250)]
[Required]
public string password
{
get => _password;
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new NonEmptyValueException("Password cannot be empty.");
}
_password = value;
}
}
[MaxLength(1000)]
[Required]
public string registration_token
{
get => _registrationToken;
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new NonEmptyValueException($"Registration token is required.");
}
_registrationToken = value;
}
}
public bool is_active { get; set; } = true;
public bool is_email_confirmed { get; set; } = false;
public bool is_owner { get; set; } = false;
public void disable()
{
is_active = false;
}
public void enable()
{
is_active = true;
}
public void markEmailAsConfirmed()
{
is_email_confirmed = true;
}
public User checkAuthenticity(string username, string password)
{
var user = _userRepo.getByUsername(username);
if (user == null || !user.is_active || !user.is_email_confirmed)
{
return null;
}
if (!_passwordHash.ValidatePassword(password, user.password))
{
return null;
}
return user;
}
public void enable(ModificationDto dto)
{
try
{
var user = _userRepo.getById(dto.id) ?? throw new ItemNotFoundException($"User with the id {dto.id} doesnot exist.");
user.enable();
user.modified_date = DateTime.Now;
user.modified_by = dto.modified_by;
_userRepo.update(user);
}
catch (Exception)
{
throw;
}
}
public void markEmailRegistered(string token)
{
try
{
var encryptedToken = _cryptography.Encrypt(token);
var user = _userRepo.getByToken(encryptedToken) ?? throw new ItemNotFoundException($"Token didnot match.");
if (user.is_email_confirmed)
{
return;
}
user.markEmailAsConfirmed();
_userRepo.update(user);
}
catch (Exception ex)
{
throw ex;
}
}
public void save()
{
try
{
bool isUsernameValid = checkNameValidity();
if (!isUsernameValid)
{
throw new DuplicateItemException("User with same name already exists.");
}
user.created_date = DateTime.Now;
user.created_by = user_dto.created_by;
user.password = _passwordHash.CreateHash(this.password);
_userRepo.insert(user);
}
catch (Exception)
{
throw;
}
}
public void update(UserDto user_dto)
{
try
{
User user = _userRepo.getById(user_dto.user_id) ?? throw new ItemNotFoundException($"User with the id {user_dto.user_id} doesnot exist.");
bool isUsernameValid = checkNameValidity(user_dto);
if (!isUsernameValid)
{
throw new DuplicateItemException("User with same name already exists.");
}
_userAssembler.copy(user, user_dto);
user.modified_by = user_dto.modified_by;
user.modified_date = DateTime.Now;
_userRepo.update(user);
USER_DETAIL user_detail = new USER_DETAIL(user_dto);
user_detail.saveOrUpdate();
}
catch (Exception)
{
throw;
}
}
private bool checkNameValidity()
{
var userWithSameEmail = _userRepo.getByUsername(this.email);
return userWithSameEmail == null || userWithSameEmail.id == this.id;
}
}
public class USER_DETAIL : BaseEntity
{
private readonly UserDetailRepository _userDetailRepo;
private readonly FileHelper _fileHelper;
private string _permanentAddress, _firstName, _lastName;
private long _userId;
protected USER_DETAIL() { }
public USER_DETAIL(UserDetailRepository userDetailRepo, FileHelper fileHelper)
{
_userDetailRepo = userDetailRepo;
_fileHelper = fileHelper;
}
public USER_DETAIL(UserDetailDto user_detail_dto)
{
this.first_name = user_detail_dto.first_name;
this.last_name = user_detail_dto.last_name;
this.user_id = user_detail_dto.user_id;
this.permanent_address = user_detail_dto.permanent_address;
this.temporary_address = user_detail_dto.temporary_address;
this.primary_contact = user_detail_dto.primary_contact;
this.secondary_contact = user_detail_dto.secondary_contact;
this.image_path = user_detail_dto.image_path;
}
public USER_DETAIL(UserDto user_dto)
{
this.user_id = user_dto.user_id;
this.first_name = user_dto.user_detail_dto.first_name;
this.permanent_address = user_dto.user_detail_dto.permanent_address;
this.temporary_address = user_dto.user_detail_dto.temporary_address;
this.primary_contact = user_dto.user_detail_dto.primary_contact;
this.secondary_contact = user_dto.user_detail_dto.secondary_contact;
this.modified_by = user_dto.modified_by;
this.modified_date = DateTime.Now;
}
public long user_id
{
get => _userId;
set
{
if (value <= 0)
{
throw new InvalidValueException("Invalid user id.");
}
_userId = value;
}
}
[Required]
[MaxLength(50)]
public string first_name
{
get => _firstName;
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new NonEmptyValueException("First name must be specified.");
}
_firstName = value;
}
}
[Required]
[MaxLength(50)]
public string last_name
{
get => _lastName;
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new NonEmptyValueException("Last name must be specified.");
}
_lastName = value;
}
}
[Required]
[MaxLength(100)]
public string permanent_address
{
get => _permanentAddress;
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new NonEmptyValueException("Address must be specified.");
}
_permanentAddress = value;
}
}
[MaxLength(100)]
public string temporary_address { get; set; }
[Required]
[MaxLength(15)]
public string primary_contact { get; set; }
[MaxLength(15)]
public string secondary_contact { get; set; }
[MaxLength(100)]
public string image_path { get; set; }
[ForeignKey(nameof(user_id))]
public virtual User user { get; set; }
public string getFullName()
{
return $"{first_name} {last_name}";
}
public void saveOrUpdate(UserDetailDto user_detail_dto, IFormFile file = null)
{
try
{
var userDetail = _userDetailRepo.getByUserId(user_detail_dto.user_id);
if (userDetail == null)
{
save(user_detail_dto, file);
}
else
{
update(user_detail_dto, userDetail, file);
}
}
catch (Exception)
{
throw;
}
}
private void update(UserDetailDto user_detail_dto, USER_DETAIL user_detail, IFormFile file)
{
if (file != null)
{
if (user_detail.image_path != null)
{
deleteImage(user_detail.image_path);
}
user_detail_dto.image_path = _fileHelper.saveFileAndGetFileName(file);
}
// no idea how data is copied from user_detail_dto to user_detail entity as passing dto to this class in constructor creates a new instance of entity and all the set properties , which are not passed from view (client side) are lost
// Or is it that I need to copy all the values from dto to entity here again?
user_detail.modified_by = user_detail_dto.modified_by;
user_detail.modified_date = DateTime.Now;
_userDetailRepo.update(user_detail);
}
private void save(UserDetailDto user_detail_dto, IFormFile file)
{
var userDetail = new USER_DETAIL(user_detail_dto);
if (file != null)
{
userDetail.image_path = _fileHelper.saveFileAndGetFileName(file);
}
userDetail.created_by = user_detail_dto.created_by;
userDetail.created_date = DateTime.Now;
_userDetailRepo.insert(userDetail);
}
private void deleteImage(string iMAGE_PATH)
{
_fileHelper.deleteImage(iMAGE_PATH, _fileHelper.getPathToImageFolder());
}
}
User_Detail entity injected in User class is also an entity. I don't think this is good by any means and not sure even if it works. Multiple constructors are present in User entity. How does dependency injection resolve this ? I am having trouble even to solve compile time problem . I didn't find any sample/example of code that is dependent or uses dependency injection principle in domain model. Can anyone help me on this?