在我目前的一个应用程序中,我需要通过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访问远程服务。
答案 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)
总的来说,我喜欢马克的回答,但我会添加两个重要的注释。
您是否应该在数据库中缓存?对我来说,这是应用程序缓存的完美使用。我通常有一个缓存接口,它封装了HTTP应用程序缓存,并且具有易于使用的方法,可以自动检查密钥的存在,否则他们会调用您的方法并放入缓存中。此外,您可以对其进行过期,以便它在自动过期后自动过期一段时间。
var result = CacheManager.Add(()=> YourMethodCall(),“CachedStuffKey”,new Timespan(0,1,0));
我知道这看起来很复杂,但将其存储在缓存中会在性能方面更快,并且无需创建新表。