Re[2]: Аудит доменной модели
От: alex_angel  
Дата: 10.07.09 17:57
Оценка:
Здравствуйте, Alkersan, Вы писали:

Привет, позволю себе вставить несколько коментариев и отзывов

A>Сегодня попробовал реализовать историю изменений. Вот что получилось:


A>DomainObject<T> — взят из статьи на codeproject.com — здесь; он отвечает за управление идентификатором (Id) сущности и позволяет отличать сохраненные (Persistent) объекты, новосозданных (Transient) исходя из значения идентификатора.


Скажи, у тебя заказчик просил идентификатор? Прямо так и сказал, что нужен id ему для каких-то целей?? Я в этом сильно сомневаюсь. Раз так — идентификатор ни при каких обстоятельствах не должен быть в доменном объекте. Домен — это твой бизнес. Если в бизнесе нет понятий идентификатора, следовательно, не должно его быть и у тебя. Для таких вещей, как разделение persistent объектов, есть паттерн UnitOfWork. (а я раньше codeproject.com считал неплохим сайтом..жуть)

A>Этот класс расширяется классом AuditableDomainObject<T>, которым я "помечаю" те объекты, подлежат аудиту. Он содержит такой функционал: коллекцию AuditLog и обертку для нее (собственно сам AuditLog и есть обертка, а сама коллекция это список записей IList<AuditLogEntry>) — в этой коллекции накапливаются изменеия с момента последней транзакции. Также существует коллекция auditableEntitys, в которой хрянятся свойства данного объекта, которые "поддаются" аудиту. Т.е. возможна ситуация, что не за всеми полями объекта Resume нужно следить, тогда такое поле просто не попадает в эту коллекцию, и его измения игнорируются. Метод LogChanges() проходится по этой коллекции полей, выявляет изменившиеся или же новосозданные значения ( поле считается созданным — если объект Transient,т.е. если у него идентификатор имеет значение по умолчанию, в данном случае 0, или же поле считается изменённым, если первоначальное значение не равно текущему, см. далее ).


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


A>Собственно поля полежащие аудиту — это члены класса AuditableEntity<T>. Содержат 2 члена — Original и Current, начальное значение и текущее соответсвенно. На основе их принимается решение — писать ли данное состояние в лог или нет. Также есть строковый член FieldName с "человеческим" названием текущего поля (_FName — это "Фамилия" например). Несколько конструкторов... и метод LogChanges(), который пишет в переданный при создании экземпляр AuditLog ткущее состояние. Этот метод вызывается извне, при опросе коллекции auditableEntitys. В эти моменты и происходит запись в лог.


держать Original и Current не имеет смысла. Что если было много изменений, какое считать Original? Если последнее, то можно просто хранить коллекцию и сортировать их при выгрузке по дате создания, например.
Ты вроде говорил, что у тебя есть разделение по слоям!!! тогда что значем "человеческое" название? то, что хочет видеть на экране пользователь? так пусть это лежит не в домене, а в презентационной логике. Опять же, если это не бизнес — нечего ему тут делать.


ну а теперь немного кода:

 public class Resume2 : AuditableObject<AuditableEntity2> {
        //non-changed fields
    }

    public class AuditableEntity2 : ICloneable<AuditableEntity2> {
        protected string FName { get; set; }
        protected string LName { get; set; }
        protected string SName { get; set; }

        protected DateTime? BirthDate { get; set; }

        public AuditableEntity2 Clone() {
            //just clone
            return null;
        }
    }

    public abstract class AuditableObject<TAuditable> where TAuditable: ICloneable<TAuditable>, new() {
        private readonly List<AuditLog<TAuditable>> history = new List<AuditLog<TAuditable>>() ;
        private AuditLog<TAuditable> transient;

        public TAuditable this[DateTime timePoint] {
            get {
                return transient != null
                           ?
                               transient.Version
                           :
                               history.Find(
                                   transaction =>
                                   timePoint >= transaction.EffectiveFrom && timePoint < transaction.EffectiveTo).
                                   Version.Clone();
            }
        }

        public void AddVersion(DateTime effectiveFrom, DateTime effectiveTo) {
            transient = new AuditLog<TAuditable>(effectiveFrom, effectiveTo, new TAuditable());
        }

        public void Commit() {
            if (transient != null) {
                history.Add(transient);
            }
            transient = null;
        }
    }

    public interface ICloneable<T> {
        T Clone();
    }

    internal class AuditLog<TAuditable> {
        public AuditLog(DateTime effectiveFrom, DateTime effectiveTo, TAuditable version) {
            EffectiveFrom = effectiveFrom;
            EffectiveTo = effectiveTo;
            Version = version;
        }

        public TAuditable Version { get; private set; }
        public DateTime EffectiveFrom { get; private set; }
        public DateTime EffectiveTo { get; private set; }
    }


и естественно, вопросы коммита в базу доменного объекта можно легко делать с помощью NHibernate — закоммитить только трансиентный лог и все, вы получаете историю. Все данные о истории хранятся в доменном объекте.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.