2009-09-24 9 views
5

En una de mis aplicaciones actuales, necesito obtener datos del cliente de un servicio remoto (CRM) a través del servicio web/SOAP. Pero también quiero almacenar en caché los datos en la base de datos mysql, de modo que no necesito conectarme al servicio web para la próxima vez (los datos no cambian a menudo, el servicio remoto es lento y tiene un límite de ancho de banda, entonces el almacenamiento en caché es correcto/a debe)Obtención de datos del servicio remoto si no se almacena en la base de datos en caché - sugerencia necesaria

Estoy bastante seguro acerca de la parte técnica de esta tarea, pero no estoy tan seguro de cómo implementar esta aplicación limpia y transparente en mi aplicación web.

Todos mis otros datos provienen de una sola base de datos mysql, así que los repositorios que devuelven listas o entidades individuales son consultadas desde la base de datos con NHibernate.

Mis ideas hasta ahora:


1, todo en un

Utilice un CustomerRepository, lo que busca el cliente mediante la identificación, si es exitosa, después lo devuelve, de lo contrario llamar al servicio web y guardar los datos recuperados en la base de datos.

controlador se ve así:

class Controller 
{ 
    private CustomerRepository Rep; 

    public ActionResult SomeAction(int id) 
    { 
     return Json(Rep.GetCustomerById(id)); 
    } 
} 

Repositorio de seudo/código simple como esto:

class CustomerRepository 
{ 
    public Customer GetCustomerById(int id) 
    { 
     var cached = Database.FindByPK(id); 
     if(cached != null) return cached; 

     var webserviceData = Webservice.GetData(id); 
     var customer = ConvertDataToCustomer(webserviceData); 

     SaveCustomer(customer); 

     return customer; 
    } 
} 

Aunque lo anterior parece algo sencillo, creo que la clase CustomerRepository crecerá bastante grande y fea . Así que no me gusta ese enfoque en absoluto.

Un repositorio solo debe cargar datos de la base de datos, que debe ser su "contrato" en mi aplicación al menos.


2 sepereate y pegado juntos en el controlador

Use clases separadas para el repositorio (acceso db) y el servicio web (acceso remoto) y dejar que el controlador de hacer el trabajo:

Controlador se ve así:

class Controller 
{ 
    private CustomerRepository Rep; 
    private Webservice Service; 

    public ActionResult SomeAction(int id) 
    { 
     var customer = Rep.GetCustomerById(id); 
     if(customer != null) return Json(customer); 

     var remote = Service.GetCustomerById(id); 
     Rep.SaveCustomer(remote); 

     return Json(remote); 
    } 
} 

Aunque esto se ve un poco mejor, todavía no me gusta poner todo lo logi c en el controlador, porque se omite el manejo de errores si el servicio no devuelve datos y probablemente podría complicar un poco más las cosas.

Tal vez podría hacer otra capa de servicio utilizada por el Controlador, pero el código sería bastante similar, pero en otra clase.

En realidad, me gustaría que mi Controlador use una única Interfaz/Clase, que encapsula esas cosas, pero no quiero una clase que "lo haga todo": acceder al repositorio, acceder al servicio web, guardar los datos ... . se siente un poco mal a mí ...



Todas las ideas hasta ahora son/probablemente llegar a ser bastante hinchado con código de almacenamiento en caché, control de errores, etc. Creo.

Tal vez podría limpiar algunas cosas usando AOP?

¿Cómo sería implementar cosas como las de arriba?

Marcos técnicos utilizados: ASP.NET MVC, Spring.NET para DI, NHibernate como ORM, mysql como base de datos, se accede al servicio remoto a través de SOAP.

Respuesta

5

Puede implementar su arquitectura limpiamente con el patrón Decorator.

Imaginemos que tiene una interfaz llamada ICustomerRepository.

Primero implemente ICustomerRepository (llamémoslo WsCustomerRepository) basado en el servicio web, y no se preocupe por la base de datos en absoluto en esta implementación.

A continuación, implementa otro ICustomerRepository (MySqlRepository) que solo habla con su base de datos.

Finalmente, crea un tercer ICustomerRepository (CachingRepository) que implementa la lógica de almacenamiento en caché que describe. Comienza por consultar el Repositorio "local", pero si no obtiene un resultado de eso, consulta el Repositorio "remoto" e inserta el resultado en el Repositorio "local" antes de devolver el resultado.

Su CachingRepository podría tener este aspecto:

