Skip to content

Commit cd0b5cf

Browse files
committed
GH-748 - Allow publication completion to delete database entries.
We now expose a spring.modulith.events.completion-mode property, defaulting the previous behavior to a value of UPDATE. The property can also be configured to DELETE, which will cause the persistence implementations to flip to removing the database entries for event publications instead of setting the completion date.
1 parent 65178e4 commit cd0b5cf

File tree

17 files changed

+572
-129
lines changed

17 files changed

+572
-129
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2024 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.support;
17+
18+
import org.springframework.core.env.Environment;
19+
import org.springframework.util.Assert;
20+
21+
/**
22+
* Different modes of event completion.
23+
*
24+
* @author Oliver Drotbohm
25+
* @since 1.3
26+
* @soundtrack Lettuce - Waffles (Unify)
27+
*/
28+
public enum CompletionMode {
29+
30+
/**
31+
* Completes an {@link org.springframework.modulith.events.EventPublication} by setting its completion date and
32+
* updating the database entry accordingly.
33+
*/
34+
UPDATE,
35+
36+
/**
37+
* Completes an {@link org.springframework.modulith.events.EventPublication} by removing the database entry.
38+
*/
39+
DELETE;
40+
41+
public static final String PROPERTY = "spring.modulith.events.completion-mode";
42+
43+
/**
44+
* Looks up the {@link CompletionMode} from the given environment or uses {@link #UPDATE} as default.
45+
*
46+
* @param environment must not be {@literal null}.
47+
* @return will never be {@literal null}.
48+
*/
49+
public static CompletionMode from(Environment environment) {
50+
51+
Assert.notNull(environment, "Environment must not be null!");
52+
53+
var result = environment.getProperty(PROPERTY, CompletionMode.class);
54+
55+
return result == null ? CompletionMode.UPDATE : result;
56+
}
57+
}

spring-modulith-events/spring-modulith-events-core/src/main/resources/META-INF/spring-configuration-metadata.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
"type": "java.lang.Boolean",
1818
"description": "Whether to enable event externalization.",
1919
"defaultValue": "true"
20+
},
21+
{
22+
"name": "spring.modulith.events.completion-mode",
23+
"type": "org.springframework.modulith.events.support.CompletionMode",
24+
"description": "How to complete event publications.",
25+
"defaultValue": "update"
2026
}
2127
]
2228
}

spring-modulith-events/spring-modulith-events-jdbc/src/main/java/org/springframework/modulith/events/jdbc/JdbcEventPublicationAutoConfiguration.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2525
import org.springframework.context.annotation.Bean;
2626
import org.springframework.context.annotation.Configuration;
27+
import org.springframework.core.env.Environment;
2728
import org.springframework.core.io.ResourceLoader;
2829
import org.springframework.jdbc.core.JdbcTemplate;
2930
import org.springframework.jdbc.support.JdbcUtils;
3031
import org.springframework.modulith.events.config.EventPublicationAutoConfiguration;
3132
import org.springframework.modulith.events.config.EventPublicationConfigurationExtension;
3233
import org.springframework.modulith.events.core.EventSerializer;
34+
import org.springframework.modulith.events.support.CompletionMode;
3335

