... спросите вы, и для этого вопроса будут все основания.
Для начала рассмотрим код, каким он является до рефакторинга в строну сокрытия его состояния:
class Review : RootAggregate
{
public Review(string fileName, string description)
{
FileName = fileName;
Description = description;
}
public static Review Create(string fileName, string description)
{
return new Review(fileName, description);
}
public string FileName { get; private set; }
public string Description { get; private set; }
}
ну и тест, который где-то там проверяет, что все свойства верно проинициализированы:
[Test]
public void Should_set_the_properties()
{
createdReview.FileName.Should().Be.EqualTo(FIRST_NAME);
createdReview.Description.Should().Be.EqualTo(DESCRIPTION);
}
И именно в тестах такого рода и будет проблема, потому как при сохранении дизайна домена, мы сможем упрятать состояние, только сделав его internal. И это даже идеально подходит для случая, когда структура солюшна подразумевает наличие отдельной сборки для каждого агрегата. В любом случае, это половинчатое решение, которое в простонародье называется "хак".
Мы же попробуем найти именно архитектурное решение, которое не будет зависеть ни от каких инфраструктурных сиюминутных особенностей.
А теперь нашу модель ждет самое большое потрясение в ее короткой жизни. Для начала познакомимся с паттерном Domain Event. Вкратце (для нашего случая), это абстракция для событий, которые генерируются в домене и извещают подписчиков об изменении состояния этого самого домена.
В представленном выше коде единственное событие, которое может происходить – создание нового экземпляра Review. После небольшой модификации наш код будет выглядеть следующим образом:
...
private string _fileName;
private string _description;
...
public static Review Create(string fileName, string description)
{
var review = new Review(fileName, description);
_messageBus.Publish(new ReviewCreatedDomainEvent(fileName, description));
return review;
}
...
}
class ReviewCreatedDomainEvent
{
public string Description { get; private set; }
public string FileName { get; private set; }
public ReviewCreatedDomainEvent(string fileName, string description)
{
FileName = fileName;
Description = description;
}
}
И таким образом наша задача в тестах превращается в почти тривиальную (после некоторых манипуляций с кодом на тему перехвата отправляемых сообщений):
[Test]
public void Should_set_the_properties()
{
Catch()
.From(() => {
Review.Create(FILE_NAME, DESCRIPTION);})
.And
.Assert.That(e => e.FileName.Should().Be.EqualTo(FILE_NAME))
.Assert.That(e => e.Description.Should().Be.EqualTo(DESCRIPTION));
}
Что и требовалось доказать.
На самом деле, решение проблем с тестированием в данном случае является лишь поводом заикнуться о Domain Events. Разработка модели на основе событий пополам с Commands-Queries Separation Principle на сегодняшний день является, пожалуй, одним из самых мощных архитектурных стилей, позволяющих разрабатывать действительно гибкие, устойчивые и масштабируемые системы.
В следующих постах попробуем разработать эту тему глубже.
Добро пожаловать в высшую лигу :)
| Опубликовать | Tweet |
Очень интересно. Только ко вот непонятно где это применить кроме как для тестирования. И не будут ли сквозные сообщения это увеличивать связность системы?
ОтветитьУдалитьХотелось бы увидеть ссылку на первоисточник.
Спасибо.
Связность между компонентами системы в случае использования обмена сообщениями как бы ниже, чем при прямом вызове методов. Если в лоб, то потому, что для того, чтобы "вызвать" какой-то метод теперь совсем не не обязательно знать ничего об "ендпоинте" реализации.
ОтветитьУдалитьА первоисточник... Да вот пусть хотя бы этот, хотя написано по этой теме уже тонна всего.