Здравствуйте, 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 подход.