Re[10]: DDD протаскивание других слоев через параметры методов Domain
От: #John Европа https://github.com/ichensky
Дата: 26.11.20 00:20
Оценка: 8 (1)
Здравствуйте, samius, Вы писали:

S>То, что не требует мокать — лучше тестируется. Полагаю, что это правда, согласен с автором. Он ведь не утверждает о невозможности.


Допустим у нас есть пользователь у него есть контактная информация и нам нужно добавить
бизнес логику, когда пользователь нажимает кнопку "Save", нам приходят данные: `Info` и `email` и мы их обрабатываем.

В DDD это выглядело бы вот так:

  Domain Layer
    public class User
    {
        public Guid Id { get; private set; }

        public string UserName { get; private set; }

        public string Info { get; private set; }

        public ContactInformation ContactInformation { get; private set; }

        protected internal User() { }

        /// <summary>
        /// Used for loading for ex. from the DB
        /// </summary>
        public static User Create(Guid id, string userName, string info, ContactInformation contactInformation)
        {
            return new User
            {
                Id = id,
                Info = info,
                UserName = userName,
                ContactInformation = contactInformation,
            };
        }

        /// <summary>
        /// Used for creation 
        /// </summary>
        public static User Create(string userName, string info, ContactInformation contactInformation)
        {
            if (string.IsNullOrWhiteSpace(userName))
            {
                throw new ArgumentException();
            }
            // other validation ...

            var id = Guid.NewGuid();

            return Create(id, info, userName, contactInformation);
        }

        public virtual ContactInformation GetContactInformation() => ContactInformation;

        public virtual void UpdateInformation(string info, string email)
        {
            // validation ..

            this.Info = info;
            GetContactInformation().UpdateEmail(email);
        }
    }
    public class ContactInformation
    {
        public Guid Id { get; private set; }

        public string Phone { get; private set; }

        public string Email { get; private set; }

        protected internal ContactInformation() { }

        public static ContactInformation Create(Guid id, string phone, string email) => new ContactInformation
        {
            Id = id,
            Phone = phone,
            Email = email
        };

        public static ContactInformation Create(string phone, string email)
        {
            var id = Guid.NewGuid();
            return Create(phone, email);
        }

        public static ContactInformation Create(string email) => Create(null, email);

        internal virtual void UpdateEmail(string newEmail)
        {
            if (newEmail.Contains("admin"))
            {
                throw new ArgumentException();
            }

            this.Email = newEmail;
        }
    }


  Infrastructure Layer
    public class UserRepository
    {

        public User Load(Guid id)
        {
            // Loading from the DB ...
            var contactInformation = ContactInformation.Create(Guid.NewGuid(), "bob@example.com", "+123456");
            var user = User.Create(id, "Bob", "Smth", contactInformation);
            return user;
        }
        public void Save(User user)
        {

        }
    }


  Application Layer
   public class UserInformationDto
    {
        public string Email { get; set; }
        public string Info { get; set; }
    }

    public class UserService
    {

        // IUserRepository
        private UserRepository userRepository;

        public UserService(UserRepository userRepository)
        {
            this.userRepository = userRepository;
        }

        public void UpdateUserInformation(Guid userId, UserInformationDto userInformation)
        {

            // validation ..

            var user = userRepository.Load(userId);

            user.UpdateInformation(userInformation.Info, userInformation.Email);

            userRepository.Save(user);
        }
    }


В трехслойной/четырехслойной архитектуре с анемичными моделями, нам точно так уже пришлось бы мокать `IChangeContactInformationService`
и метод "GetContactInformation". Потому разницы в сложности поддержания/написания тестов — нет.

  Unit tests
public class UserTests
    {
        private User user;
        private ContactInformation contactInformation;

        public UserTests()
        {
            contactInformation = Mock.Of<ContactInformation>();
            user = Mock.Of<User>();
        }

        [Fact]
        public void UpdateInformationTest()
        {
            // Arrange
            var email = "bob2@example.com";
            Mock.Get(user).Setup(x => x.GetContactInformation()).Returns(contactInformation);

            // Act
            var ex = Record.Exception(() => user.UpdateInformation("xxx", email));

            // Assert
            Assert.Null(ex);
        }
    }


Бизнес логика в одном месте, работа с данными и все остальное отдельно, все легко тестируется и поддерживается.
Если придется работать с внешним сервисом, придется создавать DomainService и дробить бизнес логику в rich-моделях.

Из дополнительных плюсов с DDD мы легко можем применить CQRS подход.
Підтримати Україну у боротьбі з країною-терористом.

https://prytulafoundation.org/
https://u24.gov.ua/

Слава Збройним Силам України!!! Героям слава!!!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.