среда, 4 ноября 2009 г.

CQS, приватное состояние – а как же тесты?

... спросите вы, и для этого вопроса будут все основания.

Для начала рассмотрим код, каким он является до рефакторинга в строну сокрытия его состояния:

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 на сегодняшний день является, пожалуй, одним из самых мощных архитектурных стилей, позволяющих разрабатывать действительно гибкие, устойчивые и масштабируемые системы.

В следующих постах попробуем разработать эту тему глубже.

Добро пожаловать в высшую лигу :)

Опубликовать

2 комментария:

  1. Очень интересно. Только ко вот непонятно где это применить кроме как для тестирования. И не будут ли сквозные сообщения это увеличивать связность системы?

    Хотелось бы увидеть ссылку на первоисточник.

    Спасибо.

    ОтветитьУдалить
  2. Связность между компонентами системы в случае использования обмена сообщениями как бы ниже, чем при прямом вызове методов. Если в лоб, то потому, что для того, чтобы "вызвать" какой-то метод теперь совсем не не обязательно знать ничего об "ендпоинте" реализации.

    А первоисточник... Да вот пусть хотя бы этот, хотя написано по этой теме уже тонна всего.

    ОтветитьУдалить