diff --git a/.git-authors b/.git-authors new file mode 100644 index 00000000..5b262302 --- /dev/null +++ b/.git-authors @@ -0,0 +1,5 @@ +authors: + db: Dmitry Belyaev; dbelyaev + bk: Björn Kieling; bkieling +email: + domain: vmware.com diff --git a/spring-modulith-events/pom.xml b/spring-modulith-events/pom.xml index 66719f03..8a4224e5 100644 --- a/spring-modulith-events/pom.xml +++ b/spring-modulith-events/pom.xml @@ -23,26 +23,6 @@ - - org.springframework - spring-context - - - - org.springframework - spring-tx - - - - org.springframework - spring-aop - - - - org.springframework - spring-jdbc - - org.junit.jupiter junit-jupiter-engine @@ -61,24 +41,6 @@ test - - org.hsqldb - hsqldb - test - - - - - org.slf4j - slf4j-api - - - - org.slf4j - jcl-over-slf4j - runtime - - ch.qos.logback logback-classic diff --git a/spring-modulith-events/spring-modulith-events-core/pom.xml b/spring-modulith-events/spring-modulith-events-core/pom.xml index b0b63e82..b9527391 100644 --- a/spring-modulith-events/spring-modulith-events-core/pom.xml +++ b/spring-modulith-events/spring-modulith-events-core/pom.xml @@ -16,5 +16,34 @@ org.springframework.modulith.events.core + - \ No newline at end of file + + org.springframework + spring-context + + + + org.springframework + spring-tx + + + + org.springframework + spring-aop + + + + + org.slf4j + slf4j-api + + + + org.slf4j + jcl-over-slf4j + runtime + + + + diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/CompletableEventPublication.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/CompletableEventPublication.java index 68906e05..0a79c584 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/CompletableEventPublication.java +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/CompletableEventPublication.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 the original author or authors. + * Copyright 2017-2022 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. @@ -24,7 +24,6 @@ import java.util.Optional; * @author Oliver Drotbohm */ public interface CompletableEventPublication extends EventPublication { - /** * Returns the completion date of the publication. * diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/DefaultEventPublication.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/DefaultEventPublication.java index f1e30d42..5d2d39a2 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/DefaultEventPublication.java +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/DefaultEventPublication.java @@ -1,11 +1,11 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2022 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 * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -27,7 +27,7 @@ import java.util.Optional; /** * Default {@link CompletableEventPublication} implementation. * - * @author Oliver Gierke + * @author Oliver Drotbohm */ @Getter @RequiredArgsConstructor(staticName = "of") @@ -41,10 +41,6 @@ class DefaultEventPublication implements CompletableEventPublication { private Optional completionDate = Optional.empty(); - /* - * (non-Javadoc) - * @see de.olivergierke.events.CompletableEventPublication#markCompleted() - */ @Override public CompletableEventPublication markCompleted() { diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/DefaultEventPublicationRegistry.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/DefaultEventPublicationRegistry.java new file mode 100644 index 00000000..70037326 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/DefaultEventPublicationRegistry.java @@ -0,0 +1,121 @@ +/* + * Copyright 2017-2022 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 java.util.List; +import java.util.stream.Stream; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationListener; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * A registry to capture event publications to {@link ApplicationListener}s. Allows to register those publications, mark + * them as completed and lookup incomplete publications. + * + * @author Oliver Drotbohm, Björn Kieling, Dmitry Belyaev + */ +@Slf4j +@RequiredArgsConstructor +public class DefaultEventPublicationRegistry implements DisposableBean, EventPublicationRegistry { + + private final @NonNull EventPublicationRepository events; + + /** + * Stores {@link EventPublication}s for the given event and {@link ApplicationListener}s. + * + * @param event must not be {@literal null}. + * @param listeners must not be {@literal null}. + */ + public void store(Object event, Stream listeners) { + + listeners.map(it -> map(event, it)) + .forEach(events::create); + } + + /** + * Returns all {@link EventPublication}s that have not been completed yet. + * + * @return will never be {@literal null}. + */ + public Iterable findIncompletePublications() { + return events.findByCompletionDateIsNull(); + } + + /** + * Marks the publication for the given event and {@link PublicationTargetIdentifier} as completed. + * + * @param event must not be {@literal null}. + * @param targetIdentifier must not be {@literal null}. + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void markCompleted(Object event, PublicationTargetIdentifier targetIdentifier) { + + Assert.notNull(event, "Domain event must not be null!"); + Assert.notNull(targetIdentifier, "Listener identifier must not be null!"); + + events.findByEventAndTargetIdentifier(event, targetIdentifier) // + .map(DefaultEventPublicationRegistry::LOGCompleted) // + .map(e -> CompletableEventPublication.of(e.getEvent(), e.getTargetIdentifier())) + .ifPresent(it -> events.updateCompletionDate(it.markCompleted())); + } + + @Override + public void destroy() { + + List publications = events.findByCompletionDateIsNull(); + + if (publications.isEmpty()) { + + LOG.info("No publications outstanding!"); + return; + } + + LOG.info("Shutting down with the following publications left unfinished:"); + + for (int i = 0; i < publications.size(); i++) { + + String prefix = (i + 1) == publications.size() ? "└─" : "├─"; + EventPublication it = publications.get(i); + + LOG.info("{} - {} - {}", prefix, it.getEvent().getClass().getName(), it.getTargetIdentifier().getValue()); + } + } + + private EventPublication map(Object event, PublicationTargetIdentifier targetIdentifier) { + + EventPublication result = CompletableEventPublication.of(event, targetIdentifier); + + LOG.debug("Registering publication of {} for {}.", // + result.getEvent().getClass().getName(), result.getTargetIdentifier().getValue()); + + return result; + } + + private static EventPublication LOGCompleted(EventPublication publication) { + + LOG.debug("Marking publication of event {} to listener {} completed.", // + publication.getEvent().getClass().getName(), publication.getTargetIdentifier().getValue()); + + return publication; + } +} diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventPublication.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventPublication.java index f5380681..5070c8fa 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventPublication.java +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventPublication.java @@ -24,11 +24,9 @@ import org.springframework.util.Assert; /** * An event publication. * - * @author Oliver Drotbohm - * @see CompletableEventPublication#of(Object, PublicationTargetIdentifier) + * @author Oliver Drotbohm, Björn Kieling, Dmitry Belyaev */ public interface EventPublication extends Comparable { - /** * Returns the event that is published. * @@ -78,10 +76,6 @@ public interface EventPublication extends Comparable { return this.getTargetIdentifier().equals(identifier); } - /* - * (non-Javadoc) - * @see java.lang.Comparable#compareTo(java.lang.Object) - */ @Override public default int compareTo(EventPublication that) { return this.getPublicationDate().compareTo(that.getPublicationDate()); diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventPublicationRegistry.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventPublicationRegistry.java index db2e1467..571b3b7b 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventPublicationRegistry.java +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventPublicationRegistry.java @@ -18,13 +18,12 @@ package org.springframework.modulith.events; import java.util.stream.Stream; import org.springframework.context.ApplicationListener; -import org.springframework.util.Assert; /** * A registry to capture event publications to {@link ApplicationListener}s. Allows to register those publications, mark * them as completed and lookup incomplete publications. * - * @author Oliver Drotbohm + * @author Oliver Drotbohm, Björn Kieling, Dmitry Belyaev */ public interface EventPublicationRegistry { @@ -36,30 +35,18 @@ public interface EventPublicationRegistry { */ void store(Object event, Stream listeners); - /** - * Marks the publication for the given event and {@link PublicationTargetIdentifier} as completed. - * - * @param event must not be {@literal null}. - * @param listener must not be {@literal null}. - */ - void markCompleted(Object event, PublicationTargetIdentifier listener); - - /** - * Marks the given {@link EventPublication} as completed. - * - * @param publication must not be {@literal null}. - */ - default void markCompleted(EventPublication publication) { - - Assert.notNull(publication, "Publication must not be null!"); - - markCompleted(publication.getEvent(), publication.getTargetIdentifier()); - } - /** * Returns all {@link EventPublication}s that have not been completed yet. * * @return will never be {@literal null}. */ Iterable findIncompletePublications(); + + /** + * Marks the publication for the given event and {@link PublicationTargetIdentifier} as completed. + * + * @param event must not be {@literal null}. + * @param targetIdentifier must not be {@literal null}. + */ + void markCompleted(Object event, PublicationTargetIdentifier targetIdentifier); } diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventPublicationRepository.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventPublicationRepository.java new file mode 100644 index 00000000..427eba73 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventPublicationRepository.java @@ -0,0 +1,30 @@ +package org.springframework.modulith.events; + +import java.util.List; +import java.util.Optional; + +/** + * Repository to store {@link EventPublication}s. + * + * @author Björn Kieling, Dmitry Belyaev + */ +public interface EventPublicationRepository { + + EventPublication create(EventPublication publication); + + EventPublication updateCompletionDate(CompletableEventPublication publication); + + /** + * Returns all {@link EventPublication} that have not been completed yet. + */ + List findByCompletionDateIsNull(); + + /** + * Return the {@link EventPublication} for the given serialized event and listener identifier. + * + * @param event must not be {@literal null}. + * @param targetIdentifier must not be {@literal null}. + * @return + */ + Optional findByEventAndTargetIdentifier(Object event, PublicationTargetIdentifier targetIdentifier); +} diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventSerializer.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventSerializer.java index 4b8f1dbe..91874ba5 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventSerializer.java +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/EventSerializer.java @@ -35,5 +35,5 @@ public interface EventSerializer { * @param type must not be {@literal null}. * @return */ - Object deserialize(Object serialized, Class type); + T deserialize(Object serialized, Class type); } diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/PublicationTargetIdentifier.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/PublicationTargetIdentifier.java index ed278647..f16b4158 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/PublicationTargetIdentifier.java +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/PublicationTargetIdentifier.java @@ -27,7 +27,7 @@ import lombok.Value; @RequiredArgsConstructor(staticName = "of") public class PublicationTargetIdentifier { - String value; + private String value; /* * (non-Javadoc) diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/config/EventPublicationConfiguration.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/config/EventPublicationConfiguration.java index 6d994a0a..0902721a 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/config/EventPublicationConfiguration.java +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/config/EventPublicationConfiguration.java @@ -19,26 +19,37 @@ import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.modulith.events.DefaultEventPublicationRegistry; import org.springframework.modulith.events.EventPublicationRegistry; +import org.springframework.modulith.events.EventPublicationRepository; import org.springframework.modulith.events.support.CompletionRegisteringBeanPostProcessor; -import org.springframework.modulith.events.support.MapEventPublicationRegistry; +import org.springframework.modulith.events.support.MapBackedEventPublicationRepository; import org.springframework.modulith.events.support.PersistentApplicationEventMulticaster; /** - * @author Oliver Drotbohm + * @author Oliver Drotbohm, Björn Kieling, Dmitry Belyaev */ @Configuration(proxyBeanMethods = false) class EventPublicationConfiguration { - @Bean - PersistentApplicationEventMulticaster applicationEventMulticaster(ObjectProvider registry) { + @Bean + EventPublicationRegistry eventPublicationRegistry( + ObjectProvider repositoryProvider) { - return new PersistentApplicationEventMulticaster( - () -> registry.getIfAvailable(() -> new MapEventPublicationRegistry())); - } + return new DefaultEventPublicationRegistry( + repositoryProvider.getIfAvailable(MapBackedEventPublicationRepository::new) + ); + } - @Bean - static CompletionRegisteringBeanPostProcessor bpp(ObjectFactory store) { - return new CompletionRegisteringBeanPostProcessor(() -> store.getObject()); - } + @Bean + PersistentApplicationEventMulticaster applicationEventMulticaster( + EventPublicationRegistry eventPublicationRegistry) { + + return new PersistentApplicationEventMulticaster(() -> eventPublicationRegistry); + } + + @Bean + static CompletionRegisteringBeanPostProcessor bpp(ObjectFactory store) { + return new CompletionRegisteringBeanPostProcessor(store::getObject); + } } diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/MapBackedEventPublicationRepository.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/MapBackedEventPublicationRepository.java new file mode 100644 index 00000000..f94ddd60 --- /dev/null +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/MapBackedEventPublicationRepository.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017-2022 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 + * + * http://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.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.modulith.events.CompletableEventPublication; +import org.springframework.modulith.events.EventPublication; +import org.springframework.modulith.events.EventPublicationRepository; +import org.springframework.modulith.events.PublicationTargetIdentifier; + +/** + * Map based {@link EventPublicationRepository}, for testing purposes only. + * + * @author Björn Kieling, Dmitry Belyaev + */ +public class MapBackedEventPublicationRepository implements EventPublicationRepository { + + private final List events = new ArrayList<>(); + + @Override + public EventPublication create(EventPublication publication) { + events.add(CompletableEventPublication.of(publication.getEvent(), publication.getTargetIdentifier())); + return publication; + } + + @Override + public EventPublication updateCompletionDate(CompletableEventPublication publication) { + findByEventAndTargetIdentifier(publication.getEvent(), publication.getTargetIdentifier()) + .ifPresent(eventPublication -> ((CompletableEventPublication) eventPublication).markCompleted()); + return publication; + } + + @Override + public List findByCompletionDateIsNull() { + return events.stream() + .filter(publication -> !publication.isPublicationCompleted()) + .collect(Collectors.toList()); + } + + @Override + public Optional findByEventAndTargetIdentifier(Object event, PublicationTargetIdentifier targetIdentifier) { + return events.stream() + .filter(publication -> + publication.equals(publication.getEvent()) && publication.getTargetIdentifier().equals(targetIdentifier)) + .map(EventPublication.class::cast) + .findAny(); + } +} diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/MapEventPublicationRegistry.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/MapEventPublicationRegistry.java deleted file mode 100644 index 0a901593..00000000 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/MapEventPublicationRegistry.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2017-2020 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 - * - * http://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.support; - -import lombok.Value; - -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.modulith.events.CompletableEventPublication; -import org.springframework.modulith.events.EventPublication; -import org.springframework.modulith.events.EventPublicationRegistry; -import org.springframework.modulith.events.PublicationTargetIdentifier; - -/** - * Map based {@link EventPublicationRegistry}, for testing purposes only. - * - * @author Oliver Drotbohm - */ -public class MapEventPublicationRegistry implements EventPublicationRegistry { - - private final Map events = new HashMap<>(); - - /* - * (non-Javadoc) - * @see org.springframework.events.EventPublicationRegistry#findIncompletePublications() - */ - @Override - public Iterable findIncompletePublications() { - - return events.entrySet().stream()// - .filter(it -> !it.getValue().isPublicationCompleted())// - .map(it -> it.getValue())// - .collect(Collectors.toList()); - } - - /* - * (non-Javadoc) - * @see org.springframework.events.EventPublicationRegistry#store(java.lang.Object, java.util.Collection) - */ - @Override - public void store(Object event, Stream identifiers) { - - identifiers.forEach(id -> { - events.computeIfAbsent(Key.of(event, id), it -> CompletableEventPublication.of(event, id)); - }); - } - - /* - * (non-Javadoc) - * @see org.springframework.events.EventPublicationRegistry#markCompleted(java.lang.Object, org.springframework.events.PublicationTargetIdentifier) - */ - @Override - public void markCompleted(Object event, PublicationTargetIdentifier id) { - events.computeIfPresent(Key.of(event, id), (__, value) -> value.markCompleted()); - } - - @Value(staticConstructor = "of") - private static class Key { - - Object event; - PublicationTargetIdentifier identifier; - } -} diff --git a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java index 07e7e5a0..e1d086f6 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java +++ b/spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/PersistentApplicationEventMulticaster.java @@ -123,8 +123,8 @@ public class PersistentApplicationEventMulticaster extends AbstractApplicationEv }); } - private ApplicationListener executeListenerWithCompletion(EventPublication publication, - TransactionalApplicationListener listener) { + private ApplicationListener executeListenerWithCompletion( + EventPublication publication, TransactionalApplicationListener listener) { listener.processEvent(publication.getApplicationEvent()); diff --git a/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/CompletableEventPublicationUnitTest.java b/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/CompletableEventPublicationTest.java similarity index 86% rename from spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/CompletableEventPublicationUnitTest.java rename to spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/CompletableEventPublicationTest.java index 53c34ec4..14dbbfcf 100644 --- a/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/CompletableEventPublicationUnitTest.java +++ b/spring-modulith-events/spring-modulith-events-core/src/test/java/org/springframework/modulith/events/CompletableEventPublicationTest.java @@ -5,7 +5,7 @@ * 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 + * http://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, @@ -15,14 +15,15 @@ */ package org.springframework.modulith.events; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import org.junit.jupiter.api.Test; /** - * @author Oliver Drotbohm + * @author Oliver Drotbohm, Björn Kieling, Dmitry Belyaev */ -class CompletableEventPublicationUnitTest { +class CompletableEventPublicationTest { @Test void rejectsNullEvent() { diff --git a/spring-modulith-events/spring-modulith-events-jackson/src/main/java/org/springframework/modulith/events/jackson/JacksonEventSerializer.java b/spring-modulith-events/spring-modulith-events-jackson/src/main/java/org/springframework/modulith/events/jackson/JacksonEventSerializer.java index 59f68459..738e9e64 100644 --- a/spring-modulith-events/spring-modulith-events-jackson/src/main/java/org/springframework/modulith/events/jackson/JacksonEventSerializer.java +++ b/spring-modulith-events/spring-modulith-events-jackson/src/main/java/org/springframework/modulith/events/jackson/JacksonEventSerializer.java @@ -52,7 +52,7 @@ class JacksonEventSerializer implements EventSerializer { * @see de.oliverDrotbohm.events.EventSerializer#deserialize(java.lang.Object, java.lang.Class) */ @Override - public Object deserialize(Object serialized, Class type) { + public T deserialize(Object serialized, Class type) { try { return mapper.get().readerFor(type).readValue(serialized.toString()); diff --git a/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublication.java b/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublication.java index 3c3e44b6..11d5f8df 100644 --- a/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublication.java +++ b/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublication.java @@ -15,25 +15,26 @@ */ package org.springframework.modulith.events.jpa; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; - import java.time.Instant; import java.util.UUID; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + /** - * @author Oliver Drotbohm + * @author Oliver Drotbohm, Dmitry Belyaev, Björn Kieling */ @Data @Entity @NoArgsConstructor(force = true) -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) class JpaEventPublication { private final @Id @Column(length = 16) UUID id; @@ -45,10 +46,10 @@ class JpaEventPublication { private Instant completionDate; @Builder - static JpaEventPublication of(Instant publicationDate, String listenerId, Object serializedEvent, - Class eventType) { - return new JpaEventPublication(UUID.randomUUID(), publicationDate, listenerId, serializedEvent.toString(), - eventType); + static JpaEventPublication of(UUID id, Instant publicationDate, String listenerId, Object serializedEvent, + Class eventType, Instant completionDate) { + return new JpaEventPublication(id, publicationDate, listenerId, serializedEvent.toString(), eventType, + completionDate); } JpaEventPublication markCompleted() { diff --git a/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationConfiguration.java b/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationConfiguration.java index 7fbc3fc0..363448b0 100644 --- a/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationConfiguration.java +++ b/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationConfiguration.java @@ -16,28 +16,25 @@ package org.springframework.modulith.events.jpa; import jakarta.persistence.EntityManager; -import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.modulith.events.EventSerializer; import org.springframework.modulith.events.config.EventPublicationConfigurationExtension; +import lombok.RequiredArgsConstructor; + /** - * @author Oliver Drotbohm + * @author Oliver Drotbohm, Dmitry Belyaev, Björn Kieling */ @Configuration(proxyBeanMethods = false) @RequiredArgsConstructor class JpaEventPublicationConfiguration implements EventPublicationConfigurationExtension { @Bean - public JpaEventPublicationRegistry jpaEventPublicationRegistry(JpaEventPublicationRepository repository, - EventSerializer serializer) { - return new JpaEventPublicationRegistry(repository, serializer); - } - - @Bean - public JpaEventPublicationRepository jpaEventPublicationRepository(EntityManager em) { - return new JpaEventPublicationRepository(em); + public JpaEventPublicationRepository jpaEventPublicationRepository(EntityManager em, EventSerializer serializer) { + // TODO Why do we want to instantiate the serializer here and what + // happens if no serializer is available? + return new JpaEventPublicationRepository(em, serializer); } } diff --git a/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationRegistry.java b/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationRegistry.java deleted file mode 100644 index cb504996..00000000 --- a/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationRegistry.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2017-2020 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 - * - * http://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.jpa; - -import lombok.EqualsAndHashCode; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import java.time.Instant; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.modulith.events.CompletableEventPublication; -import org.springframework.modulith.events.EventPublication; -import org.springframework.modulith.events.EventPublicationRegistry; -import org.springframework.modulith.events.EventSerializer; -import org.springframework.modulith.events.PublicationTargetIdentifier; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.Assert; - -/** - * JPA based {@link EventPublicationRegistry}. - * - * @author Oliver Gierke - */ -@Slf4j -@RequiredArgsConstructor -class JpaEventPublicationRegistry implements EventPublicationRegistry, DisposableBean { - - private final @NonNull JpaEventPublicationRepository events; - private final @NonNull EventSerializer serializer; - - /* - * (non-Javadoc) - * @see org.springframework.events.EventPublicationRegistry#store(java.lang.Object, java.util.Collection) - */ - @Override - public void store(Object event, Stream listeners) { - - listeners.map(it -> CompletableEventPublication.of(event, it)) // - .map(this::map) // - .forEach(it -> events.create(it)); - } - - /* - * (non-Javadoc) - * @see org.springframework.events.EventPublicationRegistry#findIncompletePublications() - */ - @Override - public Iterable findIncompletePublications() { - - List result = events.findByCompletionDateIsNull().stream() // - .map(it -> JpaEventPublicationAdapter.of(it, serializer)) // - .collect(Collectors.toList()); - - return result; - } - - /* - * (non-Javadoc) - * @see org.springframework.events.EventPublicationRegistry#markCompleted(java.lang.Object, org.springframework.events.ListenerId) - */ - @Override - @Transactional(propagation = Propagation.REQUIRES_NEW) - public void markCompleted(Object event, PublicationTargetIdentifier listener) { - - Assert.notNull(event, "Domain event must not be null!"); - Assert.notNull(listener, "Listener identifier must not be null!"); - - events.findBySerializedEventAndListenerId(serializer.serialize(event), listener.toString()) // - .map(JpaEventPublicationRegistry::LOGCompleted) // - .ifPresent(it -> events.update(it.markCompleted())); - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.DisposableBean#destroy() - */ - @Override - public void destroy() throws Exception { - - List publications = events.findByCompletionDateIsNull(); - - if (publications.isEmpty()) { - - LOG.info("No publications outstanding!"); - return; - } - - LOG.info("Shutting down with the following publications left unfinished:"); - - for (int i = 0; i < publications.size(); i++) { - - String prefix = (i + 1) == publications.size() ? "└─" : "├─"; - JpaEventPublication it = publications.get(i); - - LOG.info("{} {} - {} - {}", prefix, it.getId(), it.getEventType().getName(), it.getListenerId()); - } - } - - private JpaEventPublication map(EventPublication publication) { - - JpaEventPublication result = JpaEventPublication.builder() // - .eventType(publication.getEvent().getClass()) // - .publicationDate(publication.getPublicationDate()) // - .listenerId(publication.getTargetIdentifier().toString()) // - .serializedEvent(serializer.serialize(publication.getEvent()).toString()) // - .build(); - - LOG.debug("Registering publication of {} with id {} for {}.", // - result.getEventType(), result.getId(), result.getListenerId()); - - return result; - } - - private static JpaEventPublication LOGCompleted(JpaEventPublication publication) { - - LOG.debug("Marking publication of event {} with id {} to listener {} completed.", // - publication.getEventType(), publication.getId(), publication.getListenerId()); - - return publication; - } - - @EqualsAndHashCode - @RequiredArgsConstructor(staticName = "of") - static class JpaEventPublicationAdapter implements EventPublication { - - private final JpaEventPublication publication; - private final EventSerializer serializer; - - /* - * (non-Javadoc) - * @see org.springframework.events.EventPublication#getEvent() - */ - @Override - public Object getEvent() { - return serializer.deserialize(publication.getSerializedEvent(), publication.getEventType()); - } - - /* - * (non-Javadoc) - * @see org.springframework.events.EventPublication#getListenerId() - */ - @Override - public PublicationTargetIdentifier getTargetIdentifier() { - return PublicationTargetIdentifier.of(publication.getListenerId()); - } - - /* - * (non-Javadoc) - * @see org.springframework.events.EventPublication#getPublicationDate() - */ - @Override - public Instant getPublicationDate() { - return publication.getPublicationDate(); - } - } -} diff --git a/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationRepository.java b/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationRepository.java index 57155e4c..5a8b2f68 100644 --- a/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationRepository.java +++ b/spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationRepository.java @@ -15,72 +15,135 @@ */ package org.springframework.modulith.events.jpa; -import jakarta.persistence.EntityManager; -import jakarta.persistence.TypedQuery; -import lombok.RequiredArgsConstructor; - +import java.time.Instant; import java.util.List; import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; + +import org.springframework.modulith.events.CompletableEventPublication; +import org.springframework.modulith.events.EventPublication; +import org.springframework.modulith.events.EventPublicationRepository; +import org.springframework.modulith.events.EventSerializer; +import org.springframework.modulith.events.PublicationTargetIdentifier; import org.springframework.transaction.annotation.Transactional; +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; + /** - * Repository to store {@link JpaEventPublication}s. + * Repository to store {@link EventPublication}s. * - * @author Oliver Drotbohm + * @author Oliver Drotbohm, Dmitry Belyaev, Björn Kieling */ @RequiredArgsConstructor -public class JpaEventPublicationRepository { +public class JpaEventPublicationRepository implements EventPublicationRepository { private final EntityManager entityManager; + private final EventSerializer serializer; + @Override @Transactional - JpaEventPublication create(JpaEventPublication publication) { + public EventPublication create(EventPublication publication) { - entityManager.persist(publication); + entityManager.persist(domainToEntity(publication)); return publication; } + @Override @Transactional - JpaEventPublication update(JpaEventPublication publication) { - - entityManager.merge(publication); - entityManager.flush(); + public EventPublication updateCompletionDate(CompletableEventPublication publication) { + findEntityBySerializedEventAndListenerId(publication.getEvent(), + publication.getTargetIdentifier().getValue()).ifPresent(entity -> { + entity.setCompletionDate(publication.getCompletionDate().orElse(null)); + entityManager.flush(); + }); return publication; } - /** - * Returns all {@link JpaEventPublication} that have not been completed yet. - */ + @Override @Transactional(readOnly = true) - List findByCompletionDateIsNull() { + public List findByCompletionDateIsNull() { String query = "select p from JpaEventPublication p where p.completionDate is null"; - return entityManager.createQuery(query, JpaEventPublication.class).getResultList(); + return entityManager.createQuery(query, JpaEventPublication.class).getResultList().stream() + .map(this::entityToDomain).collect(Collectors.toList()); } - /** - * Return the {@link JpaEventPublication} for the given serialized event and listener identifier. - * - * @param event must not be {@literal null}. - * @param listenerId must not be {@literal null}. - * @return - */ + @Override @Transactional(readOnly = true) - Optional findBySerializedEventAndListenerId(Object event, String listenerId) { + public Optional findByEventAndTargetIdentifier(Object event, + PublicationTargetIdentifier targetIdentifier) { + Optional result = findEntityBySerializedEventAndListenerId(event, targetIdentifier.getValue()); + return result.map(this::entityToDomain); + } + + private Optional findEntityBySerializedEventAndListenerId(Object event, String listenerId) { String query = "select p from JpaEventPublication p where p.serializedEvent = ?1 and p.listenerId = ?2"; - + String serializedEvent = serializeEvent(event); TypedQuery typedQuery = entityManager.createQuery(query, JpaEventPublication.class) - .setParameter(1, event) - .setParameter(2, listenerId); + .setParameter(1, serializedEvent).setParameter(2, listenerId); + JpaEventPublication resultEntity = typedQuery.getSingleResult(); + return Optional.ofNullable(resultEntity); + } - try { - return Optional.of(typedQuery.getSingleResult()); - } catch (Exception o_O) { - return Optional.empty(); + private String serializeEvent(Object event) { + return serializer.serialize(event).toString(); + } + + private JpaEventPublication domainToEntity(EventPublication domain) { + String serializedEvent = serializeEvent(domain.getEvent()); + return JpaEventPublication.builder().id(UUID.randomUUID()).publicationDate(domain.getPublicationDate()) + .listenerId(domain.getTargetIdentifier().getValue()).serializedEvent(serializedEvent) + .eventType(domain.getEvent().getClass()).build(); + } + + private CompletableEventPublication entityToDomain(JpaEventPublication entity) { + return JpaEventPublicationAdapter.of(entity, serializer); + } + + @EqualsAndHashCode + @RequiredArgsConstructor(staticName = "of") + private static class JpaEventPublicationAdapter implements CompletableEventPublication { + + private final JpaEventPublication publication; + private final EventSerializer serializer; + + @Override + public Object getEvent() { + return serializer.deserialize(publication.getSerializedEvent(), publication.getEventType()); + } + + @Override + public PublicationTargetIdentifier getTargetIdentifier() { + return PublicationTargetIdentifier.of(publication.getListenerId()); + } + + @Override + public Instant getPublicationDate() { + return publication.getPublicationDate(); + } + + @Override + public Optional getCompletionDate() { + return Optional.ofNullable(publication.getCompletionDate()); + } + + @Override + public boolean isPublicationCompleted() { + return publication.getCompletionDate() != null; + } + + @Override + public CompletableEventPublication markCompleted() { + publication.markCompleted(); + return this; } } } diff --git a/spring-modulith-events/spring-modulith-events-jpa/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.factories b/spring-modulith-events/spring-modulith-events-jpa/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports similarity index 100% rename from spring-modulith-events/spring-modulith-events-jpa/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.factories rename to spring-modulith-events/spring-modulith-events-jpa/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/spring-modulith-events/spring-modulith-events-jpa/src/test/java/org/springframework/modulith/events/jpa/JpaEventPublicationConfigurationIntegrationTests.java b/spring-modulith-events/spring-modulith-events-jpa/src/test/java/org/springframework/modulith/events/jpa/JpaEventPublicationConfigurationIntegrationTests.java index 1243c195..9aa98e93 100644 --- a/spring-modulith-events/spring-modulith-events-jpa/src/test/java/org/springframework/modulith/events/jpa/JpaEventPublicationConfigurationIntegrationTests.java +++ b/spring-modulith-events/spring-modulith-events-jpa/src/test/java/org/springframework/modulith/events/jpa/JpaEventPublicationConfigurationIntegrationTests.java @@ -16,60 +16,36 @@ package org.springframework.modulith.events.jpa; import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; -import jakarta.persistence.EntityManager; -import jakarta.persistence.TypedQuery; +import example.ExampleApplication; import lombok.RequiredArgsConstructor; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.modulith.events.EventSerializer; +import org.springframework.modulith.events.EventPublicationRegistry; import org.springframework.test.context.TestConstructor; import org.springframework.test.context.TestConstructor.AutowireMode; -import org.springframework.test.context.junit.jupiter.SpringExtension; /** - * @author Oliver Drotbohm + * @author Oliver Drotbohm, Dmitry Belyaev, Björn Kieling */ -@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = ExampleApplication.class) @TestConstructor(autowireMode = AutowireMode.ALL) @RequiredArgsConstructor class JpaEventPublicationConfigurationIntegrationTests { private final ApplicationContext context; - @Configuration - @Import(JpaEventPublicationConfiguration.class) - static class TestConfig { - - @Bean - EventSerializer eventSerializer() { - return mock(EventSerializer.class); - } - - @Bean - EntityManager entityManager() { - - EntityManager em = mock(EntityManager.class); - - // Mock API for query executed at bootstrap time - TypedQuery query = mock(TypedQuery.class); - doReturn(query).when(em).createQuery(any(String.class), any()); - - return em; - } - } + @MockBean + private EventSerializer serializer; @Test void bootstrapsApplicationComponents() { - assertThat(context.getBean(JpaEventPublicationRegistry.class)).isNotNull(); + assertThat(context.getBean(EventPublicationRegistry.class)).isNotNull(); assertThat(context.getBean(JpaEventPublicationRepository.class)).isNotNull(); } } diff --git a/spring-modulith-events/spring-modulith-events-jpa/src/test/java/org/springframework/modulith/events/jpa/JpaEventPublicationRepositoryIntegrationTests.java b/spring-modulith-events/spring-modulith-events-jpa/src/test/java/org/springframework/modulith/events/jpa/JpaEventPublicationRepositoryIntegrationTests.java index 3beb69aa..f56d33fb 100644 --- a/spring-modulith-events/spring-modulith-events-jpa/src/test/java/org/springframework/modulith/events/jpa/JpaEventPublicationRepositoryIntegrationTests.java +++ b/spring-modulith-events/spring-modulith-events-jpa/src/test/java/org/springframework/modulith/events/jpa/JpaEventPublicationRepositoryIntegrationTests.java @@ -15,14 +15,14 @@ */ package org.springframework.modulith.events.jpa; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; -import lombok.RequiredArgsConstructor; - -import java.time.Instant; import javax.sql.DataSource; @@ -34,7 +34,10 @@ import org.springframework.context.annotation.Import; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.modulith.events.CompletableEventPublication; +import org.springframework.modulith.events.EventPublication; import org.springframework.modulith.events.EventSerializer; +import org.springframework.modulith.events.PublicationTargetIdentifier; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.SharedEntityManagerCreator; @@ -45,8 +48,10 @@ import org.springframework.test.context.TestConstructor.AutowireMode; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.transaction.annotation.Transactional; +import lombok.RequiredArgsConstructor; + /** - * @author Oliver Drotbohm + * @author Oliver Drotbohm, Dmitry Belyaev, Björn Kieling */ @ExtendWith(SpringExtension.class) @TestConstructor(autowireMode = AutowireMode.ALL) @@ -54,13 +59,17 @@ import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor class JpaEventPublicationRepositoryIntegrationTests { + private static final PublicationTargetIdentifier TARGET_IDENTIFIER = PublicationTargetIdentifier.of("listener"); + + private static final EventSerializer eventSerializer = mock(EventSerializer.class); + @Configuration @Import(JpaEventPublicationConfiguration.class) static class TestConfig { @Bean EventSerializer eventSerializer() { - return mock(EventSerializer.class); + return eventSerializer; } // Database @@ -102,17 +111,34 @@ class JpaEventPublicationRepositoryIntegrationTests { @Test void persistsJpaEventPublication() { - JpaEventPublication publication = JpaEventPublication.of(Instant.now(), "listener", "", Object.class); + TestEvent testEvent = new TestEvent("abc"); + String serializedEvent = "{\"eventId\":\"abc\"}"; + + when(eventSerializer.serialize(testEvent)).thenReturn(serializedEvent); + when(eventSerializer.deserialize(serializedEvent, TestEvent.class)).thenReturn(testEvent); + + CompletableEventPublication publication = CompletableEventPublication.of(testEvent, TARGET_IDENTIFIER); // Store publication repository.create(publication); - assertThat(repository.findByCompletionDateIsNull()).containsExactly(publication); - assertThat(repository.findBySerializedEventAndListenerId("", "listener")).isPresent(); + List eventPublications = repository.findByCompletionDateIsNull(); + assertThat(eventPublications).hasSize(1); + assertThat(eventPublications.get(0).getEvent()).isEqualTo(publication.getEvent()); + assertThat(eventPublications.get(0).getTargetIdentifier()).isEqualTo(publication.getTargetIdentifier()); + assertThat(repository.findByEventAndTargetIdentifier(testEvent, TARGET_IDENTIFIER)).isPresent(); // Complete publication - repository.update(publication.markCompleted()); + repository.updateCompletionDate(publication.markCompleted()); assertThat(repository.findByCompletionDateIsNull()).isEmpty(); } + + private static final class TestEvent { + private final String eventId; + + private TestEvent(String eventId) { + this.eventId = eventId; + } + } } diff --git a/spring-modulith-events/spring-modulith-events-tests/pom.xml b/spring-modulith-events/spring-modulith-events-tests/pom.xml index 693e39b0..3b39f393 100644 --- a/spring-modulith-events/spring-modulith-events-tests/pom.xml +++ b/spring-modulith-events/spring-modulith-events-tests/pom.xml @@ -44,6 +44,11 @@ spring-orm + + org.hsqldb + hsqldb + test + \ No newline at end of file