2011-04-25 16 views
11

Empiezo a trabajar en la infraestructura de almacenamiento en caché de mi sitio ASP.NET MVC. El problema es que parece que no puede encontrar un lugar razonable para el almacenamiento en caché de datos (que no sea 'en todas partes')Patrón de repositorio de ASP.NET/Caché de la capa de servicio

En este momento mi arquitectura es similar a esto:

Controlador -> Servicio de Capa -> Repositorio. El repositorio utiliza Linq a SQL para el acceso a datos.

El repositorio expone métodos genéricos como Insertar, GetById y GetQueryable, que devuelve un IQueryable que la capa de servicio puede refinar aún más.

Me gusta la idea de poner el almacenamiento en caché en la capa de repositorio, ya que a la capa de servicio no debería importarle realmente de dónde provienen los datos. El problema es con la invalidación de caché. La capa de servicio tiene más información sobre cuándo los datos quedan obsoletos que el repositorio. Por ejemplo:

Supongamos que tenemos una tabla de Usuarios y una tabla de Pedidos (el ejemplo canónico). La capa de servicios ofrece métodos como getOrder (int id), lo que exigiría la capa de repositorio:

public Order GetOrder(int id) 
{ 
    using(var repo = _repoFactory.Create<Order>()) 
    { 
     return repo.GetById(id) 
    } 
} 

o

repo.GetQueryable(order => order.Id == id && order.HasShipped == false).Single(); 

Si caché en la capa de repositorio, parece que sería muy limitado en saber cuándo esa orden de datos ha cambiado. Supongamos que el usuario fue eliminado, haciendo que todas sus órdenes se eliminen con un CASCADE. La capa de servicio podría invalidar el caché de Pedidos, ya que sabía que el usuario acababa de eliminarlo. Sin embargo, el repositorio (ya que es una Unidad de trabajo), no lo sabría. (Ignore el hecho de que no deberíamos consultar pedidos para un usuario eliminado, ya que es solo un ejemplo).

Hay otras situaciones en las que creo que esto se muestra. Supongamos que queremos obtener todas las órdenes de los usuarios:

repo.GetQueryable(order => order.UserId == userId).ToList() 

El repositorio puede almacenar en caché los resultados de esta consulta, pero, si se añade otro orden, esta consulta ya no es válida. Sin embargo, solo la capa de servicio es consciente de esto.

También es posible que mi comprensión de la capa de repositorio sea incorrecta. En cierto modo, lo veo como una fachada alrededor de la fuente de datos (es decir, cambiando de L2SQL a EF a lo que sea, la capa de servicio no tiene conocimiento de la fuente subyacente).

+1

Parece una evaluación correcta del almacenamiento en caché y sus peligros. – gt124

+0

Ver esta respuesta: https://stackoverflow.com/a/7805942/13729 – ssmith

Respuesta

8

Realísticamente, necesitará otra capa; la capa de almacenamiento en caché de datos. Su capa de servicio lo utilizará cuando solicite datos. Tras dicha solicitud, decidirá si tiene los datos en caché o si necesita consultar el repositorio apropiado. Del mismo modo, su capa de servicio puede indicar a esta nueva capa de caché de datos de una invalidación (la eliminación de un usuario en particular, etc.).

Sin embargo, lo que esto puede significar para su arquitectura es que su capa de caché de datos implementará la misma interfaz que sus repositorios. Una implementación bastante simple almacenaría en caché los datos por tipo de entidad y clave. Sin embargo, si está utilizando un ORM más sofisticado detrás de las escenas (NHibernate, EF 4, etc.), debería tener el caché como una opción para usted.

+1

Gracias por la respuesta. Creo que este es el enfoque que voy a tomar. He leído muchos artículos que intentan guardar en caché en la capa de repositorio, pero creo que esta solución tiene más sentido. – mfanto

2

Puede poner un evento en los objetos devueltos por sus repositorios, y hacer que el repositorio suscriba la invalidación de caché a un controlador.

Por ejemplo,

public class SomethingRepository{ 
     public Something GetById(int id){ 
      var something = _table.Single(x=>x.id==id); 
      something.DataChanged += this.InvalidateCache; 
      return something; 
     } 

     public void InvalidateCache(object sender, EventArgs e){ 
      // invalidate your cache 
     } 
    } 

Y el objeto Something necesita tener un evento DataChanged y algunos método público para su capa de servicios para llamar a desencadenarla. Al igual que,

public class Something{ 
     private int _id; 
     public int Id{ 
     get { return _id; } 
     set { 
      if(_id != value) 
      { 
       _id = value; 
       OnDataChanged(); 
      } 
     } 
     } 
     public event EventHandler DataChanged; 
     public void OnDataChanged(){ 
      if(DataChanged!=null) 
       DataChanged(this, EventArgs.Empty); 
     } 
    } 

Por lo tanto, toda la capa de servicio necesita saber es que se está cambiando los datos, y el repositorio se encarga de la invalidación de caché.

También sugiero que tome el consejo de ventaur y ponga la lógica de invalidación de caché en un servicio aparte. No es necesario ir tan lejos como para crear una "capa de almacenamiento en caché de datos" por separado, pero la lógica sería más limpia si se mantiene en una clase diferente.

+0

Gracias por la respuesta. Creo que en este momento voy a seguir la ruta que vendeur (y tú) sugirieron, y una vez que puedo migrar a algo como EF, volver a evaluar las opciones de almacenamiento en caché. Me gusta la idea de suscribirte para cambiar las notificaciones. Parte de la aplicación se distribuye entre múltiples servicios, y esto podría ayudar a invalidar los datos obsoletos de inmediato. – mfanto

Cuestiones relacionadas