AOP, разделяющий сквозные проблемы

#c# #castle-dynamicproxy #aop #cross-cutting-concerns

#c# #castle-dynamicproxy #aop #сквозные проблемы

Вопрос:

Я пытаюсь начать использовать преимущества аспектно-ориентированного программирования для повторяющихся задач. Я не уверен, как разделять проблемы. Я использую C #, а для AOP я использую Castle.DynamicProxy (с использованием функции Autofac InterceptedBy), но я надеюсь, что ответ на этот вопрос может быть достаточно общим советом, который можно применить и к другим решениям AOP (возможно, вы сможете убедить меня переключиться на другое решение AOP).

Например, у меня есть что-то вроде следующего перехватчика, который перехватывает все вызовы методов к классу. В настоящее время у него есть две проблемы: когда вызывается метод, (1) измерьте, сколько времени занял вызов, и (2) запишите имя метода до и после его вызова.

 public class TimeLoggingInterceptor : IInterceptor
{
    private ILog m_Log;
    public TimeLoggingInterceptor(ILog log)
    {
        m_Log = log;
    }
    public void Intercept(IInvocation invocation)
    {
        // Logging concerns
        string fullMethodName = invocation.TargetType.Name   "."   invocation.MethodInvocationTarget.Name;
        m_Log.Debug(fullMethodName   " started.");

        // Timing concerns
        DateTime beforeStamp = DateTime.UtcNow;

        // Call method
        invocation.Proceed();

        // Timing concerns
        DateTime afterStamp = DateTime.UtcNow;
        TimeSpan callTime = afterStamp - beforeStamp;

        // Logging concerns
        m_Log.Debug(fullMethodName   " finished. Took "   callTime.TotalMilliseconds   "ms.");
    }
}
  

У меня непреодолимое ощущение, что здесь проблемы с синхронизацией (измерение того, сколько времени занял вызов метода) должны быть отделены от проблем с протоколированием (запись в файл журнала), потому что…ну, это отдельные проблемы. Я подумываю о том, чтобы сделать что-то подобное, но я не уверен, как подойти к упорядочиванию или куда поместить переменную callTime:

 public class TimingInterceptor : IInterceptor
{
    private static ThreadLocal<TimeSpan> callTime = new ThreadLocal<TimeSpan>();
    public static TimeSpan CallTime
    {
        get
        {
            if (!callTime.IsValueCreated) throw new InvalidOperationException("callTime was never set");
            return callTime.Value;
        }
    }

    public void Intercept(IInvocation invocation)
    {
        // Timing concerns
        DateTime beforeStamp = DateTime.UtcNow;

        // Call method
        invocation.Proceed();

        // Timing concerns
        DateTime afterStamp = DateTime.UtcNow;
        callTime.Value = afterStamp - beforeStamp;
    }
}
public class LoggingInterceptor : IInterceptor
{
    private ILog m_Log;
    public LoggingInterceptor(ILog log)
    {
        m_Log = log;
    }

    public void Intercept(IInvocation invocation)
    {
        // Logging concerns
        string fullMethodName = invocation.TargetType.Name   "."   invocation.MethodInvocationTarget.Name;
        m_Log.Debug(fullMethodName   " started.");

        // Call method
        invocation.Proceed();

        // Logging concerns
        m_Log.Debug(fullMethodName   " finished. Took "   TimingInterceptor.CallTime.TotalMilliseconds   "ms.");
    }
}
  

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

Какие подходы используют люди, чтобы убедиться, что эти проблемы будут выполняться в правильном порядке? Должен ли я объединять перехватчики в цепочку, используя метод перехвата LoggingInterceptor TimingInterceptor для перехвата TimingInterceptor? Или мне поместить какой-то [InterceptOrder(1|2|3|...)] атрибут в классы перехватчика? Или я могу поместить что-то вроде [InterceptAfter(typeof(TimingInterceptor))] в LoggingInterceptor?

И есть ли лучшая альтернатива использованию локальной переменной потока для отслеживания времени вызова? Да, я бы хотел, чтобы это было потокобезопасно. Я думаю, что предпочтительнее было бы сохранить эту переменную в стеке, но я не уверен, как LoggingInterceptor мог бы получить дескриптор TimingInterceptor без введения слишком большой связи (было бы неплохо иметь возможность отключить реализацию TimingInterceptor без перекомпиляции LoggingInterceptor).

Комментарии:

1. Мой перехватчик ведения журнала добавляет продолжительность вызова к концу сообщения журнала

2. 1, В AspectJ вы можете указать порядковый номер для метода interceptor; и 2, в вашем случае лично я бы создал единый EnterLeaveInterceptor, который, в свою очередь, вызывает другой MethodService, и организовал функции ведения журнала методов и синхронизации в MethodService. Чтобы их было легко настроить, также избегайте слишком большого количества прокси-классов.

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

4. Леник: Это кажется вполне приемлемым и дешевым решением (создайте один перехватчик, попросите его вызвать серию методов вокруг точки перехвата). Однако я новичок в AOP и надеюсь начать с хороших практик. Я хотел бы иметь возможность добавлять и удалять перехватчики, не добавляя слишком много обязанностей к одному классу. Если есть простой, масштабируемый способ, которым я могу объединить перехватчики, я могу избежать переноса слишком большого количества обязанностей в один класс и, следовательно, упростить их.

Ответ №1:

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

Комментарии:

1. Это то, чего я ожидаю от него. Но мне нужно учитывать порядок вызовов, поскольку один должен выполняться раньше другого.

2. И, для протокола, да, код работает. Но TimingInterceptor передает информацию о времени обратно в LoggingInterceptor через статическую локальную переменную потока , которая просто ужасно пахнет.

Ответ №2:

Полезно попробовать отдельные проблемы в ваших приложениях. Но проблемы не могут зависеть от других.

Это может привести к путанице в терминологии, используемой в IOC / AOP и глобальной области сопряжения. Действительно, проблемы в парадигме AOP означают независимый код / обработку.

В вашем случае я могу определить «проблему / аспект ведения журнала» и зависимость между измерением времени / калькулятором (старт Стоп) и регистратором.

Для работы в вашей платформе AOP должен быть внедрен только аспект ведения журнала / рекомендации, которые могут зависеть от logger time calculator (например, бизнес / домен / функциональный Stopwath). В идеале, logger и time calculator находятся за интерфейсами, и аспект ведения журнала должен использовать контейнер IOC для создания рекомендаций по ведению журнала с помощью встроенных (пожалуйста, конструктором) logger и time calculator.

Таким образом, рекомендации по ведению журнала, аспект ведения журнала, модуль logger и модуль time calculator могут быть модульным тестированием и разрабатываться разными командами в разных проектах.

Использование threadlocal / threadstatic или callcontext часто является плохой идеей и может отражать проблему дизайна.

нет: если вы используете threadstatic / threadlocal, позаботьтесь об объекте memoryleak / long retention, управлении пулом потоков, асинхронном вызове (задаче), ошибке, которую трудно обнаружить из-за последовательных результатов в случайном стиле.