Применяете ли Вы события к модели домена немедленно, когда все еще есть возможность бесполезных событий или отмены?


Каждый пример поиска событий, который я вижу, предназначен для интернета. Это, кажется, особенно хорошо работает с архитектурой MVC, где представления на стороне клиента не работают с доменным кодом, а интерактивность ограничена. Я не совсем уверен, как экстраполировать на богатое настольное приложение, где пользователь может редактировать список или выполнять какую-то другую длительную задачу.

Модель домена является персистентно-агностической и Презентационно-агностической и может быть изменена только путем применения домена события к агрегатному корню. Мой конкретный вопрос: Должен ли код представления видоизменять модель домена, пока пользователь вносит незафиксированные изменения?

  1. Если код представления не изменяет модель домена,как вы применяете логику домена? Было бы неплохо иметь мгновенную проверку домена и вычисления домена, чтобы всплывать к модели представления, когда пользователь редактирует. В противном случае вам придется дублировать нетривиальную логику в модели представления.

  2. Если код презентации действительно изменяет модель домена, как вы реализуете отмену? Там нет доменного события undelete, так как понятие отмены существует только в незафиксированном сеансе редактирования, и я бы не хотел добавлять версию отмены каждого события. Хуже того, мне нужна способность отменять события, вышедшие из строя. Вы просто удаляете событие и воспроизводите все на каждом отмене? (Отмена также происходит, если текстовое поле возвращается в свое предыдущее состояние, например.)

  3. Если код презентации видоизменяет ли модель домена, лучше ли сохранять каждое событие, выполняемое пользователем, или просто конденсировать активность пользователя до простейшего набора возможных событий? Для простого примера представьте, что вы меняете поле комментариев снова и снова. закончился перед спасением. Вы действительно будете упорствовать каждый промежуточный этап CommentChangedEvent в том же поле во время того же редактирования сеанс? Или для более сложного примера, пользователь будет изменение параметров, запуск оптимизационного расчета, корректировка параметры, повторный запуск вычисления и т. д., Пока этот пользователь не будет удовлетворен самым последним результатом и фиксирует изменения. Я не думаю, что кто-то будет считать все промежуточные события стоящими сохраняющий. Как бы вы сохранили это в сжатом виде?

Существует сложная совместная доменная логика, которая заставила меня думать, что DDD/ES - это путь. Мне нужна картина того, как взаимодействуют модели представления богатых клиентов и модели предметной области, и я надеюсь на простоту и элегантность.

2 4

2 ответа:

Я не вижу, чтобы настольные DDD-приложения так сильно отличались от MVC-приложений, у вас могут быть те же самые слои, за исключением того, что они в основном не разделены сетью.

Приложения CQRS/ES лучше всего работают спользовательским интерфейсом на основе задач , где вы выполняете команды, отражающие намерения пользователя. Но под задачей мы не подразумеваем каждое действие, которое пользователь может предпринять на экране, оно должно иметь смысл и цель в домене. Как вы справедливо заметили в 3., нет необходимости моделировать каждую микро модификацию как полную полноценная команда DDD и связанное с ней событие. Это может загрязнить ваш поток событий.

Таким образом, у вас будет в основном два уровня:

Действие уровня пользовательского интерфейса

Ими можно полностью управлять на уровне представления. Они складываются, чтобы в конечном итоге быть сопоставлены с одной командой, но вы можете отменить их по отдельности довольно легко. Ничто не мешает вам моделировать их как микро-события, которые инкапсулируют замыкания для do и undo, например. Я никогда не видел ... "cherrypickable" отменяется в любом пользовательском интерфейсе, и я действительно не вижу смысла, но это должно быть осуществимо и доступно пользователю, пока действия коммутативны (их эффект не зависит от порядка выполнения).

Задача уровня домена

Более грубая активность, представленная командой и соответствующим событием. Если вам нужно отменить эти, я бы предпочел добавить новое событие отката в поток событий, чем пытаться удалить существующее ("не изменить прошлое " ).

Отражение инвариантов предметной области и вычислений в UI

Именно здесь вам действительно нужно правильно провести различие между двумя типами задач, потому что действия пользовательского интерфейса, как правило, не обновляют ничего на экране, кроме нескольких основных проверок (обязательные поля, форматы строк и чисел и т. д.) Выдача команд, с другой стороны, приведет к обновленному представлению модели, но вам, возможно, придется материализовать действие с помощью некоторых кнопка подтверждения.

Если ваши Ui В основном предназначены для отображения вычисленных чисел и проекций пользователю, это может быть проблемой. Вы можете поместить вычисления в отдельную службу, вызываемую пользовательским интерфейсом, а затем выдать команду модификации со всеми обновленными вычисляемыми значениями, когда пользователь сохраняет. Или вы можете просто отправить команду с одним измененным параметром, и домен вызовет ту же службу вычислений. Оба они на самом деле ближе к CRUD и, вероятно, приведут к анемичная доменная модель, хотя ИМО.

Я закончил делать что-то вроде этого, хотя у меня есть хранилище, управляющее транзакциями.

В основном все мои репозитории реализуют

public interface IEntityRepository<TEntityType, TEventType>{
    TEntityType ApplyEvents(IEnumerable<TEventType> events);
    Task Commit();
    Task Cancel();
}

Таким образом, в то время как ApplyEvents обновит и вернет сущность, я также сохраняю начальную версию внутренне, пока не будет вызвана фиксация. Если вызывается отмена, я просто меняю их обратно, отбрасывая события.

Очень хорошая особенность этого - я только толкаю события в сеть, БД и т. д., Как только транзакция завершена. Осуществление репозиторий будет зависеть от базы данных или веб-служб, но все, что нужно знать вызывающему коду, - это фиксация или отмена.

Править Для отмены вы сохраняете старую сущность, обновленную сущность и события с тех пор в структуре памяти. Что-то вроде

 public class EntityTransaction<TEntityType, TEventType>
{ 
    public TEntityType oldVersion{get;set;}
    public TEntityType newVersion{get;set;}
    public List<TEventType> events{get;set;}
}

Тогда ваши ApplyEvents будут выглядеть так, для пользователя

private Dictionary<Guid, EntityTransaction<IUser, IUserEvents>> transactions;

public IUser ApplyEvents(IEnumerable<IUserEvent> events)
{
    //get the id somehow
    var id = GetUserID(events);
    if(transactions.ContainsKey(id) == false){
        var user = GetByID(id);
        transactions.Add(id, new EntityTransaction{
             oldVersion = user;
             newVersion = user;
             events = new List<IUserEvent>()
        });
    }

    var transaction = transactions[id];
    foreach(var ev in events){
        transaction.newVersion.When(ev);
        transaction.events.Add(ev);
    }
}

Затем в вашей отмене вы просто заменяете старую версию новой, если вы отменяете транзакцию.

Есть смысл?