-
Notifications
You must be signed in to change notification settings - Fork 4.3k
TRUNK-6516: Provide CDC mechanism with Debezium #6151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /** | ||
| * This Source Code Form is subject to the terms of the Mozilla Public License, | ||
| * v. 2.0. If a copy of the MPL was not distributed with this file, You can | ||
| * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under | ||
| * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. | ||
| * | ||
| * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS | ||
| * graphic logo is a trademark of OpenMRS Inc. | ||
| */ | ||
| package org.openmrs.event; | ||
|
|
||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * See {@link EntityEvent} or {@link AggregatedEntityEvent}. | ||
| * | ||
| * @since 2.9.0 | ||
| */ | ||
| public abstract class BaseSessionEvent extends BaseEvent { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moving sessionId to BaseSessionEvent since BaseEvent is now extended by CDCEvent, for which sessionId does not make any sense as it is published by an asynchronous task. |
||
| private static final long serialVersionUID = 1L; | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consistency nit: Also: double blank line before |
||
| protected String sessionId; | ||
|
|
||
| /** | ||
| * Default constructor for deserialization. | ||
| */ | ||
| public BaseSessionEvent() { | ||
|
Check warning on line 27 in api/src/main/java/org/openmrs/event/BaseSessionEvent.java
|
||
| } | ||
|
|
||
| public BaseSessionEvent(Set<String> tags) { | ||
|
Check warning on line 30 in api/src/main/java/org/openmrs/event/BaseSessionEvent.java
|
||
| super(tags); | ||
| } | ||
|
|
||
| public BaseSessionEvent(String sessionId, Set<String> tags) { | ||
|
Check warning on line 34 in api/src/main/java/org/openmrs/event/BaseSessionEvent.java
|
||
| super(tags); | ||
| this.sessionId = sessionId; | ||
| } | ||
|
|
||
| public String getSessionId() { | ||
| return sessionId; | ||
| } | ||
|
|
||
| public void setSessionId(String sessionId) { | ||
| this.sessionId = sessionId; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| /** | ||
| * This Source Code Form is subject to the terms of the Mozilla Public License, | ||
| * v. 2.0. If a copy of the MPL was not distributed with this file, You can | ||
| * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under | ||
| * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. | ||
| * | ||
| * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS | ||
| * graphic logo is a trademark of OpenMRS Inc. | ||
| */ | ||
| package org.openmrs.event; | ||
|
|
||
| import org.codehaus.jackson.annotate.JsonIgnore; | ||
| import org.jspecify.annotations.Nullable; | ||
| import org.springframework.core.ResolvableType; | ||
| import org.springframework.core.ResolvableTypeProvider; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * Issued by the CDC engine (if available, see e.g. https://github.com/openmrs/openmrs-module-debezium) | ||
| * <p> | ||
| * Please note that CDC events are usually not happening inside an application transaction rather | ||
| * they are fetched from DB by a separate process e.g. Debezium. For this reason it does not make sense | ||
| * to use {@link org.springframework.transaction.event.TransactionalEventListener} to process them. | ||
| * <p> | ||
| * If you want to guarantee delivery, please use {@link org.springframework.context.event.EventListener} | ||
| * so that an event is processed in the same thread as the publisher and if the processing fails, it will be | ||
| * re-tried by the publisher. It is recommended only for fast enough listeners so that publisher is not | ||
| * blocked for too long, and it keeps up with the events queue. | ||
| * <p> | ||
| * If you combine {@link org.springframework.context.event.EventListener} with {@link org.springframework.scheduling.annotation.Async} | ||
| * 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. | ||
| * <p> | ||
| * For longer running processing with guaranteed delivery you may use {@link org.openmrs.event.outbox.OutboxEventListener} | ||
| * to persist events to the database and have them processed asynchronously or publish events with | ||
| * {@link org.springframework.context.event.EventListener} to a message broker for asynchronous processing. | ||
| * The latter is more performant, and it can also be used to exchange messages with external systems. | ||
| * | ||
| * @param <T> entityType associated with this event | ||
| * | ||
| * @since 2.9.0 | ||
| */ | ||
| public class CDCEvent<T> extends BaseEvent implements ResolvableTypeProvider { | ||
| private static final long serialVersionUID = 1L; | ||
|
|
||
| private String snapshot; | ||
|
|
||
| private Class<T> entityType; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It makes it easier to listen to events for certain entity types without a need for conditions in code. |
||
|
|
||
| private String transactionId; | ||
|
|
||
| private Operation operation; | ||
|
|
||
| private String tableName; | ||
|
|
||
| private Map<String, Object> primaryKey; | ||
|
Check failure on line 57 in api/src/main/java/org/openmrs/event/CDCEvent.java
|
||
|
|
||
| private Map<String, Object> newState; | ||
|
Check failure on line 59 in api/src/main/java/org/openmrs/event/CDCEvent.java
|
||
|
|
||
| private Map<String, Object> previousState; | ||
|
Check failure on line 61 in api/src/main/java/org/openmrs/event/CDCEvent.java
|
||
|
|
||
| /** | ||
| * Default constructor for deserialization. | ||
| */ | ||
| public CDCEvent() { | ||
| } | ||
|
|
||
| public CDCEvent(Class<T> entityType) { | ||
| this.entityType = entityType; | ||
| } | ||
|
|
||
| public CDCEvent(Class<T> entityType, Set<String> tags) { | ||
| super(tags); | ||
| this.entityType = entityType; | ||
| } | ||
|
|
||
| public void setPrimaryKey(Map<String, Object> primaryKey) { | ||
| this.primaryKey = primaryKey; | ||
| } | ||
|
|
||
| public Map<String, Object> getPrimaryKey() { | ||
| return primaryKey; | ||
| } | ||
|
|
||
| public boolean isCompositePrimaryKey() { | ||
| return primaryKey != null && primaryKey.size() > 1; | ||
| } | ||
|
|
||
| public void setEntityType(Class<T> entityType) { | ||
| this.entityType = entityType; | ||
| } | ||
|
|
||
| public Class<T> getEntityType() { | ||
| return entityType; | ||
| } | ||
|
|
||
| public void setTransactionId(String transactionId) { | ||
| this.transactionId = transactionId; | ||
| } | ||
|
|
||
| public String getTransactionId() { | ||
| return transactionId; | ||
| } | ||
|
|
||
| public void setTableName(String tableName) { | ||
| this.tableName = tableName; | ||
| } | ||
|
|
||
| public String getTableName() { | ||
| return tableName; | ||
| } | ||
|
|
||
| public void setOperation(Operation operation) { | ||
| this.operation = operation; | ||
| } | ||
|
|
||
| public Operation getOperation() { | ||
| return operation; | ||
| } | ||
|
|
||
| /** | ||
| * Indicates that the event is an initial data synchronization. | ||
| * | ||
| * @return true, first or last for snapshot event, false or null otherwise | ||
| */ | ||
| public String getSnapshot() { | ||
| return snapshot; | ||
| } | ||
|
|
||
| public void setSnapshot(String snapshot) { | ||
| this.snapshot = snapshot; | ||
| } | ||
|
|
||
| public Map<String, Object> getNewState() { | ||
| return newState; | ||
| } | ||
|
|
||
| public void setNewState(Map<String, Object> newState) { | ||
| this.newState = newState; | ||
| } | ||
|
|
||
| public Map<String, Object> getPreviousState() { | ||
| return previousState; | ||
| } | ||
|
|
||
| public void setPreviousState(Map<String, Object> previousState) { | ||
| this.previousState = previousState; | ||
| } | ||
|
|
||
| public enum Operation { | ||
| READ, CREATE, UPDATE, DELETE, TRUNCATE | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is TRUNCATE used for?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See https://debezium.io/documentation/reference/stable/connectors/mysql.html#mysql-truncate-events not useful to us at the moment, but added as suggested by AI for completeness.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok 👍 |
||
| } | ||
|
|
||
| @JsonIgnore | ||
| @Override | ||
| public @Nullable ResolvableType getResolvableType() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
| if (entityType != null && getClass().getTypeParameters().length == 1) { | ||
| return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forClass(entityType)); | ||
| } else { | ||
| return ResolvableType.forClass(getClass()); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /** | ||
| * This Source Code Form is subject to the terms of the Mozilla Public License, | ||
| * v. 2.0. If a copy of the MPL was not distributed with this file, You can | ||
| * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under | ||
| * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. | ||
| * | ||
| * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS | ||
| * graphic logo is a trademark of OpenMRS Inc. | ||
| */ | ||
| package org.openmrs.event; | ||
|
|
||
| /** | ||
| * Issued by the CDC engine upon completed transaction. It can be used to gather | ||
| * events belonging to the same transaction. | ||
| * | ||
| * @since 2.9.0 | ||
| */ | ||
| public class CDCTransactionCompletedEvent extends BaseEvent { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we also need one for transaction rolled back?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No CDC for roll back. The DB log contains only committed data.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh i see! 😊 |
||
|
|
||
| private static final long serialVersionUID = 1L; | ||
|
|
||
| private String transactionId; | ||
|
|
||
| public CDCTransactionCompletedEvent() { | ||
| } | ||
|
|
||
| public CDCTransactionCompletedEvent(String transactionId) { | ||
| this.transactionId = transactionId; | ||
| } | ||
|
|
||
| public String getTransactionId() { | ||
| return transactionId; | ||
| } | ||
|
|
||
| public void setTransactionId(String transactionId) { | ||
| this.transactionId = transactionId; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| /** | ||
| * This Source Code Form is subject to the terms of the Mozilla Public License, | ||
| * v. 2.0. If a copy of the MPL was not distributed with this file, You can | ||
| * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under | ||
| * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. | ||
| * | ||
| * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS | ||
| * graphic logo is a trademark of OpenMRS Inc. | ||
| */ | ||
| package org.openmrs.event; | ||
|
|
||
| /** | ||
| * Issued by the CDC engine upon started transaction. | ||
| * | ||
| * @since 2.9.0 | ||
| */ | ||
| public class CDCTransactionStartedEvent extends BaseEvent { | ||
|
|
||
| private static final long serialVersionUID = 1L; | ||
|
|
||
| private String transactionId; | ||
|
|
||
| public CDCTransactionStartedEvent() { | ||
| } | ||
|
|
||
| public CDCTransactionStartedEvent(String transactionId) { | ||
| this.transactionId = transactionId; | ||
| } | ||
|
|
||
| public String getTransactionId() { | ||
| return transactionId; | ||
| } | ||
|
|
||
| public void setTransactionId(String transactionId) { | ||
| this.transactionId = transactionId; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to Spring docs extending ApplicationEvent is no longer recommended or needed for events.