Как заставить реактор.cache.CacheMono не вызывать расчет дважды? - PullRequest
0 голосов
/ 04 марта 2019

Я пытаюсь реализовать механизм кэширования для своих настроек, и я не могу заставить его работать.

У меня есть реализация моего хранилища:

@AllArgsConstructor
public class CachedSettingsRepository<E extends SettingsEntity> implements SettingsRepository<E> {

    private final String key;
    private final SettingsRepository<E> settingsRepository;
    private final Cache<String, Signal<? extends E>> cache;

    @Trace
    @Override
    public Mono<E> get() {
        return CacheMono.lookup(cache.asMap(), key)
                        .onCacheMissResume(settingsRepository::get);

    }

    @Trace
    @Override
    public Mono<Void> delete() {
        return settingsRepository.delete()
                                 .doOnNext(empty -> cache.invalidate(key));
    }

    @Trace
    @Override
    public Mono<E> cas(Function<Mono<E>, Publisher<E>> function) {
        return settingsRepository.cas(function)
                                 .doOnNext(result -> cache.invalidate(key));
    }

}

I 'мы настроили его так:

@Configuration
public class CacheContext {

    @Bean
    @ConfigurationProperties("settings.cache")
    public CacheSettingsBean paymentCacheSettingsBean() {
        return new CacheSettingsBean();
    }

    @Bean
    public Cache<String, Signal<? extends PaymentSettingsEntity>> paymentSettingsCache() {
        return Caffeine.newBuilder()
                       .maximumSize(paymentCacheSettingsBean().getMaximumSize())
                       .expireAfterWrite(paymentCacheSettingsBean().getExpireAfterWrite())
                       .build();
    }

    @Bean
    public CachedSettingsRepository<PaymentSettingsEntity> cachedPaymentSettingsRepository(
            SettingsRepository<PaymentSettingsEntity> paymentSettingsRepository,
            Cache<String, Signal<? extends PaymentSettingsEntity>> paymentSettingsCache) {
        return new CachedSettingsRepository<>(paymentCacheSettingsBean().getKey(), paymentSettingsRepository, paymentSettingsCache);
    }

}

И я пытаюсь проверить это:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
        classes = [CacheContext, TestContext])
@ActiveProfiles("settings-cache")
@EnableConfigurationProperties
class CachedSettingsRepositoryTest extends Specification {

    @Autowired
    CachedSettingsRepository<PaymentSettingsEntity> cachedPaymentSettingsRepository
    @Autowired
    SettingsRepository<PaymentSettingsEntity> paymentSettingsRepository

    def "test getting from cache"() {
        given:
            def paymentSettings = paymentSettingsEntity()
        when:
            def result1 = cachedPaymentSettingsRepository.get().block()
            def result2 = cachedPaymentSettingsRepository.get().block()
        then:
            1 * paymentSettingsRepository.get() >> Mono.just(paymentSettings)
            0 * _

            result1 == paymentSettings
            result1 == result2
    }

    def paymentSettingsEntity() {
        PaymentSettingsEntity.of([PaymentRangeSetting.of(0.0, 100.0)])
    }

    @Configuration
    static class TestContext {
        private DetachedMockFactory factory = new DetachedMockFactory()

        @Bean
        SettingsRepository<PaymentSettingsEntity> paymentSettingsRepository() {
            factory.Mock(SettingsRepository)
        }
    }

}

Я ожидаю, что он вызовет мой paymentSettingsRepository с первой попытки, а затем вернет кэшированныйна втором, но тестирование не выполняется все время.

1 Ответ

0 голосов
/ 04 марта 2019

Причина такого поведения заключается в реализации CacheMono и специфике теста.Вот код CacheMono:

public static <KEY, VALUE> MonoCacheBuilderMapMiss<VALUE> lookup(Map<KEY, ? super Signal<? extends VALUE>> cacheMap, KEY key) {
    return otherSupplier -> Mono.defer(() ->
            Mono.justOrEmpty(cacheMap.get(key))
                .switchIfEmpty(otherSupplier.get().materialize()
                                .doOnNext(value -> cacheMap.put(key, value)))
                .dematerialize()
    );
}

Он использует отложенный вызов только перед получением из кэша, но otherSupplier.get () не был заблокирован, поэтому тест не пройден.

Полагаю, это будетхорошо бы поменять otherSupplier.get() на Mono.defer(() -> otherSupplier.get())

...