7

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

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

第一个用例使用 an OrderHistoryRepository,第二个用例使用OrderSenderRepository. 这些存储库是具有多种实现的接口(MockOrderHistoryRepository以及InternetOrderHistoryRepository第一个实现)。唯一通过接口与这些OrderInteractor存储库交互以隐藏真正的实现。

Mock版本非常虚拟,但Internet历史存储库的版本将一些数据保留在缓存中以更好地执行。

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

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

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

最后,我的第三个猜测是有某种CacheInvalidator(无论名称如何)接口,在模拟存储库时使用一个实现,在使用存储库时使用Dummy一个Real实现。这将被注入到,并且选定的实现将由正在构建存储库和. 这意味着我将有一个- 正在构建和- 并且- 正在构建和。但是在这里,我不知道这应该由末尾使用还是直接由InteractorInternetCacheInvalidatorInteractorFactoryCacheInvalidatorMockedOrderHistoryRepositoryFactoryMockedOrderHistoryRepositoryDummyCacheInvalidatorInternetOrderHistoryRepositoryFactoryInternetOrderHistoryRepositoryRealCacheInvalidatorCacheInvalidatorInteractorsendOrder()InternetOrderSenderRepository(尽管我认为后者更好,因为交互者可能不应该知道引擎盖下有一些缓存管理)。

您首选的架构方式是什么?

非常感谢。皮埃尔

4

1 回答 1

1

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

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

我的第二个猜测是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在两个存储库实现中使用 ,还可以将接口隔离与上面的缓存键类结合起来。

如何申请

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