Как внедрить аудит на бизнес-уровне

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

Все функции, которые я хочу проверить, находятся на бизнес-уровне, и я хотел бы создать объект аудита, в котором хранится дата и время вызова функции, включая результат.

Недавно я посетил конференцию, и одна из сессий была посвящена хорошо продуманным веб-приложениям, и я пытаюсь реализовать некоторые идеи. В основном я использую Enum для возврата результата функции и использую оператор switch для обновления пользовательского интерфейса на этом уровне. Функции используют ранний возврат, который не оставляет времени для создания, настройки и сохранения аудита.

Мой вопрос заключается в том, какие подходы используют другие при аудите бизнес-функций, и какой подход вы бы выбрали, если бы у вас была функция, подобная моей (если вы скажете, бросьте ее, я выслушаю, но я буду сварливым).

Код выглядит примерно так:

function Login(string username, string password)
{
 User user = repo.getUser(username, password);

 if (user.failLogic1) { return failLogic1Enum; }
 if (user.failLogic2) { return failLogic2Enum; }
 if (user.failLogic3) { return failLogic3Enum; }
 if (user.failLogic4) { return failLogic4Enum; }

 user.AddAudit(new (Audit(AuditTypeEnum LoginSuccess));
 user.Save();

 return successEnum;
}

Я мог бы расширить операторы if, чтобы создать новый аудит в каждом из них, но тогда функция начинает запутываться. Я мог бы провести аудит на уровне пользовательского интерфейса в операторе switch, но это кажется неправильным.

Действительно ли плохо вставлять все это в try catch с finally и использовать finally для создания объекта аудита и установки его информации, таким образом решая проблему раннего возврата? У меня сложилось впечатление, что a finally предназначен для очистки, а не для одитинга.

Меня зовут Дэвид, и я просто пытаюсь стать лучше. Спасибо.


person David A Gibson    schedule 30.06.2011    source источник


Ответы (2)


Я не могу сказать, что использовал его, но он кажется кандидатом на аспектно-ориентированное программирование< /а>. По сути, вы можете вводить код в каждый вызов метода для таких вещей, как ведение журнала/аудит/и т. д. в автоматическом режиме.

Отдельно создание блока try/catch/finally не идеально, но я бы проверил стоимость/выгоду, чтобы увидеть, стоит ли оно того. Если вы можете разумно дешево рефакторить код, чтобы вам не пришлось его использовать, сделайте это. Если бы стоимость была непомерной, я бы попробовал/наконец-то. Я думаю, что многие люди зацикливаются на «лучшем решении», но время/деньги всегда являются ограничениями, поэтому делайте то, что «имеет смысл».

person skaz    schedule 30.06.2011
comment
Привет, читая об аспектно-ориентированном программировании, он звонит в колокола с другими аспектами сеанса. Практически бесшумно внедрить бизнес-логику none на бизнес-уровень, так что я посмотрю на это, спасибо. - person David A Gibson; 30.06.2011

Проблема с перечислением в том, что оно не является расширяемым. Если вы добавите новые компоненты позже, ваша платформа аудита не сможет обрабатывать новые события.

В нашей последней системе с использованием EF мы создали базовый POCO для нашего события аудита в пространстве имен сущности:

public class AuditEvent : EntityBase
{
    public string Event { get; set; }
    public virtual AppUser AppUser { get; set; }
    public virtual AppUser AdminUser { get; set; }
    public string Message{get;set;}
    private DateTime _timestamp;

    public DateTime Timestamp
    {
        get { return _timestamp == DateTime.MinValue ? DateTime.UtcNow : _timestamp; }
        set { _timestamp = value; }
    }

    public virtual Company Company { get; set; }
// etc.
    }

В нашем слое Task мы реализовали абстрактную базу AuditEventTask:

internal abstract class AuditEventTask<TEntity>
{
    internal readonly AuditEvent AuditEvent;

    internal AuditEventTask()
    {
        AuditEvent = InitializeAuditEvent();
    }

    internal void Add(UnitOfWork unitOfWork)
    {
        if (unitOfWork == null)
        {
            throw new ArgumentNullException(Resources.UnitOfWorkRequired_Message);
        }
        new AuditEventRepository(unitOfWork).Add(AuditEvent);
    }

    private AuditEvent InitializeAuditEvent()
    {
        return new AuditEvent {Event = SetEvent(), Timestamp = DateTime.UtcNow};
    }

    internal abstract void Log(UnitOfWork unitOfWork, TEntity entity, string appUserName, string adminUserName);

    protected abstract string SetEvent();
}

Журнал должен быть реализован для записи данных, связанных с событием, а SetEvent реализован, чтобы заставить производную задачу неявно устанавливать тип своего события:

internal class EmailAuditEventTask : AuditEventTask<Email>
{
    internal override void Log(UnitOfWork unitOfWork, Email email, string appUserName, string adminUserName)
    {
        AppUser appUser = new AppUserRepository(unitOfWork).Find(au => au.Email.Equals(appUserName, StringComparison.OrdinalIgnoreCase));
        AuditEvent.AppUser = appUser;
        AuditEvent.Company = appUser.Company;
        AuditEvent.Message = email.EmailType;
        Add(unitOfWork);
    }

    protected override string SetEvent()
    {
        return AuditEvent.SendEmail;
    }
}

Загвоздка здесь заключается во внутренней базовой задаче — базовая задача МОЖЕТ быть общедоступной, чтобы ее могли использовать более поздние дополнения к пространству имен Task — но в целом я думаю, что это дает вам представление.

Когда дело доходит до реализации, другие наши задачи определяют, когда должно происходить ведение журнала, поэтому в вашем случае:

AuditEventTask task;
if (user.failLogic1) { task = new FailLogin1AuditEventTask(fail 1 params); }
if (user.failLogic2) { task = new FailLogin2AuditEventTask(fail 2 params); }
if (user.failLogic3) { task = new FailLogin3AuditEventTask(etc); }
if (user.failLogic4) { task = new FailLogin4AuditEventTask(etc); }

task.Log();
user.Save();
person The Evil Greebo    schedule 30.06.2011
comment
Я еще не совсем слежу за частями UnitOfWork, поэтому мне нужно уйти и немного почитать, но я думаю, что вижу, как это структурировано в вашем последнем блоке кода, и, возможно, как это сработает для меня, ура - person David A Gibson; 30.06.2011
comment
Наш UnitOfWork — это, по сути, просто наша транзакция Entity Framework — вам не нужно беспокоиться об этом для этой реализации. - person The Evil Greebo; 30.06.2011