public class CachingRepository : ICustomerRepository 
{ 
    private readonly ICustomerRepository remoteRep; 
    private readonly ICustomerRepository localRep; 

    public CachingRepository(ICustomerRepository remoteRep, ICustomerRepository localRep) 
    { 
     this.remoteRep = remoteRep; 
     this.localRep = localRep; 
    } 

    // implement ICustomerRepository... 
} 

Como se puede ver, el CachingRepository ni siquiera tiene que saber sobre el servicio web y base de datos concreta, pero se puede concentrar en su Responsabilidad Individual: El almacenamiento en caché

Esto le otorga una clara separación de responsabilidades, y en lo que respecta a sus Controladores, solo hablan con una instancia de ICustomerRepository.

+1

¡Oh, me gusta tu respuesta incluso más que la mía! Bonito. –

+0

ahora que es de hecho un enfoque interesante – Max

+0

@Max: Decorator es un patrón que tiene un gran uso en DI, entre otras cosas en escenarios exactamente como este :) –

0

Este es un candidato perfecto para una capa de servicio, en mi opinión.

Tiene dos orígenes de datos (DB Repo y servicio web), y necesita encapsularlos para el controlador, como sugirió.

Si la capa de servicio tiene un método tal que su controlador puede llamar, de esta manera:

public class Controller 
{ 
    private CustomerService _customerService; // Perhaps injected by an IoC container? 

    public ActionResult SomeAction(int id) 
    { 
     return Json(_customerService.GetCustomerById(id)); 
    } 
} 

A continuación, la capa de servicio puede manejar la lógica de negocio/almacenamiento en caché como tal (se podía inyectar el repositorio):

public class CustomerService 
{ 
    private readonly ICustomerRepository _customerRepository; 

    public CustomerService(ICustomerRepository customerRepository) 
    { 
     _customerRepository = customerRepository; 
    } 

    public Customer GetCustomerById(int id) 
    { 
     var cached = _customerRepository.FindByPK(id); 
     if(cached != null) return cached; 

     var webserviceData = Webservice.GetData(id); // You could inject the web service as well... 
     var customer = ConvertDataToCustomer(webserviceData); 

     _customerRepository.SaveCustomer(customer); 

     return customer; 
    } 
} 

Luego, puede mantener el repositorio y la lógica del servicio web en clases separadas, o (mejor aún) en ensamblajes separados.

Espero que ayude!

+0

Pensé en ese enfoque, también, sin embargo: ¿cuál sería el beneficio de usar una capa de servicio en lugar de poner todo eso en el controlador/método de acción? Eso es de lo que no estoy tan seguro. – Max

+1

La ventaja es que mantiene la lógica empresarial fuera del controlador y le permite reutilizar el método de capa de servicio en otro controlador, si es necesario. –

0

Personalmente en este caso, me gustaría reconsiderar su diseño. Pensaría en tener un proceso separado (quizás un servicio de Windows) que actualice su base de datos MySql desde el servicio remoto.

De esta forma, los clientes siempre pueden obtener los datos de su DB local sin tener que bloquear durante un tiempo significativo. Si es necesario, puede seguir utilizando el almacenamiento en caché cuando saque los datos de MySql DB.

Supongo que si este enfoque es factible depende de cuántos clientes tenga. El enfoque funcionó bien para mí hace unos años cuando obtuve las tasas de cambio de un servicio web que no era particularmente confiable.

0

En general, me gusta la respuesta de Mark, pero agregaría dos notas importantes.

  1. En caso de que el almacenamiento en caché incluso ser esto en su base de datos? Para mí, este es el uso perfecto de la caché de aplicaciones. Normalmente tengo una interfaz de caché que encapsula la caché de la aplicación HTTP y tiene métodos fáciles de usar que comprueban automáticamente la existencia de una clave, de lo contrario llaman a su método y la guardan en caché. También puede poner una caducidad para que caduque automáticamente. una cierta cantidad de tiempo.

    var result = CacheManager.Add (() => YourMethodCall(), "CachedStuffKey", nuevo Timespan (0,1,0));

Sé que esto parece complicado, pero almacenarlo en caché será más rápido en términos de rendimiento y se deshace de tener que crear una nueva tabla.

  1. Estoy de acuerdo en que este tipo de lógica realmente no pertenece a un controlador, su controlador debería ser bastante estúpido y ser mayormente pegamento entre su UI y sus capas inferiores. Aunque esto depende de qué tipo de aplicación estés creando. No todas las aplicaciones necesitan ser super estructuras de tres niveles.
Cuestiones relacionadas