Skip to content

Commit 0ae19fb

Browse files
committed
GH-353 - Improvements in internal event externalization APIs.
DelegatingEventExternalizer.externalize(…) flavors now return a CompletableFuture to allow the target APIs to use asynchronous message sending and to transparently return a result from those invocations.
1 parent e4190c4 commit 0ae19fb

File tree

6 files changed

+185
-15
lines changed

6 files changed

+185
-15
lines changed

spring-modulith-events/spring-modulith-events-amqp/src/main/java/org/springframework/modulith/events/amqp/RabbitEventExternalizerConfiguration.java

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ DelegatingEventExternalizer rabbitEventExternalizer(EventExternalizationConfigur
6262
var routing = BrokerRouting.of(target, context);
6363

6464
operations.convertAndSend(routing.getTarget(), routing.getKey(payload), payload);
65+
66+
return null;
6567
});
6668
}
6769
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.modulith.events;
17+
18+
import java.util.Objects;
19+
20+
import org.springframework.core.ResolvableType;
21+
import org.springframework.core.ResolvableTypeProvider;
22+
import org.springframework.lang.Nullable;
23+
import org.springframework.util.Assert;
24+
25+
/**
26+
* An infrastructure event signaling that an application event has been externalized with a particular, broker-specific
27+
* result.
28+
*
29+
* @author Oliver Drotbohm
30+
* @since 1.1
31+
*/
32+
public class EventExternalized<S, T> implements ResolvableTypeProvider {
33+
34+
private final S event;
35+
private final Object mapped;
36+
private final RoutingTarget target;
37+
private final @Nullable T brokerResult;
38+
private final ResolvableType type;
39+
40+
/**
41+
* Creates a new {@link EventExternalized} event for the given source event, its mapped derivative,
42+
* {@link RoutingTarget} and broker result.
43+
*
44+
* @param event must not be {@literal null}.
45+
* @param mapped must not be {@literal null}.
46+
* @param target must not be {@literal null}.
47+
* @param brokerResult can be {@literal null}
48+
*/
49+
public EventExternalized(S event, Object mapped, RoutingTarget target, @Nullable T brokerResult) {
50+
51+
Assert.notNull(event, "Source event must not be null!");
52+
Assert.notNull(mapped, "Mapped event must not be null!");
53+
Assert.notNull(target, "Routing target must not be null!");
54+
55+
this.event = event;
56+
this.mapped = mapped;
57+
this.target = target;
58+
this.brokerResult = brokerResult;
59+
60+
this.type = ResolvableType.forClassWithGenerics(EventExternalized.class, ResolvableType.forInstance(event),
61+
brokerResult == null ? ResolvableType.forClass(Object.class) : ResolvableType.forInstance(brokerResult));
62+
}
63+
64+
/**
65+
* Returns the source event.
66+
*
67+
* @return will never be {@literal null}.
68+
*/
69+
public S getEvent() {
70+
return event;
71+
}
72+
73+
/**
74+
* Returns the type of the source event.
75+
*
76+
* @return will never be {@literal null}.
77+
*/
78+
@SuppressWarnings("unchecked")
79+
public Class<S> getEventType() {
80+
return (Class<S>) type.getGeneric(0).resolve(Object.class);
81+
}
82+
83+
/**
84+
* Returns the mapped event.
85+
*
86+
* @return will never be {@literal null}.
87+
*/
88+
public Object getMapped() {
89+
return mapped;
90+
}
91+
92+
/**
93+
* Returns the routing target.
94+
*
95+
* @return will never be {@literal null}.
96+
*/
97+
public RoutingTarget getTarget() {
98+
return target;
99+
}
100+
101+
/**
102+
* Returns the broker result.
103+
*
104+
* @return can be {@literal null}.
105+
*/
106+
public T getBrokerResult() {
107+
return brokerResult;
108+
}
109+
110+
/*
111+
* (non-Javadoc)
112+
* @see org.springframework.core.ResolvableTypeProvider#getResolvableType()
113+
*/
114+
@Override
115+
public ResolvableType getResolvableType() {
116+
return type;
117+
}
118+
119+
/*
120+
* (non-Javadoc)
121+
* @see java.lang.Object#equals(java.lang.Object)
122+
*/
123+
@Override
124+
public boolean equals(Object obj) {
125+
126+
if (obj == this) {
127+
return true;
128+
}
129+
130+
if (!(obj instanceof EventExternalized that)) {
131+
return false;
132+
}
133+
134+
return Objects.equals(this.event, that.event)
135+
&& Objects.equals(this.mapped, that.mapped)
136+
&& Objects.equals(this.brokerResult, that.brokerResult);
137+
}
138+
139+
/*
140+
* (non-Javadoc)
141+
* @see java.lang.Object#hashCode()
142+
*/
143+
@Override
144+
public int hashCode() {
145+
return Objects.hash(this.event, this.mapped, this.brokerResult);
146+
}
147+
}

spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/DelegatingEventExternalizer.java

+21-9
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
*/
1616
package org.springframework.modulith.events.support;
1717

18-
import java.util.function.BiConsumer;
18+
import java.util.concurrent.CompletableFuture;
19+
import java.util.function.BiFunction;
1920

2021
import org.springframework.modulith.events.ApplicationModuleListener;
2122
import org.springframework.modulith.events.EventExternalizationConfiguration;
@@ -24,7 +25,7 @@
2425
import org.springframework.util.Assert;
2526

