Как отслеживать последнего пользователя, внесшего изменения в объект в DDD?

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

В 90% объектов моего домена я хочу знать последнего пользователя, который внес изменения в него. Мне не нужен полный контрольный журнал - это слишком много для моих нужд.

Все мои классы реализуют абстрактный базовый класс, содержащий:

public abstract class Base
 {
 public User LastChangedBy { get; set; }
 public DateTime LastChangedDate { get; set; }
 }

Вариант 1: выполняет принципы DDD, но не настолько элегантно

Никогда не позволяйте объекту вводить недопустимое состояние

public abstract class Base
{
 public User LastChangedBy { get; protected set; }
 public DateTime LastChangedDate { get; protected set; }
}
public SomeObject
{
 .....
 SomeBehaviorThatChangesObject(User changedBy, ...)
 AnotherBehaviorThatChangesObject(User changedBy, ...)
}

Мне нужно, чтобы все мои сеттеры были приватными, базовые классы устанавливались как защищенные. Каждое изменение объектов должно выполняться с помощью метода, который принимает (User changedBy) в качестве параметра.

Очень безопасно, НО, так как пользователь может сделать 6 изменений объекта, я должен предоставить объект User для каждого из них. Ну, на самом деле, я должен предоставить это почти для каждого метода в моей модели домена...

Вариант 2: не самый лучший, но я прочитал об этом

Ввести поле bool IsValid. Во всех сеттерах я установил IsValid в false. Создал метод AcceptChanges (User changesAcceptedBy), который устанавливает это поле в true. Не забудьте всегда проверять, действителен ли объект до его сохранения.

public abstract class Base
 {
 public bool IsValid {get; protected set;}
 public User LastChangedBy { get; protected set; }
 public DateTime LastChangedDate { get; protected set; }
 }
public SomeObject
{
 public Object Propery{get; set{IsValid = false; ...}}
 .....
 SomeBehaviorThatChangesObject(...)
 {
 //change the object
 IsValid = false;
 }
}

Вариант 3: мои глаза наиболее практичны, но не очень DDD

Сделайте это в слое persistance в UnitOfWork или в репозиториях, таких как репозиторий .SaveChanges(User changedBy). Я, или тот, кто реализует эту часть, может забыть об этом, и он оставит объект в недопустимом состоянии...

public SomeRepository
 {
 public void Update (User changedBy)
 {...}
 }

Это совершенно нормально, чтобы иметь возможность увидеть, кто изменил объект последним, но я не видел никаких хороших примеров для его внедрения DDD. Как вы это делаете?

Обновление В ответ на решение jgauffins: Спасибо, Base действительно реализует интерфейс, и я очень сильно переусердствовал, чтобы не перегружать информацию. Я чувствую, что это не изящно, потому что каждому методу нужен мой пользовательский объект в качестве параметра для каждого изменения, и мне внезапно приходится абсурдно делать все сеттеры частным...

Помещение в репо может выполнить эту работу, но приведенный выше аргумент верен, и я не думал об этом, поэтому именно поэтому я спросил. Я фактически планирую службу, которая может потребовать изменить некоторые объекты.

Это большое изменение, меняя сотни методов и свойств, поэтому я хочу быть shure. А также я приведу несколько методов, чтобы установить одно поле, как я упоминал в этом сообщении, где мне не нужен метод, потому что "set" описывает достаточно для пользователь знает, что такое изменение... Мое чувство кишки, однако, говорит, что ты прав, просто хотел посмотреть, есть ли альтернативные способы сделать то же самое.

Окончательное обновление:

Читая все комментарии, вопросы и предлагаемые решения, я понял, что мне, вероятно, пришлось переосмыслить, как я посмотрел поля LastChangedBy и LastChangedDate. Для некоторых объектов это действительно относится к чему-то, что я считаю принадлежащим домену. Для многих других это действительно одитинг, который я ищу. Я не понимал разницы.

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

Вместо того, чтобы смешивать это с LastChangedBy, я создаю поля для LastEditedBy. Это может быть даже список редакторов, если мне нужно отслеживать каждое изменение.

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

Чтобы выполнить аудит, я сделал следующее:

Прежде всего, я разделил базовый класс на интерфейсы, которые необходимо реализовать. LastChangedBy и LastChangedDate определены в IAuditable интерфейсе, объекты которого должны быть выполнены.

Затем переопределить DbContext.SaveChanges() следующим образом:

public override int SaveChanges()
 {
 if(ChangeTracker.Entries<iauditable>().Any())
 throw new InvalidOperationException
 (
 "Tried to save changes on an object that is needs to be Audited. Please provide the User that makes the changes!"
 );
 return base.SaveChanges();
 }
 public int SaveChanges(User changedBy)
 {
 var entries = ChangeTracker.Entries<iauditable>().Where(entry=>entry.State == EntityState.Modified);
 foreach (var dbEntityEntry in entries)
 {
 dbEntityEntry.Entity.Audit(DateTime.Now, changedBy);
 }
 return base.SaveChanges();
 }
</iauditable></iauditable>

Конечно, есть и другие способы сделать это, но это походило на начало как минимум.

Теперь я понимаю, что мой вопрос мог быть лучше, но, честно говоря, я не понимал, что часть проблемы заключалась в том, что я хотел отслеживать изменения по разным причинам на разных объектах. Пример с приведенным выше документом - это только один из объектов, в которых LastChangedBy не является тем, что мне нужно установить или отслеживать в моем домене, но его можно переписать, чтобы быть более похожим, как описано выше.

2 ответа

Я начал с Thread.CurrentPrincipal. Но если вы собираетесь переключиться на асинхронную обработку (например, используя команды, как я описываю здесь), позже использовать Thread.CurrentPrincipal невозможно.

Но сначала есть фундаментальная проблема:

public abstract class Base
{
 public User LastChangedBy { get; protected set; }
 public DateTime LastChangedDate { get; protected set; }
}

Это не базовый класс. Он не добавляет никаких функций. Вместо этого вы должны изменить его, используя интерфейс с именем ITrackChanges или аналогичным.

public SomeObject
{
 .....
 SomeBehaviorThatChangesObject(User changedBy, ...)
 AnotherBehaviorThatChangesObject(User changedBy, ...)
}

Что я делаю. Почему вы думаете, что это не изящно? Администратор или справочная служба могут делать что-то от имени пользователя с таким подходом.

Обновление

Я чувствую, что это не изящно, потому что каждый метод будет нуждаться в моем объекте пользователя в качестве параметра для каждого изменения

Ну. Это деловое требование, не так ли? Вы должны изменить свойство LastChangedBy. Следовательно, он должен быть предоставлен.

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


... Или вы можете установить пользователя в Thread.CurrentPrincipal и не сделать его частью какого-либо интерфейса.

Это хорошо работает с любой идентичной моделью, заслуживающей внимания, будь то на веб-сайте или настольном приложении с пользовательской аутентификацией или аутентификацией AD.

Чтобы развернуть, ваш репозиторий и т.д. может быть запущен, если пользователь не установлен, а также вы все еще можете выполнять разрешения на роль или на основе утверждений, чтобы предотвратить/разрешить определенные типы обновлений на основе идентификационных данных пользователя, если вы так пожелаете.

licensed under cc by-sa 3.0 with attribution.