... спросите вы, и для этого вопроса будут все основания.
Для начала рассмотрим код, каким он является до рефакторинга в строну сокрытия его состояния:
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 |
Очень интересно. Только ко вот непонятно где это применить кроме как для тестирования. И не будут ли сквозные сообщения это увеличивать связность системы?
ОтветитьУдалитьХотелось бы увидеть ссылку на первоисточник.
Спасибо.
Связность между компонентами системы в случае использования обмена сообщениями как бы ниже, чем при прямом вызове методов. Если в лоб, то потому, что для того, чтобы "вызвать" какой-то метод теперь совсем не не обязательно знать ничего об "ендпоинте" реализации.
ОтветитьУдалитьА первоисточник... Да вот пусть хотя бы этот, хотя написано по этой теме уже тонна всего.