我有一个试图遵循清洁架构的应用程序,我需要做一些缓存失效,但我不知道应该在哪个层中完成。
为了这个例子,假设我有OrderInteractor
有2个用例:getOrderHistory()
和sendOrder(Order)
。
第一个用例是使用OrderHistoryRepository
,第二个用例是OrderSenderRepository
。这些存储库是具有多个实现的接口(第一个实现MockOrderHistoryRepository
和InternetOrderHistoryRepository
)。 OrderInteractor
仅通过接口与这些存储库交互,以隐藏真实的实现。
Mock
版本非常虚拟,但历史存储库的Internet
版本会使缓存中的某些数据保持更好的状态。
现在,我想实现以下内容:当订单成功发送时,我想使历史记录的缓存无效,但我不知道应该在哪里执行实际的缓存失效。
我的第一个猜测是向invalidateCache()
添加OrderHistoryRepository
,并在交互器内sendOrder()
方法的末尾使用此方法。在InternetOrderHistoryRepository
中,我只需实现缓存失效,我会很好。但我将被迫在MockOrderHistoryRepository
内部实际实现该方法,并向外部暴露了存储库执行某些缓存管理的事实。我认为OrderInteractor
不应该知道这个缓存管理,因为它是Internet
OrderHistoryRepository
版本的实现细节。
我的第二个猜测是在InternetOrderSenderRepository
知道订单已成功发送时执行缓存失效但它会强制此存储库知道InternetOrderHistoryRepository
以便使用缓存密钥通过这个repo进行缓存管理。我不希望我的OrderSenderRepository
与OrderHistoryRepository
有依赖关系。
最后,我的第三个猜测是,在存储库被模拟时使用某种CacheInvalidator
(无论名称)接口,使用Dummy
实现,Real
实现{1}}正在使用Interactor
存储库。此Internet
将注入CacheInvalidator
,所选实施将由构建存储库和Interactor
的{{1}}提供。这意味着我将有Factory
- 构建CacheInvalidator
和MockedOrderHistoryRepositoryFactory
- 以及MockedOrderHistoryRepository
- 构建DummyCacheInvalidator
和{{1} }}。但在这里,我不知道InternetOrderHistoryRepositoryFactory
应该在InternetOrderHistoryRepository
的末尾使用RealCacheInvalidator
还是直接由CacheInvalidator
使用{尽管我认为后者更好,因为交互者可能不应该知道在引擎盖下有一些缓存管理。
您最喜欢的构建方式是什么?
非常感谢你。 皮尔
答案 0 :(得分:0)
您的第二个猜测是正确的,因为缓存是持久性机制的一个细节。例如。如果存储库将是基于文件的存储库,则可能不是问题。
交互器(用例)根本不应该了解缓存。这将使测试变得更容易,因为您不需要真正的缓存或模拟来进行测试。
我的第二个猜测是,当知道订单已成功发送时,将在
InternetOrderSenderRepository
内执行缓存失效,但是它将强制此存储库知道InternetOrderHistoryRepository
以便获取此仓库用于缓存管理的缓存密钥。
似乎您的缓存键是多个订单属性的组合,因此您需要将缓存键创建逻辑封装在某个地方以供重用。
在这种情况下,您有以下选择:
两个接口的一个实现
您可以创建一个实现InternetOrderSenderRepository
和InternetOrderHistoryRepository
接口的类。在这种情况下,您可以将缓存密钥生成逻辑提取到私有方法中并重新使用。
使用实用程序类创建缓存密钥
简单地提取实用程序类中的缓存键创建逻辑,并在两个存储库中使用它。
创建缓存键类
缓存键只是一个任意对象,因为缓存必须仅检查键是否存在,这意味着使用每个对象都具有的equals
方法。但是,为了更安全地使用类型,大多数缓存将通用类型用作密钥,以便您可以定义一个。
因此,您可以将缓存键逻辑和验证放在自己的类中。这样的好处是您可以轻松测试该逻辑。
public class OrderCacheKey {
private Integer orderId;
private int version;
public OrderCacheKey(Integer orderId, int version) {
this.orderId = Objects.requireNonNull(orderId);
if (version < 0) {
throw new IllegalArgumentException("version must be a positive integer");
}
this.version = version;
}
public OrderCacheKey(Order order) {
this(order.getId(), order.getVersion());
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
OrderCacheKey other = (OrderCacheKey) obj;
if (!Objects.equals(orderId, other.orderId))
return false;
return Objects.equals(version, other.version);
}
public int hashCode() {
int result = 1;
result = 31 * result + Objects.hashCode(orderId);
result = 31 * result + Objects.hashCode(version);
return result;
}
}
您可以使用此类作为缓存的键类型:Cache<OrderCacheKey, Order>
。然后,您可以在两个存储库实现中使用OrderCacheKey
类。
引入订单缓存界面以隐藏缓存详细信息
您可以应用接口隔离原理,并将完整的缓存详细信息隐藏在简单的接口后面。这将使您的单元测试更加容易,因为您必须减少模拟量。
public interface OrderCache {
public void add(Order order);
public Order get(Integer orderId, int version);
public void remove(Order order);
public void removeByKey(Integer orderId, int version);
}
然后您可以在两个存储库实现中使用OrderCache
,还可以将接口隔离与上面的缓存键类结合使用。
如何申请