public DateTime GetDate()
{
ICache cache = ...; // получим экземпляр кэша
string key = "somekey";
object value;
// Попробуем достать данные из кэша
if (!cache.TryGet(key, out value))
{
// Данные в кэше отсутствуют,
// получим новые данные
value = DateTime.Now;
// и поместим их в кэш на 5 секунд
cache.Add(key, value, TimeSpan.FromSeconds(5));
}
return (DateTime)value;
}
Из примера видно, что сервис-метод GetDate перегружен логикой кэширования, и с появлением еще одного аналогичного сервис-метода, логика кэширования будет просто скопирована в него. Очевидно, что такой подход приведет к тому, что со временем такой код станет труднее поддерживать при изменении схемы кэширования, и естественно возникает вопрос об организации функционала сквозного кэширования. Организовать такую функциональность можно через AOP или в случае с WCF можно воспользоваться стандартным механизмом перехвата вызова через специальные интерфейсы (WCF Interceptor Interfaces), а точнее через интерфейс IOperationInvoker.
И, предже чем приступить к реализации наметим основные шаги:
- Для начала создадим простой WCF сервис
- Объявим интерфейс кэша ICache
- Реализуем свой перехватчик (IOperationInvoker)
- И закончим реализацией IOperationBehavior атрибута для нашего перехватчика
Шаг 1. Создадим новый WCF проект, назовем его MyService, с контрактом в виде интерфейса:
[ServiceContract]
public interface IMyService
{
[OperationContract]
DateTime GetDate();
}
Сервис метод GetDate() будет возвращать текущее время на сервере (этот метод и будем кэшировать).
public class MyService : IMyService
{
public DateTime GetDate()
{
return DateTime.Now;
}
}
Шаг 2. Наш WCF сервис будет работать с кэшем, но пока у нас нет конкретной реализации кэша, поэтому определим только интерфейс:
public interface ICache
{
bool TryGet(string key, out object value);
void Add(string key, object value, TimeSpan timeout);
}
Для простоты, в интерфейсе только два метода, для извлечения из кэша (TryGet) и помещение в кэш (Add).
Шаг 3. Теперь самое интересное. Когда мы делаем вызов любого сервис-метода из клиентского приложения, мы посылаем WCF сервису сообщение (SOAP), которое содержит: название метода, входные параметры для метода и прочее. Далее WCF сервис, получив запрос от клиента в виде сообщения (SOAP), наравляет его на обработку в свой Dispatcher - некий конвеер по обработке запроса, который можно разбить на два последовательных участка: DispatchRuntime и DispatchOperation. Нас будет интересовать последний, так как именно там (а более точнее в экземпляре объекта типа IOperationInvoker) происходит непосредственный вызов сервис метода с передачей ему уже десериализованных входных параметров и получение результатов выполнения, которые мы и собираемся кэшировать. Стандартный IOperationInvoker не умеет кэшировать, поэтому придется написать свой:
public class CacheOperationInvoker : IOperationInvoker
{
IOperationInvoker _invoker;
ICache _cache;
TimeSpan _timeout;
public CacheOperationInvoker(IOperationInvoker invoker, ICache cache, TimeSpan timeout)
{
_invoker = invoker; // стандартный OperationInvoker
_cache = cache; // наш объект кэша
_timeout = timeout; // как долго объект будет находиться в кэше
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
// Хэш ключ.
// Разумно вычислять его на основе
// входных параметров и названия кешируемого метода.
// Но мы для простоты обойдемся константой.
string key = "somekey";
Tuple<object, object[]> result;
object cacheItem;
// Попробуем достать данные из кэша
if (!_cache.TryGet(key, out cacheItem))
{
// Данные в кэше отсутствуют,
// получим новые данные, вызовем базовый Invoke
object returnValue = _invoker.Invoke(instance, inputs, out outputs);
// Подготовим новый элемент кэша (упакуем returnValue и outputs)
result = Tuple.Create<object, object[]>(returnValue, outputs);
// Выполним кэширование на указанный timeout
_cache.Add(key, result, _timeout);
}
else
{
// Данные нашлись в кэше
result = cacheItem as Tuple<object, object[]>;
}
outputs = result.Item2;
return result.Item1;
}
public object[] AllocateInputs()
{
// просто пробрасываем вызов
return _invoker.AllocateInputs();
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
// просто пробрасываем вызов
return _invoker.InvokeBegin(instance, inputs, callback, state);
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
// просто пробрасываем вызов
return _invoker.InvokeEnd(instance, out outputs, result);
}
public bool IsSynchronous
{
// просто пробрасываем вызов
get { return _invoker.IsSynchronous; }
}
}
Шаг 4. Вся основная работа уже сделана, осталось как то наделить наш севис метод данным поведением. Для этого надо реализовать атрибут, который к тому же еще и IOperationBehavior:
public class CacheBehaviorAttribute : Attribute, IOperationBehavior
{
TimeSpan _timeout;
public CacheBehaviorAttribute(int seconds)
{
_timeout = TimeSpan.FromSeconds(seconds);
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
// Стандартный OperationInvoker
IOperationInvoker invoker = dispatchOperation.Invoker;
// Наш кэш, разумно сделать его singleton
// и получать из DI/IoC-контейнера
ICache cache = new MyCache();
// Подменим стандартный OperationInvoker расширенным.
dispatchOperation.Invoker = new CacheOperationInvoker(invoker, cache, _timeout);
}
public void Validate(OperationDescription operationDescription)
{
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
}
и не забываем пометить этим атрибутом севис-метод в сервис-интерфейсе:
[ServiceContract]
public interface IMyService
{
[OperationContract]
[CacheBehavior(5)] // Кэшируем результат на 5 секунд
DateTime GetDate();
}
PS: Если Вы реализуете кэширование для WCF Web HTTP Services, то нужно использовать стандартный механизм кэширования ASP.net. Пример, приведенный в статье, демонстрирует общий подход к решению проблемы.
Ссылки:
Комментариев нет:
Отправить комментарий