2627
/**
27-
* An {@link EventExternalizationSupport} delegating to a {@link BiConsumer} for the actual externalization. Note, that
28+
* An {@link EventExternalizationSupport} delegating to a {@link BiFunction} for the actual externalization. Note, that
2829
* this <em>needs</em> to be a {@link Component} to make sure it is considered an event listener, as without the
2930
* annotation Spring Framework would skip it as it lives in the {@code org.springframework} package.
3031
*
@@ -34,17 +35,17 @@
3435
@Component
3536
public class DelegatingEventExternalizer extends EventExternalizationSupport {
3637

37-
private final BiConsumer<RoutingTarget, Object> delegate;
38+
private final BiFunction<RoutingTarget, Object, CompletableFuture<?>> delegate;
3839

3940
/**
4041
* Creates a new {@link DelegatingEventExternalizer} for the given {@link EventExternalizationConfiguration} and
41-
* {@link BiConsumer} implementing the actual externalization.
42+
* {@link BiFunction} implementing the actual externalization.
4243
*
4344
* @param configuration must not be {@literal null}.
4445
* @param delegate must not be {@literal null}.
4546
*/
4647
public DelegatingEventExternalizer(EventExternalizationConfiguration configuration,
47-
BiConsumer<RoutingTarget, Object> delegate) {
48+
BiFunction<RoutingTarget, Object, CompletableFuture<?>> delegate) {
4849

4950
super(configuration);
5051

@@ -59,16 +60,27 @@ public DelegatingEventExternalizer(EventExternalizationConfiguration configurati
5960
*/
6061
@Override
6162
@ApplicationModuleListener
62-
public void externalize(Object event) {
63-
super.externalize(event);
63+
public CompletableFuture<?> externalize(Object event) {
64+
return super.externalize(event);
6465
}
6566

6667
/*
6768
* (non-Javadoc)
6869
* @see org.springframework.modulith.events.support.EventExternalizationSupport#externalize(org.springframework.modulith.events.RoutingTarget, java.lang.Object)
6970
*/
7071
@Override
71-
protected void externalize(Object payload, RoutingTarget target) {
72-
delegate.accept(target, payload);
72+
protected CompletableFuture<?> externalize(Object payload, RoutingTarget target) {
73+
74+
try {
75+
76+
var result = delegate.apply(target, payload);
77+
78+
return result != null
79+
? result
80+
: CompletableFuture.failedFuture(new IllegalStateException("Delegate must not return null!"));
81+
82+
} catch (RuntimeException o_O) {
83+
return CompletableFuture.failedFuture(o_O);
84+
}
7385
}
7486
}

spring-modulith-events/spring-modulith-events-core/src/main/java/org/springframework/modulith/events/support/EventExternalizationSupport.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
*/
1616
package org.springframework.modulith.events.support;
1717

18+
import java.util.concurrent.CompletableFuture;
19+
1820
import org.slf4j.Logger;
1921
import org.slf4j.LoggerFactory;
2022
import org.springframework.modulith.events.ApplicationModuleListener;
2123
import org.springframework.modulith.events.EventExternalizationConfiguration;
24+
import org.springframework.modulith.events.EventExternalized;
2225
import org.springframework.modulith.events.RoutingTarget;
2326
import org.springframework.modulith.events.core.ConditionalEventListener;
2427
import org.springframework.util.Assert;
@@ -61,14 +64,15 @@ public boolean supports(Object event) {
6164
* Externalizes the given event.
6265
*
6366
* @param event must not be {@literal null}.
67+
* @return the externalization result, will never be {@literal null}.
6468
*/
6569
@ApplicationModuleListener
66-
public void externalize(Object event) {
70+
public CompletableFuture<?> externalize(Object event) {
6771

6872
Assert.notNull(event, "Object must not be null!");
6973

7074
if (!configuration.supports(event)) {
71-
return;
75+
return CompletableFuture.completedFuture(null);
7276
}
7377

7478
var target = configuration.determineTarget(event);
@@ -80,14 +84,16 @@ public void externalize(Object event) {
8084
logger.debug("Externalizing event of type {} to {}.", event.getClass(), target);
8185
}
8286

83-
externalize(mapped, target);
87+
return externalize(mapped, target)
88+
.thenApply(it -> new EventExternalized<>(event, mapped, target, it));
8489
}
8590

8691
/**
8792
* Publish the given payload to the given {@link RoutingTarget}.
8893
*
8994
* @param payload must not be {@literal null}.
9095
* @param target must not be {@literal null}.
96+
* @return the externalization result, will never be {@literal null}.
9197
*/
92-
protected abstract void externalize(Object payload, RoutingTarget target);
98+
protected abstract CompletableFuture<?> externalize(Object payload, RoutingTarget target);
9399
}

spring-modulith-events/spring-modulith-events-jms/src/main/java/org/springframework/modulith/events/jms/JmsEventExternalizerConfiguration.java

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.modulith.events.jms;
1717

18+
import java.util.concurrent.CompletableFuture;
19+
1820
import org.slf4j.Logger;
1921
import org.slf4j.LoggerFactory;
2022
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -55,6 +57,8 @@ DelegatingEventExternalizer jmsEventExternalizer(EventExternalizationConfigurati
5557
var serialized = serializer.serialize(payload);
5658

5759
operations.send(target.getTarget(), session -> session.createTextMessage(serialized.toString()));
60+
61+
return CompletableFuture.completedFuture(null);
5862
});
5963
}
6064
}

spring-modulith-events/spring-modulith-events-kafka/src/main/java/org/springframework/modulith/events/kafka/KafkaEventExternalizerConfiguration.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ DelegatingEventExternalizer kafkaEventExternalizer(EventExternalizationConfigura
6161
return new DelegatingEventExternalizer(configuration, (target, payload) -> {
6262

6363
var routing = BrokerRouting.of(target, context);
64-
65-
operations.send(routing.getTarget(), routing.getKey(payload), payload);
64+
return operations.send(routing.getTarget(), routing.getKey(payload), payload);
6665
});
6766
}
6867
}

0 commit comments

Comments
 (0)