3436
/**
3537
* @author Dmitry Belyaev
@@ -48,10 +50,16 @@ DatabaseType databaseType(DataSource dataSource) {
4850
}
4951

5052
@Bean
51-
JdbcEventPublicationRepository jdbcEventPublicationRepository(JdbcTemplate jdbcTemplate,
52-
EventSerializer serializer, DatabaseType databaseType, JdbcConfigurationProperties properties) {
53+
JdbcRepositorySettings jdbcEventPublicationRepositorySettings(DatabaseType databaseType,
54+
JdbcConfigurationProperties properties, Environment environment) {
55+
56+
return new JdbcRepositorySettings(databaseType, CompletionMode.from(environment), properties.getSchema());
57+
}
5358

54-
return new JdbcEventPublicationRepository(jdbcTemplate, serializer, databaseType, properties);
59+
@Bean
60+
JdbcEventPublicationRepository jdbcEventPublicationRepository(JdbcTemplate jdbcTemplate,
61+
EventSerializer serializer, JdbcRepositorySettings settings) {
62+
return new JdbcEventPublicationRepository(jdbcTemplate, serializer, settings);
5563
}
5664

5765
@Bean

spring-modulith-events/spring-modulith-events-jdbc/src/main/java/org/springframework/modulith/events/jdbc/JdbcEventPublicationRepository.java

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,20 @@ class JdbcEventPublicationRepository implements EventPublicationRepository, Bean
116116
ID IN
117117
""";
118118

119+
private static final String SQL_STATEMENT_DELETE_BY_EVENT_AND_LISTENER_ID = """
120+
DELETE FROM %s
121+
WHERE
122+
LISTENER_ID = ?
123+
AND SERIALIZED_EVENT = ?
124+
""";
125+
126+
private static final String SQL_STATEMENT_DELETE_BY_ID = """
127+
DELETE
128+
FROM %s
129+
WHERE
130+
ID = ?
131+
""";
132+
119133
private static final String SQL_STATEMENT_DELETE_UNCOMPLETED = """
120134
DELETE
121135
FROM %s
@@ -134,7 +148,8 @@ class JdbcEventPublicationRepository implements EventPublicationRepository, Bean
134148

135149
private final JdbcOperations operations;
136150
private final EventSerializer serializer;
137-
private final DatabaseType databaseType;
151+
private final JdbcRepositorySettings settings;
152+
138153
private ClassLoader classLoader;
139154

140155
private final String sqlStatementInsert,
@@ -145,6 +160,8 @@ class JdbcEventPublicationRepository implements EventPublicationRepository, Bean
145160
sqlStatementUpdateById,
146161
sqlStatementFindByEventAndListenerId,
147162
sqlStatementDelete,
163+
sqlStatementDeleteByEventAndListenerId,
164+
sqlStatementDeleteById,
148165
sqlStatementDeleteUncompleted,
149166
sqlStatementDeleteUncompletedBefore;
150167

@@ -154,22 +171,20 @@ class JdbcEventPublicationRepository implements EventPublicationRepository, Bean
154171
*
155172
* @param operations must not be {@literal null}.
156173
* @param serializer must not be {@literal null}.
157-
* @param databaseType must not be {@literal null}.
158-
* @param properties must not be {@literal null}.
174+
* @param settings must not be {@literal null}.
159175
*/
160176
public JdbcEventPublicationRepository(JdbcOperations operations, EventSerializer serializer,
161-
DatabaseType databaseType, JdbcConfigurationProperties properties) {
177+
JdbcRepositorySettings settings) {
162178

163179
Assert.notNull(operations, "JdbcOperations must not be null!");
164180
Assert.notNull(serializer, "EventSerializer must not be null!");
165-
Assert.notNull(databaseType, "DatabaseType must not be null!");
166-
Assert.notNull(properties, "JdbcConfigurationProperties must not be null!");
181+
Assert.notNull(settings, "DatabaseType must not be null!");
167182

168183
this.operations = operations;
169184
this.serializer = serializer;
170-
this.databaseType = databaseType;
185+
this.settings = settings;
171186

172-
var schema = properties.getSchema();
187+
var schema = settings.getSchema();
173188
var table = ObjectUtils.isEmpty(schema) ? "EVENT_PUBLICATION" : schema + ".EVENT_PUBLICATION";
174189

175190
this.sqlStatementInsert = SQL_STATEMENT_INSERT.formatted(table);
@@ -180,6 +195,8 @@ public JdbcEventPublicationRepository(JdbcOperations operations, EventSerializer
180195
this.sqlStatementUpdateById = SQL_STATEMENT_UPDATE_BY_ID.formatted(table);
181196
this.sqlStatementFindByEventAndListenerId = SQL_STATEMENT_FIND_BY_EVENT_AND_LISTENER_ID.formatted(table);
182197
this.sqlStatementDelete = SQL_STATEMENT_DELETE.formatted(table);
198+
this.sqlStatementDeleteByEventAndListenerId = SQL_STATEMENT_DELETE_BY_EVENT_AND_LISTENER_ID.formatted(table);
199+
this.sqlStatementDeleteById = SQL_STATEMENT_DELETE_BY_ID.formatted(table);
183200
this.sqlStatementDeleteUncompleted = SQL_STATEMENT_DELETE_UNCOMPLETED.formatted(table);
184201
this.sqlStatementDeleteUncompletedBefore = SQL_STATEMENT_DELETE_UNCOMPLETED_BEFORE.formatted(table);
185202
}
@@ -222,10 +239,20 @@ public TargetEventPublication create(TargetEventPublication publication) {
222239
@Transactional
223240
public void markCompleted(Object event, PublicationTargetIdentifier identifier, Instant completionDate) {
224241

225-
operations.update(sqlStatementUpdateByEventAndListenerId, //
226-
Timestamp.from(completionDate), //
227-
identifier.getValue(), //
228-
serializer.serialize(event));
242+
var targetIdentifier = identifier.getValue();
243+
var serializedEvent = serializer.serialize(event);
244+
245+
if (settings.isDeleteCompletion()) {
246+
247+
operations.update(sqlStatementDeleteByEventAndListenerId, targetIdentifier, serializedEvent);
248+
249+
} else {
250+
251+
operations.update(sqlStatementUpdateByEventAndListenerId, //
252+
Timestamp.from(completionDate), //
253+
targetIdentifier, //
254+
serializedEvent);
255+
}
229256
}
230257

231258
/*
@@ -235,7 +262,12 @@ public void markCompleted(Object event, PublicationTargetIdentifier identifier,
235262
@Override
236263
@Transactional
237264
public void markCompleted(UUID identifier, Instant completionDate) {
238-
operations.update(sqlStatementUpdateById, Timestamp.from(completionDate), uuidToDatabase(identifier));
265+
266+
if (settings.isDeleteCompletion()) {
267+
operations.update(sqlStatementDeleteById, uuidToDatabase(identifier));
268+
} else {
269+
operations.update(sqlStatementUpdateById, Timestamp.from(completionDate), uuidToDatabase(identifier));
270+
}
239271
}
240272

241273
/*
@@ -294,7 +326,7 @@ public List<TargetEventPublication> findIncompletePublicationsPublishedBefore(In
294326
@Override
295327
public void deletePublications(List<UUID> identifiers) {
296328

297-
var dbIdentifiers = identifiers.stream().map(databaseType::uuidToDatabase).toList();
329+
var dbIdentifiers = identifiers.stream().map(this::uuidToDatabase).toList();
298330

299331
batch(dbIdentifiers, DELETE_BATCH_SIZE)
300332
.forEach(it -> operations.update(sqlStatementDelete.concat(toParameterPlaceholders(it.length)), it));
@@ -376,11 +408,11 @@ private TargetEventPublication resultSetToPublication(ResultSet rs) throws SQLEx
376408
}
377409

378410
private Object uuidToDatabase(UUID id) {
379-
return databaseType.uuidToDatabase(id);
411+
return settings.getDatabaseType().uuidToDatabase(id);
380412
}
381413

382414
private UUID getUuidFromResultSet(ResultSet rs) throws SQLException {
383-
return databaseType.databaseToUUID(rs.getObject("ID"));
415+
return settings.getDatabaseType().databaseToUUID(rs.getObject("ID"));
384416
}
385417

386418
@Nullable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2024 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.jdbc;
17+
18+
import org.springframework.lang.Nullable;
19+
import org.springframework.modulith.events.support.CompletionMode;
20+
import org.springframework.util.Assert;
21+
22+
/**
23+
* Internal abstraction of customization options for {@link JdbcEventPublicationRepository}.
24+
*
25+
* @author Oliver Drotbohm
26+
* @since 1.3
27+
* @soundtrack Jeff Coffin - Bom Bom (Only the Horizon)
28+
*/
29+
public class JdbcRepositorySettings {
30+
31+
private final DatabaseType databaseType;
32+
private final String schema;
33+
private final CompletionMode completionMode;
34+
35+
/**
36+
* Creates a new {@link JdbcRepositorySettings} for the given {@link DatabaseType}, {@link CompletionMode} and schema
37+
*
38+
* @param databaseType must not be {@literal null}.
39+
* @param schema can be {@literal null}
40+
* @param completionMode must not be {@literal null}.
41+
*/
42+
JdbcRepositorySettings(DatabaseType databaseType, CompletionMode completionMode, @Nullable String schema) {
43+
44+
Assert.notNull(databaseType, "Database type must not be null!");
45+
Assert.notNull(completionMode, "Completion mode must not be null!");
46+
47+
this.databaseType = databaseType;
48+
this.schema = schema;
49+
this.completionMode = completionMode;
50+
}
51+
52+
/**
53+
* Returns the {@link DatabaseType}.
54+
*
55+
* @return will never be {@literal null}.
56+
*/
57+
public DatabaseType getDatabaseType() {
58+
return databaseType;
59+
}
60+
61+
/**
62+
* Return the schema to be used.
63+
*
64+
* @return can be {@literal null}.
65+
*/
66+
@Nullable
67+
public String getSchema() {
68+
return schema;
69+
}
70+
71+
/**
72+
* Returns whether we use the deleting completion mode.
73+
*/
74+
public boolean isDeleteCompletion() {
75+
return completionMode == CompletionMode.DELETE;
76+
}
77+
}

0 commit comments

Comments
 (0)