0

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?

0

It looks like you don't really have any logic here. Its all validation and persistence.

Basically its a pretty simple conversion though.

ADM

class Service
{
    void SendPackage(User user)
}

class User
{
    string Id;
    string Name;
}

Fat Model

class User
{
    string Id;
    string Name;
    void SendPackage() {...}
}

I would keep the repo seperate and not inject it.

class Repository
{
    void Save(User user);
}

I think its been pretty much proven that injecting the repos does nothing to help your code

  • "I think its been pretty much proven that injecting the repos does nothing to help your code" - for me this is conflating concepts - the "fat domain model" should be concerned with logical operations on a logical model, repositories are persistence which is a different beast altogether and belong at an impure orchestration/service level. – Ant P Sep 3 at 7:59
  • @AntP I agree, but the OP has injected the repos. I think its a pretty common thing to do. – Ewan Sep 3 at 8:01
  • In ADM, I take Dto as parameter in Service. So , I am having problem implementing it in Fat Model – Dot Net developer Sep 3 at 8:03
  • so do i in my example. when you move to a fat model your dto becomes the model – Ewan Sep 3 at 8:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.