如果未在数据库中缓存,则从远程服务获取数据 - 需要建议

时间:2009-09-24 07:47:55

标签: c# asp.net-mvc architecture

在我目前的一个应用程序中,我需要通过Webservice / SOAP从远程服务(CRM)获取客户数据。但是我也希望将数据缓存在mysql数据库中,以便我下次不需要连接到webservice(数据不会经常更改,远程服务很慢并且有带宽限制 - 因此缓存可以/必须)。

我非常确定这项任务的技术部分,但我不太确定如何在我的网络应用程序中实现这种干净透明。

我所有的其他数据都来自一个mysql数据库,因此我使用NHibernate从数据库中查询返回列表或单个实体的存储库。

到目前为止我的想法:


1合一

使用CustomerRepository,它通过Id查找客户,如果成功,则返回它,否则调用webservice并将检索到的数据保存到数据库。

控制器看起来像这样:

class Controller
{
    private CustomerRepository Rep;

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

伪/简单代码中的存储库,如下所示:

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;
    }
}

虽然上面看起来有点简单,但我认为CustomerRepository类会变得非常庞大和丑陋。所以我根本不喜欢这种方法。

存储库应该只加载数据库中的数据,至少应该是我的应用程序中的“合同”。


2在控制器中分离并粘在一起

为存储库(db access)和webservice(远程访问)使用单独的类,让控制器完成工作:

控制器看起来像这样:

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);
    }
}

虽然这看起来好一点,但我仍然不喜欢将所有逻辑放在控制器中,因为如果服务不返回数据的错误处理被省略,可能会使事情更加混乱。

也许我可以创建Controller使用的另一个服务层,但代码将完全相同,但在另一个类中。

实际上我想让我的Controller使用一个接口/类,它封装了那些东西,但我不想要一个“做到这一切”的类:访问存储库,访问web服务,保存数据......我觉得有点不对劲......



到目前为止,所有的想法都可能会变得非常臃肿,包括缓存代码,错误处理等等。我想。

也许我可以使用AOP清理一些东西?

如何实现上述内容?

使用的技术框架:ASP.NET MVC,用于DI的Spring.NET,用作ORM的NHibernate,用作数据库的mysql,通过SOAP访问远程服务。

4 个答案:

答案 0 :(得分:5)

您可以使用Decorator模式干净地实现您的体系结构。

让我们假设您有一个名为ICustomerRepository的接口。

您首先基于Web服务实现ICustomerRepository(让我们称之为WsCustomerRepository),并且在此实现中根本不用担心数据库。

然后实现另一个与数据库对话的ICustomerRepository(MySqlRepository)。

最后,您创建了第三个ICustomerRepository(CachingRepository),它实现了您描述的缓存逻辑。它首先查询“本地”存储库,但如果它没有得到结果,它会查询“远程”存储库并在返回结果之前将结果插入“本地”存储库。

您的CachingRepository可能如下所示:

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...
}

正如您所看到的,CachingRepository甚至不需要了解具体的Web服务和数据库,但可以专注于其单一职责:缓存

这使您可以清楚地分离责任,就控制器而言,它们只与ICustomerRepository实例通信。

答案 1 :(得分:0)

在我看来,这是服务层的完美候选者。

您有两个数据源(DB Repo和Web Service),您需要按照建议封装控制器的数据源。

如果服务层有一个控制器可以调用的方法,如下所示:

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

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

然后服务层可以像这样处理业务逻辑/缓存(您可以注入存储库):

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;
    }
}

然后,您可以将存储库和Web服务逻辑保存在单独的类中,或者(更好)单独的程序集。

希望有所帮助!

答案 2 :(得分:0)

在这种情况下,我会重新考虑你的设计。我想考虑有一个单独的进程(也许是一个Windows服务)来从远程服务更新你的MySql数据库。

这样,客户端始终可以从本地数据库获取数据,而无需在很长时间内阻止。如果有必要,在从MySql DB中获取数据时仍然可以使用缓存。

我想这种方法是否可行取决于您拥有多少客户。几年前,当从不太特别可靠的Web服务获得汇率时,这种方法对我很有用。

答案 3 :(得分:0)

总的来说,我喜欢马克的回答,但我会添加两个重要的注释。

  1. 您是否应该在数据库中缓存?对我来说,这是应用程序缓存的完美使用。我通常有一个缓存接口,它封装了HTTP应用程序缓存,并且具有易于使用的方法,可以自动检查密钥的存在,否则他们会调用您的方法并放入缓存中。此外,您可以对其进行过期,以便它在自动过期后自动过期一段时间。

    var result = CacheManager.Add(()=> YourMethodCall(),“CachedStuffKey”,new Timespan(0,1,0));

  2. 我知道这看起来很复杂,但将其存储在缓存中会在性能方面更快,并且无需创建新表。

    1. 我同意这种类型的逻辑真的不属于控制器,你的控制器应该是非常愚蠢的,并且主要是你的UI和你的下层之间的粘合剂。虽然,这取决于您正在构建的应用程序类型。并非所有应用都需要3层超级结构。