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