АОП Разделение сквозных проблем
Я пытаюсь начать использовать преимущества Аспектно-ориентированного программирования для повторяющихся задач. Я не уверен, как идти о разделении проблем. Я использую C# и для AOP я использую Castle.DynamicProxy (используя функцию InterofptedBy от Autofac), но я надеюсь, что ответ на этот вопрос может быть достаточно общим советом, чтобы применить его и к другим решениям 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 должен обойти это.
Какие подходы люди используют, чтобы убедиться, что эти проблемы произойдут в правильном порядке? Должен ли я перехватывать цепочки перехватчиков, используя метод перехвата LogingInterceptor методом перехвата TimingInterceptor? Или я ставлю какой-то [InterceptOrder(1|2|3|...)]
атрибут на классах перехватчиков? Или я могу поставить что-то вроде [InterceptAfter(typeof(TimingInterceptor))]
на LoggingInterceptor?
И есть ли лучшая альтернатива использованию локальной переменной потока для отслеживания времени вызова? Да, я бы хотел, чтобы это было надежно. Я думаю, что сохранение этой переменной в стеке может быть предпочтительным, но я не уверен, как LoggingInterceptor может получить дескриптор TimingInterceptor без слишком большого количества связей (было бы неплохо иметь возможность переключать реализацию TimingInterceptor без перекомпиляции LoggingInterceptor),
1 ответ
Вы пробовали просто добавить оба Interceptors в ваш прокси и запустить ваш код? Насколько я понимаю, если прокси-сервер имеет несколько перехватчиков, вызов Proceed() в первом перехватчике в цепочке будет фактически вызывать следующий перехватчик, а не фактически выполнять вызов.
Это хорошо, чтобы попробовать отдельные проблемы в ваших приложениях. Но проблемы не могут зависеть от другого.
Это может привести к путанице в отношении терминологии, используемой в IOC/AOP и в области глобальной связи. Действительно, проблемы в AOP-парадигме означают независимый код / лечение.
В вашем случае я могу определить "проблему / аспект ведения журнала" и зависимость между измерением времени / калькулятором (Start+Stop) и регистратором.
Ваша инфраструктура AOP должна внедрять только аспект / рекомендацию ведения журнала, которые могут зависеть от работы регистратора + калькулятора времени (например, бизнес / домен / функциональный секундомер). В идеале, регистратор и калькулятор времени находятся за интерфейсами, а аспект ведения журнала должен использовать контейнер IOC для создания рекомендаций по ведению журнала с помощью введенного (пожалуйста, конструктора) регистратора и калькулятора времени.
Таким образом, рекомендации по ведению журнала, аспект ведения журнала, модуль регистрации и модуль расчета времени могут быть модульным тестированием и разрабатываться различными группами в разных проектах.
Использование threadlocal / threadstatic или callcontext часто является плохой идеей и может отражать проблему дизайна.
не: если вы используете threadstatic / threadlocal, позаботьтесь об утечке памяти / объекте длительного хранения, управлении пулом потоков, асинхронном вызове (Task), трудно обнаруживаемой ошибке из-за последовательных и случайных результатов стиля.