Skip to content

Commit 57f4018

Browse files
authored
TRUNK-6516: Provide CDC mechanism with Debezium (#6151)
1 parent 329a0da commit 57f4018

17 files changed

Lines changed: 417 additions & 157 deletions

api/src/main/java/org/openmrs/api/OpenmrsPropertyConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
1717
import org.springframework.core.io.ClassPathResource;
1818
import org.springframework.core.io.FileSystemResource;
19+
1920
import java.util.Properties;
2021

2122
/**

api/src/main/java/org/openmrs/event/AggregatedEntityEvent.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010
package org.openmrs.event;
1111

12-
import java.util.HashSet;
12+
import java.util.ArrayList;
1313
import java.util.List;
1414
import java.util.Set;
1515

@@ -18,21 +18,23 @@
1818
* <p>
1919
* It needs to be used together with {@link TransactionalEventAggregator}.
2020
*
21-
* @since 2.9.x
21+
* @since 2.9.0
2222
*/
23-
public abstract class AggregatedEntityEvent extends BaseEvent {
23+
public abstract class AggregatedEntityEvent extends BaseSessionEvent {
24+
private static final long serialVersionUID = 1L;
2425

2526
private List<EntityEvent<?>> events;
2627

2728
public AggregatedEntityEvent() {
29+
this.events = new ArrayList<>();
2830
}
2931

3032
public AggregatedEntityEvent(List<EntityEvent<?>> events) {
31-
this(events, new HashSet<>());
33+
this.events = events;
3234
}
3335

3436
public AggregatedEntityEvent(List<EntityEvent<?>> events, Set<String> tags) {
35-
super(events, tags);
37+
super(tags);
3638
this.events = events;
3739
}
3840

@@ -42,6 +44,5 @@ public List<EntityEvent<?>> getEvents() {
4244

4345
public void setEvents(List<EntityEvent<?>> events) {
4446
this.events = events;
45-
this.source = events;
4647
}
4748
}

api/src/main/java/org/openmrs/event/BaseEvent.java

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
package org.openmrs.event;
1111

1212
import org.openmrs.event.outbox.OutboxableEvent;
13-
import org.springframework.context.ApplicationEvent;
1413

1514
import java.util.HashSet;
1615
import java.util.Set;
@@ -20,36 +19,22 @@
2019
* <p>
2120
* Extend {@link EntityEvent} or {@link AggregatedEntityEvent} instead.
2221
*
23-
* @since 2.9.x
22+
* @since 2.9.0
2423
*/
25-
class BaseEvent extends ApplicationEvent implements OutboxableEvent {
26-
24+
class BaseEvent implements OutboxableEvent {
25+
private static final long serialVersionUID = 1L;
26+
2727
protected final Set<String> tags;
28-
private String sessionId;
2928

3029
public BaseEvent() {
31-
this(""); //empty for deserialization to work
32-
}
33-
34-
public BaseEvent(Object source) {
35-
this(source, new HashSet<>());
30+
this.tags = new HashSet<>();
3631
}
3732

38-
public BaseEvent(Object source, Set<String> tags) {
39-
super(source);
33+
public BaseEvent(Set<String> tags) {
4034
this.tags = tags;
4135
}
42-
43-
36+
4437
public Set<String> getTags() {
4538
return tags;
4639
}
47-
48-
public String getSessionId() {
49-
return sessionId;
50-
}
51-
52-
public void setSessionId(String sessionId) {
53-
this.sessionId = sessionId;
54-
}
5540
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* This Source Code Form is subject to the terms of the Mozilla Public License,
3+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
4+
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5+
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6+
*
7+
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8+
* graphic logo is a trademark of OpenMRS Inc.
9+
*/
10+
package org.openmrs.event;
11+
12+
import java.util.Set;
13+
14+
/**
15+
* See {@link EntityEvent} or {@link AggregatedEntityEvent}.
16+
*
17+
* @since 2.9.0
18+
*/
19+
public abstract class BaseSessionEvent extends BaseEvent {
20+
private static final long serialVersionUID = 1L;
21+
22+
protected String sessionId;
23+
24+
/**
25+
* Default constructor for deserialization.
26+
*/
27+
public BaseSessionEvent() {
28+
}
29+
30+
public BaseSessionEvent(Set<String> tags) {
31+
super(tags);
32+
}
33+
34+
public BaseSessionEvent(String sessionId, Set<String> tags) {
35+
super(tags);
36+
this.sessionId = sessionId;
37+
}
38+
39+
public String getSessionId() {
40+
return sessionId;
41+
}
42+
43+
public void setSessionId(String sessionId) {
44+
this.sessionId = sessionId;
45+
}
46+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/**
2+
* This Source Code Form is subject to the terms of the Mozilla Public License,
3+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
4+
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5+
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6+
*
7+
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8+
* graphic logo is a trademark of OpenMRS Inc.
9+
*/
10+
package org.openmrs.event;
11+
12+
import org.codehaus.jackson.annotate.JsonIgnore;
13+
import org.jspecify.annotations.Nullable;
14+
import org.springframework.core.ResolvableType;
15+
import org.springframework.core.ResolvableTypeProvider;
16+
17+
import java.util.Map;
18+
import java.util.Set;
19+
20+
/**
21+
* Issued by the CDC engine (if available, see e.g. https://github.com/openmrs/openmrs-module-debezium)
22+
* <p>
23+
* Please note that CDC events are usually not happening inside an application transaction rather
24+
* they are fetched from DB by a separate process e.g. Debezium. For this reason it does not make sense
25+
* to use {@link org.springframework.transaction.event.TransactionalEventListener} to process them.
26+
* <p>
27+
* If you want to guarantee delivery, please use {@link org.springframework.context.event.EventListener}
28+
* so that an event is processed in the same thread as the publisher and if the processing fails, it will be
29+
* re-tried by the publisher. It is recommended only for fast enough listeners so that publisher is not
30+
* blocked for too long, and it keeps up with the events queue.
31+
* <p>
32+
* If you combine {@link org.springframework.context.event.EventListener} with {@link org.springframework.scheduling.annotation.Async}
33+
* you may run longer processing, but you lose the ability to re-try if anything fails, and in worst case you may lose the event.
34+
* <p>
35+
* For longer running processing with guaranteed delivery you may use {@link org.openmrs.event.outbox.OutboxEventListener}
36+
* to persist events to the database and have them processed asynchronously or publish events with
37+
* {@link org.springframework.context.event.EventListener} to a message broker for asynchronous processing.
38+
* The latter is more performant, and it can also be used to exchange messages with external systems.
39+
*
40+
* @param <T> entityType associated with this event
41+
*
42+
* @since 2.9.0
43+
*/
44+
public class CDCEvent<T> extends BaseEvent implements ResolvableTypeProvider {
45+
private static final long serialVersionUID = 1L;
46+
47+
private String snapshot;
48+
49+
private Class<T> entityType;
50+
51+
private String transactionId;
52+
53+
private Operation operation;
54+
55+
private String tableName;
56+
57+
private Map<String, Object> primaryKey;
58+
59+
private Map<String, Object> newState;
60+
61+
private Map<String, Object> previousState;
62+
63+
/**
64+
* Default constructor for deserialization.
65+
*/
66+
public CDCEvent() {
67+
}
68+
69+
public CDCEvent(Class<T> entityType) {
70+
this.entityType = entityType;
71+
}
72+
73+
public CDCEvent(Class<T> entityType, Set<String> tags) {
74+
super(tags);
75+
this.entityType = entityType;
76+
}
77+
78+
public void setPrimaryKey(Map<String, Object> primaryKey) {
79+
this.primaryKey = primaryKey;
80+
}
81+
82+
public Map<String, Object> getPrimaryKey() {
83+
return primaryKey;
84+
}
85+
86+
public boolean isCompositePrimaryKey() {
87+
return primaryKey != null && primaryKey.size() > 1;
88+
}
89+
90+
public void setEntityType(Class<T> entityType) {
91+
this.entityType = entityType;
92+
}
93+
94+
public Class<T> getEntityType() {
95+
return entityType;
96+
}
97+
98+
public void setTransactionId(String transactionId) {
99+
this.transactionId = transactionId;
100+
}
101+
102+
public String getTransactionId() {
103+
return transactionId;
104+
}
105+
106+
public void setTableName(String tableName) {
107+
this.tableName = tableName;
108+
}
109+
110+
public String getTableName() {
111+
return tableName;
112+
}
113+
114+
public void setOperation(Operation operation) {
115+
this.operation = operation;
116+
}
117+
118+
public Operation getOperation() {
119+
return operation;
120+
}
121+
122+
/**
123+
* Indicates that the event is an initial data synchronization.
124+
*
125+
* @return true, first or last for snapshot event, false or null otherwise
126+
*/
127+
public String getSnapshot() {
128+
return snapshot;
129+
}
130+
131+
public void setSnapshot(String snapshot) {
132+
this.snapshot = snapshot;
133+
}
134+
135+
public Map<String, Object> getNewState() {
136+
return newState;
137+
}
138+
139+
public void setNewState(Map<String, Object> newState) {
140+
this.newState = newState;
141+
}
142+
143+
public Map<String, Object> getPreviousState() {
144+
return previousState;
145+
}
146+
147+
public void setPreviousState(Map<String, Object> previousState) {
148+
this.previousState = previousState;
149+
}
150+
151+
public enum Operation {
152+
READ, CREATE, UPDATE, DELETE, TRUNCATE
153+
}
154+
155+
@JsonIgnore
156+
@Override
157+
public @Nullable ResolvableType getResolvableType() {
158+
if (entityType != null && getClass().getTypeParameters().length == 1) {
159+
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forClass(entityType));
160+
} else {
161+
return ResolvableType.forClass(getClass());
162+
}
163+
}
164+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* This Source Code Form is subject to the terms of the Mozilla Public License,
3+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
4+
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5+
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6+
*
7+
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8+
* graphic logo is a trademark of OpenMRS Inc.
9+
*/
10+
package org.openmrs.event;
11+
12+
/**
13+
* Issued by the CDC engine upon completed transaction. It can be used to gather
14+
* events belonging to the same transaction.
15+
*
16+
* @since 2.9.0
17+
*/
18+
public class CDCTransactionCompletedEvent extends BaseEvent {
19+
20+
private static final long serialVersionUID = 1L;
21+
22+
private String transactionId;
23+
24+
public CDCTransactionCompletedEvent() {
25+
}
26+
27+
public CDCTransactionCompletedEvent(String transactionId) {
28+
this.transactionId = transactionId;
29+
}
30+
31+
public String getTransactionId() {
32+
return transactionId;
33+
}
34+
35+
public void setTransactionId(String transactionId) {
36+
this.transactionId = transactionId;
37+
}
38+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* This Source Code Form is subject to the terms of the Mozilla Public License,
3+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
4+
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5+
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6+
*
7+
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8+
* graphic logo is a trademark of OpenMRS Inc.
9+
*/
10+
package org.openmrs.event;
11+
12+
/**
13+
* Issued by the CDC engine upon started transaction.
14+
*
15+
* @since 2.9.0
16+
*/
17+
public class CDCTransactionStartedEvent extends BaseEvent {
18+
19+
private static final long serialVersionUID = 1L;
20+
21+
private String transactionId;
22+
23+
public CDCTransactionStartedEvent() {
24+
}
25+
26+
public CDCTransactionStartedEvent(String transactionId) {
27+
this.transactionId = transactionId;
28+
}
29+
30+
public String getTransactionId() {
31+
return transactionId;
32+
}
33+
34+
public void setTransactionId(String transactionId) {
35+
this.transactionId = transactionId;
36+
}
37+
}

0 commit comments

Comments
 (0)