清洁体系结构和缓存失效

时间:2016-08-31 09:42:03

标签: java android clean-architecture

我有一个试图遵循清洁架构的应用程序,我需要做一些缓存失效,但我不知道应该在哪个层中完成。

为了这个例子,假设我有OrderInteractor有2个用例:getOrderHistory()sendOrder(Order)

第一个用例是使用OrderHistoryRepository,第二个用例是OrderSenderRepository。这些存储库是具有多个实现的接口(第一个实现MockOrderHistoryRepositoryInternetOrderHistoryRepository)。 OrderInteractor仅通过接口与这些存储库交互,以隐藏真实的实现。

Mock版本非常虚拟,但历史存储库的Internet版本会使缓存中的某些数据保持更好的状态。

现在,我想实现以下内容:当订单成功发送时,我想使历史记录的缓存无效,但我不知道应该在哪里执行实际的缓存失效。

我的第一个猜测是向invalidateCache()添加OrderHistoryRepository,并在交互器内sendOrder()方法的末尾使用此方法。在InternetOrderHistoryRepository中,我只需实现缓存失效,我会很好。但我将被迫在MockOrderHistoryRepository内部实际实现该方法,并向外部暴露了存储库执行某些缓存管理的事实。我认为OrderInteractor不应该知道这个缓存管理,因为它是Internet OrderHistoryRepository版本的实现细节。

我的第二个猜测是在InternetOrderSenderRepository知道订单已成功发送时执行缓存失效但它会强制此存储库知道InternetOrderHistoryRepository以便使用缓存密钥通过这个repo进行缓存管理。我不希望我的OrderSenderRepositoryOrderHistoryRepository有依赖关系。

最后,我的第三个猜测是,在存储库被模拟时使用某种CacheInvalidator(无论名称)接口,使用Dummy实现,Real实现{1}}正在使用Interactor存储库。此Internet将注入CacheInvalidator,所选实施将由构建存储库和Interactor的{​​{1}}提供。这意味着我将有Factory - 构建CacheInvalidatorMockedOrderHistoryRepositoryFactory - 以及MockedOrderHistoryRepository - 构建DummyCacheInvalidator和{{1} }}。但在这里,我不知道InternetOrderHistoryRepositoryFactory应该在InternetOrderHistoryRepository的末尾使用RealCacheInvalidator还是直接由CacheInvalidator使用{尽管我认为后者更好,因为交互者可能不应该知道在引擎盖下有一些缓存管理。

您最喜欢的构建方式是什么?

非常感谢你。 皮尔

1 个答案:

答案 0 :(得分:0)

您的第二个猜测是正确的,因为缓存是持久性机制的一个细节。例如。如果存储库将是基于文件的存储库,则可能不是问题。

交互器(用例)根本不应该了解缓存。这将使测试变得更容易,因为您不需要真正的缓存或模拟来进行测试。

  

我的第二个猜测是,当知道订单已成功发送时,将在InternetOrderSenderRepository内执行缓存失效,但是它将强制此存储库知道InternetOrderHistoryRepository以便获取此仓库用于缓存管理的缓存密钥

似乎您的缓存键是多个订单属性的组合,因此您需要将缓存键创建逻辑封装在某个地方以供重用。

在这种情况下,您有以下选择:

两个接口的一个实现

您可以创建一个实现InternetOrderSenderRepositoryInternetOrderHistoryRepository接口的类。在这种情况下,您可以将缓存密钥生成逻辑提取到私有方法中并重新使用。

使用实用程序类创建缓存密钥

简单地提取实用程序类中的缓存键创建逻辑,并在两个存储库中使用它。

创建缓存键类

缓存键只是一个任意对象,因为缓存必须仅检查键是否存在,这意味着使用每个对象都具有的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,还可以将接口隔离与上面的缓存键类结合使用。

如何申请

  • 您可以使用面向方面的编程和上述选项之一来实现缓存
  • 您可以为每个存储库创建一个包装器(或委托),以便在需要时将缓存和委托应用于实际存储库。这与面向方面的方法非常相似。您只需手动实现方面。