GH-206 - Event Publication Registry now uses custom Clock instance if configured.
We now consider a user defined Clock bean in the application context to obtain the Instant to use as publication date for event publications.
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.modulith.events;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -49,13 +50,28 @@ public interface CompletableEventPublication extends EventPublication {
|
||||
CompletableEventPublication markCompleted();
|
||||
|
||||
/**
|
||||
* Creates a {@link CompletableEventPublication} for the given event an listener identifier.
|
||||
* Creates a {@link CompletableEventPublication} for the given event an listener identifier using a default
|
||||
* {@link Instant}. Prefer using {@link #of(Object, PublicationTargetIdentifier, Instant)} with a dedicated
|
||||
* {@link Instant} obtained from a {@link Clock}.
|
||||
*
|
||||
* @param event must not be {@literal null}.
|
||||
* @param id must not be {@literal null}.
|
||||
* @return
|
||||
* @return will never be {@literal null}.
|
||||
* @see #of(Object, PublicationTargetIdentifier, Instant)
|
||||
*/
|
||||
static CompletableEventPublication of(Object event, PublicationTargetIdentifier id) {
|
||||
return new DefaultEventPublication(event, id);
|
||||
return new DefaultEventPublication(event, id, Instant.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CompletableEventPublication} for the given event an listener identifier and publication date.
|
||||
*
|
||||
* @param event must not be {@literal null}.
|
||||
* @param id must not be {@literal null}.
|
||||
* @param publicationDate must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
static CompletableEventPublication of(Object event, PublicationTargetIdentifier id, Instant publicationDate) {
|
||||
return new DefaultEventPublication(event, id, publicationDate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,15 +39,17 @@ class DefaultEventPublication implements CompletableEventPublication {
|
||||
*
|
||||
* @param event must not be {@literal null}.
|
||||
* @param targetIdentifier must not be {@literal null}.
|
||||
* @param publicationDate must not be {@literal null}.
|
||||
*/
|
||||
DefaultEventPublication(Object event, PublicationTargetIdentifier targetIdentifier) {
|
||||
DefaultEventPublication(Object event, PublicationTargetIdentifier targetIdentifier, Instant publicationDate) {
|
||||
|
||||
Assert.notNull(event, "Event must not be null!");
|
||||
Assert.notNull(targetIdentifier, "PublicationTargetIdentifier must not be null!");
|
||||
Assert.notNull(publicationDate, "Publication date must not be null!");
|
||||
|
||||
this.event = event;
|
||||
this.targetIdentifier = targetIdentifier;
|
||||
this.publicationDate = Instant.now();
|
||||
this.publicationDate = publicationDate;
|
||||
this.completionDate = Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.modulith.events;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
@@ -40,17 +41,21 @@ public class DefaultEventPublicationRegistry implements DisposableBean, EventPub
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultEventPublicationRegistry.class);
|
||||
|
||||
private final EventPublicationRepository events;
|
||||
private final Clock clock;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultEventPublicationRegistry} for the given {@link EventPublicationRepository}.
|
||||
*
|
||||
* @param events must not be {@literal null}.
|
||||
* @param clock must not be {@literal null}.
|
||||
*/
|
||||
public DefaultEventPublicationRegistry(EventPublicationRepository events) {
|
||||
public DefaultEventPublicationRegistry(EventPublicationRepository events, Clock clock) {
|
||||
|
||||
Assert.notNull(events, "EventPublicationRepository must not be null!");
|
||||
Assert.notNull(clock, "Clock must not be null!");
|
||||
|
||||
this.events = events;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -58,10 +63,11 @@ public class DefaultEventPublicationRegistry implements DisposableBean, EventPub
|
||||
* @see org.springframework.modulith.events.EventPublicationRegistry#store(java.lang.Object, java.util.stream.Stream)
|
||||
*/
|
||||
@Override
|
||||
public void store(Object event, Stream<PublicationTargetIdentifier> listeners) {
|
||||
public Collection<EventPublication> store(Object event, Stream<PublicationTargetIdentifier> listeners) {
|
||||
|
||||
listeners.map(it -> map(event, it))
|
||||
.forEach(events::create);
|
||||
return listeners.map(it -> map(event, it))
|
||||
.map(events::create)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -118,7 +124,7 @@ public class DefaultEventPublicationRegistry implements DisposableBean, EventPub
|
||||
|
||||
private EventPublication map(Object event, PublicationTargetIdentifier targetIdentifier) {
|
||||
|
||||
EventPublication result = CompletableEventPublication.of(event, targetIdentifier);
|
||||
var result = CompletableEventPublication.of(event, targetIdentifier, clock.instant());
|
||||
|
||||
LOGGER.debug("Registering publication of {} for {}.", //
|
||||
result.getEvent().getClass().getName(), result.getTargetIdentifier().getValue());
|
||||
|
||||
@@ -36,7 +36,7 @@ public interface EventPublicationRegistry {
|
||||
* @param event must not be {@literal null}.
|
||||
* @param listeners must not be {@literal null}.
|
||||
*/
|
||||
void store(Object event, Stream<PublicationTargetIdentifier> listeners);
|
||||
Collection<EventPublication> store(Object event, Stream<PublicationTargetIdentifier> listeners);
|
||||
|
||||
/**
|
||||
* Returns all {@link EventPublication}s that have not been completed yet.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.modulith.events.config;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -22,6 +23,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -55,8 +57,9 @@ class EventPublicationConfiguration {
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
EventPublicationRegistry eventPublicationRegistry(EventPublicationRepository repository) {
|
||||
return new DefaultEventPublicationRegistry(repository);
|
||||
EventPublicationRegistry eventPublicationRegistry(EventPublicationRepository repository,
|
||||
ObjectProvider<Clock> clock) {
|
||||
return new DefaultEventPublicationRegistry(repository, clock.getIfAvailable(() -> Clock.systemUTC()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.modulith.events;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.AdditionalAnswers.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultEventPublicationRegistry}.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DefaultEventPublicationRegistryUnitTests {
|
||||
|
||||
@Mock EventPublicationRepository repository;
|
||||
@Mock Clock clock;
|
||||
|
||||
@Test // GH-206
|
||||
void usesCustomClockIfConfigured() {
|
||||
|
||||
when(repository.create(any())).then(returnsFirstArg());
|
||||
|
||||
var now = Instant.now();
|
||||
var clock = Clock.fixed(now, ZoneId.systemDefault());
|
||||
|
||||
var registry = new DefaultEventPublicationRegistry(repository, clock);
|
||||
|
||||
var identifier = PublicationTargetIdentifier.of("id");
|
||||
var publications = registry.store(new Object(), Stream.of(identifier));
|
||||
|
||||
assertThat(publications).hasSize(1).element(0).satisfies(it -> {
|
||||
assertThat(it.getPublicationDate()).isEqualTo(now);
|
||||
assertThat(it.getTargetIdentifier()).isEqualTo(identifier);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,10 @@ package org.springframework.modulith.events.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -32,11 +35,13 @@ import org.springframework.boot.test.context.assertj.AssertableApplicationContex
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.context.annotation.AdviceMode;
|
||||
import org.springframework.modulith.events.EventPublicationRegistry;
|
||||
import org.springframework.modulith.events.EventPublicationRepository;
|
||||
import org.springframework.modulith.events.config.EventPublicationConfiguration.AsyncPropertiesDefaulter;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.ProxyAsyncConfiguration;
|
||||
import org.springframework.scheduling.aspectj.AspectJAsyncConfiguration;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link EventPublicationConfiguration}.
|
||||
@@ -105,6 +110,21 @@ class EventPublicationConfigurationIntegrationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test // GH-206
|
||||
void wiresCustomClockIntoEventPublicationRegistryIfConfigured() {
|
||||
|
||||
var clock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
|
||||
|
||||
basicSetup()
|
||||
.withBean(Clock.class, () -> clock)
|
||||
.run(context -> {
|
||||
|
||||
var registry = context.getBean(EventPublicationRegistry.class);
|
||||
|
||||
assertThat(ReflectionTestUtils.getField(registry, "clock")).isSameAs(clock);
|
||||
});
|
||||
}
|
||||
|
||||
private <T> ContextConsumer<AssertableApplicationContext> expect(Function<Shutdown, T> extractor,
|
||||
T expected) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user