Skip to content

Commit a15fc45

Browse files
devhawkCopilot
andauthored
Assorted Changes (#373)
* move `DBOS` `registerLifecycleListener`, `registerWorkflow`, `getRegisteredWorkflow`, `getRegisteredWorkflows`, `getRegisteredWorkflowInstances`, `getExternalState` and `upsertExternalState` to `DBOSIntegration` * Remove `@Step.retriesAllowed` * Validate StepOptions * fix/improve javadocs --------- Co-authored-by: Copilot <copilot@github.com>
1 parent 4cb9dba commit a15fc45

12 files changed

Lines changed: 371 additions & 190 deletions

File tree

transact-spring-boot-starter/src/main/java/dev/dbos/transact/spring/DBOSWorkflowRegistrar.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public void afterSingletonsInstantiated() {
101101
beanFactory = configurableCtx.getBeanFactory();
102102
}
103103

104+
var integration = dbos.integration();
104105
for (Map.Entry<Class<?>, List<String>> entry : beanNamesByClass.entrySet()) {
105106
Class<?> targetClass = entry.getKey();
106107
List<String> beanNames = entry.getValue();
@@ -128,7 +129,7 @@ public void afterSingletonsInstantiated() {
128129
}
129130
var wfTag = method.getAnnotation(Workflow.class);
130131
if (wfTag != null) {
131-
dbos.registerWorkflow(wfTag, rawTarget, method, registerName);
132+
integration.registerWorkflow(wfTag, rawTarget, method, registerName);
132133
}
133134
}
134135
}

transact-spring-boot-starter/src/test/java/dev/dbos/transact/spring/DBOSWorkflowRegistrarTest.java

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import static org.mockito.Mockito.when;
1111

1212
import dev.dbos.transact.DBOS;
13+
import dev.dbos.transact.internal.DBOSIntegration;
1314
import dev.dbos.transact.workflow.Step;
1415
import dev.dbos.transact.workflow.Workflow;
1516

@@ -73,6 +74,9 @@ private static ConfigurableApplicationContext mockCtx(
7374
@Test
7475
void registersBeansWithWorkflowMethods() throws Exception {
7576
var mockDbos = mock(DBOS.class);
77+
var mockIntegration = mock(DBOSIntegration.class);
78+
when(mockDbos.integration()).thenReturn(mockIntegration);
79+
7680
var mockBeanFactory = mock(ConfigurableListableBeanFactory.class);
7781
var mockCtx = mockCtx(mockBeanFactory, "workflowBean");
7882
var bean = new BeanWithWorkflow();
@@ -86,25 +90,31 @@ void registersBeansWithWorkflowMethods() throws Exception {
8690
var wfTag = method.getAnnotation(Workflow.class);
8791
assertNotNull(wfTag);
8892

89-
verify(mockDbos).registerWorkflow(eq(wfTag), eq(bean), eq(method), eq(null));
93+
verify(mockIntegration).registerWorkflow(eq(wfTag), eq(bean), eq(method), eq(null));
9094
}
9195

9296
@Test
9397
void skipsBeansWithoutWorkflowMethods() {
9498
var mockDbos = mock(DBOS.class);
99+
var mockIntegration = mock(DBOSIntegration.class);
100+
when(mockDbos.integration()).thenReturn(mockIntegration);
101+
95102
var mockBeanFactory = mock(ConfigurableListableBeanFactory.class);
96103
var mockCtx = mockCtx(mockBeanFactory, "plainBean");
97104

98105
when(mockCtx.getBean("plainBean")).thenReturn(new BeanWithoutWorkflow());
99106

100107
new DBOSWorkflowRegistrar(mockDbos, mockCtx).afterSingletonsInstantiated();
101108

102-
verify(mockDbos, never()).registerWorkflow(any(), any(), any(), any());
109+
verify(mockIntegration, never()).registerWorkflow(any(), any(), any(), any());
103110
}
104111

105112
@Test
106113
void skipsBeansThatThrowOnLookup() {
107114
var mockDbos = mock(DBOS.class);
115+
var mockIntegration = mock(DBOSIntegration.class);
116+
when(mockDbos.integration()).thenReturn(mockIntegration);
117+
108118
var mockBeanFactory = mock(ConfigurableListableBeanFactory.class);
109119
var mockCtx = mockCtx(mockBeanFactory, "badBean");
110120

@@ -113,12 +123,15 @@ void skipsBeansThatThrowOnLookup() {
113123
// should complete without throwing
114124
new DBOSWorkflowRegistrar(mockDbos, mockCtx).afterSingletonsInstantiated();
115125

116-
verify(mockDbos, never()).registerWorkflow(any(), any(), any(), any());
126+
verify(mockIntegration, never()).registerWorkflow(any(), any(), any(), any());
117127
}
118128

119129
@Test
120130
void processesMultipleBeans() {
121131
var mockDbos = mock(DBOS.class);
132+
var mockIntegration = mock(DBOSIntegration.class);
133+
when(mockDbos.integration()).thenReturn(mockIntegration);
134+
122135
var mockBeanFactory = mock(ConfigurableListableBeanFactory.class);
123136
var mockCtx = mockCtx(mockBeanFactory, "wfBean", "plainBean");
124137
var wfBean = new BeanWithWorkflow();
@@ -129,13 +142,16 @@ void processesMultipleBeans() {
129142

130143
new DBOSWorkflowRegistrar(mockDbos, mockCtx).afterSingletonsInstantiated();
131144

132-
verify(mockDbos).registerWorkflow(any(), eq(wfBean), any(), eq(null));
133-
verify(mockDbos, never()).registerWorkflow(any(), eq(plainBean), any(), any());
145+
verify(mockIntegration).registerWorkflow(any(), eq(wfBean), any(), eq(null));
146+
verify(mockIntegration, never()).registerWorkflow(any(), eq(plainBean), any(), any());
134147
}
135148

136149
@Test
137150
void registersMultipleBeansOfSameClassUsingBeanNames() {
138151
var mockDbos = mock(DBOS.class);
152+
var mockIntegration = mock(DBOSIntegration.class);
153+
when(mockDbos.integration()).thenReturn(mockIntegration);
154+
139155
var mockBeanFactory = mock(ConfigurableListableBeanFactory.class);
140156
var mockCtx = mockCtx(mockBeanFactory, "primaryBean", "secondaryBean");
141157
var primaryBean = new BeanWithWorkflow();
@@ -153,13 +169,16 @@ void registersMultipleBeansOfSameClassUsingBeanNames() {
153169

154170
new DBOSWorkflowRegistrar(mockDbos, mockCtx).afterSingletonsInstantiated();
155171

156-
verify(mockDbos).registerWorkflow(any(), eq(primaryBean), any(), eq(null));
157-
verify(mockDbos).registerWorkflow(any(), eq(secondaryBean), any(), eq("secondaryBean"));
172+
verify(mockIntegration).registerWorkflow(any(), eq(primaryBean), any(), eq(null));
173+
verify(mockIntegration).registerWorkflow(any(), eq(secondaryBean), any(), eq("secondaryBean"));
158174
}
159175

160176
@Test
161177
void beanWithBothWorkflowAndStep_onlyRegistersWorkflow() throws Exception {
162178
var mockDbos = mock(DBOS.class);
179+
var mockIntegration = mock(DBOSIntegration.class);
180+
when(mockDbos.integration()).thenReturn(mockIntegration);
181+
163182
var mockBeanFactory = mock(ConfigurableListableBeanFactory.class);
164183
var mockCtx = mockCtx(mockBeanFactory, "mixedBean");
165184
var bean = new BeanWithWorkflowAndStep();
@@ -169,15 +188,18 @@ void beanWithBothWorkflowAndStep_onlyRegistersWorkflow() throws Exception {
169188
new DBOSWorkflowRegistrar(mockDbos, mockCtx).afterSingletonsInstantiated();
170189

171190
var method = BeanWithWorkflowAndStep.class.getMethod("myWorkflow");
172-
verify(mockDbos).registerWorkflow(any(), eq(bean), eq(method), eq(null));
173-
verify(mockDbos, never())
191+
verify(mockIntegration).registerWorkflow(any(), eq(bean), eq(method), eq(null));
192+
verify(mockIntegration, never())
174193
.registerWorkflow(
175194
any(), any(), eq(BeanWithWorkflowAndStep.class.getMethod("myStep")), any());
176195
}
177196

178197
@Test
179198
void beanLookupFailureMidScan_doesNotPreventOtherBeanRegistration() {
180199
var mockDbos = mock(DBOS.class);
200+
var mockIntegration = mock(DBOSIntegration.class);
201+
when(mockDbos.integration()).thenReturn(mockIntegration);
202+
181203
var mockBeanFactory = mock(ConfigurableListableBeanFactory.class);
182204
var mockCtx = mockCtx(mockBeanFactory, "badBean", "goodBean");
183205
var goodBean = new BeanWithWorkflow();
@@ -187,12 +209,15 @@ void beanLookupFailureMidScan_doesNotPreventOtherBeanRegistration() {
187209

188210
new DBOSWorkflowRegistrar(mockDbos, mockCtx).afterSingletonsInstantiated();
189211

190-
verify(mockDbos).registerWorkflow(any(), eq(goodBean), any(), eq(null));
212+
verify(mockIntegration).registerWorkflow(any(), eq(goodBean), any(), eq(null));
191213
}
192214

193215
@Test
194216
void inheritedWorkflowMethods_areDetectedAndRegistered() throws Exception {
195217
var mockDbos = mock(DBOS.class);
218+
var mockIntegration = mock(DBOSIntegration.class);
219+
when(mockDbos.integration()).thenReturn(mockIntegration);
220+
196221
var mockBeanFactory = mock(ConfigurableListableBeanFactory.class);
197222
var mockCtx = mockCtx(mockBeanFactory, "derivedBean");
198223
var bean = new DerivedWorkflowBean();
@@ -202,12 +227,15 @@ void inheritedWorkflowMethods_areDetectedAndRegistered() throws Exception {
202227
new DBOSWorkflowRegistrar(mockDbos, mockCtx).afterSingletonsInstantiated();
203228

204229
var method = BaseWorkflowBean.class.getDeclaredMethod("baseWorkflow");
205-
verify(mockDbos).registerWorkflow(any(), eq(bean), eq(method), eq(null));
230+
verify(mockIntegration).registerWorkflow(any(), eq(bean), eq(method), eq(null));
206231
}
207232

208233
@Test
209234
void nonSingletonBeanWithWorkflow_throwsIllegalStateException() {
210235
var mockDbos = mock(DBOS.class);
236+
var mockIntegration = mock(DBOSIntegration.class);
237+
when(mockDbos.integration()).thenReturn(mockIntegration);
238+
211239
var mockBeanFactory = mock(ConfigurableListableBeanFactory.class);
212240
var mockCtx = mockCtx(mockBeanFactory, "prototypeBean");
213241

@@ -222,6 +250,9 @@ void nonSingletonBeanWithWorkflow_throwsIllegalStateException() {
222250
@Test
223251
void nonSingletonBeanWithStep_throwsIllegalStateException() {
224252
var mockDbos = mock(DBOS.class);
253+
var mockIntegration = mock(DBOSIntegration.class);
254+
when(mockDbos.integration()).thenReturn(mockIntegration);
255+
225256
var mockBeanFactory = mock(ConfigurableListableBeanFactory.class);
226257
var mockCtx = mockCtx(mockBeanFactory, "prototypeBean");
227258

@@ -236,6 +267,9 @@ void nonSingletonBeanWithStep_throwsIllegalStateException() {
236267
@Test
237268
void registersMultipleBeansOfSameClassWithBeanNamesWhenNoneIsPrimary() {
238269
var mockDbos = mock(DBOS.class);
270+
var mockIntegration = mock(DBOSIntegration.class);
271+
when(mockDbos.integration()).thenReturn(mockIntegration);
272+
239273
var mockBeanFactory = mock(ConfigurableListableBeanFactory.class);
240274
var mockCtx = mockCtx(mockBeanFactory, "beanA", "beanB");
241275
var beanA = new BeanWithWorkflow();
@@ -253,7 +287,7 @@ void registersMultipleBeansOfSameClassWithBeanNamesWhenNoneIsPrimary() {
253287

254288
new DBOSWorkflowRegistrar(mockDbos, mockCtx).afterSingletonsInstantiated();
255289

256-
verify(mockDbos).registerWorkflow(any(), eq(beanA), any(), eq("beanA"));
257-
verify(mockDbos).registerWorkflow(any(), eq(beanB), any(), eq("beanB"));
290+
verify(mockIntegration).registerWorkflow(any(), eq(beanA), any(), eq("beanA"));
291+
verify(mockIntegration).registerWorkflow(any(), eq(beanB), any(), eq("beanB"));
258292
}
259293
}

transact/src/main/java/dev/dbos/transact/DBOS.java

Lines changed: 24 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22

33
import dev.dbos.transact.config.DBOSConfig;
44
import dev.dbos.transact.context.DBOSContext;
5-
import dev.dbos.transact.database.ExternalState;
65
import dev.dbos.transact.execution.DBOSExecutor;
76
import dev.dbos.transact.execution.DBOSLifecycleListener;
8-
import dev.dbos.transact.execution.RegisteredWorkflow;
9-
import dev.dbos.transact.execution.RegisteredWorkflowInstance;
107
import dev.dbos.transact.execution.ThrowingRunnable;
118
import dev.dbos.transact.execution.ThrowingSupplier;
129
import dev.dbos.transact.internal.DBOSIntegration;
@@ -35,7 +32,6 @@
3532
import java.time.Duration;
3633
import java.time.Instant;
3734
import java.util.Arrays;
38-
import java.util.Collection;
3935
import java.util.HashSet;
4036
import java.util.Iterator;
4137
import java.util.List;
@@ -66,7 +62,9 @@ public class DBOS implements AutoCloseable {
6662
private final Set<DBOSLifecycleListener> lifecycleRegistry = ConcurrentHashMap.newKeySet();
6763
private final DBOSConfig config;
6864
private final AtomicReference<DBOSExecutor> dbosExecutor = new AtomicReference<>();
69-
private final DBOSIntegration integration = new DBOSIntegration(dbosExecutor::get);
65+
private final DBOSIntegration integration =
66+
new DBOSIntegration(
67+
dbosExecutor::get, this::registerLifecycleListener, this::registerWorkflow);
7068

7169
private AlertHandler alertHandler;
7270

@@ -132,7 +130,7 @@ public static String version() {
132130
*
133131
* @param listener
134132
*/
135-
public void registerLifecycleListener(@NonNull DBOSLifecycleListener listener) {
133+
private void registerLifecycleListener(@NonNull DBOSLifecycleListener listener) {
136134
if (dbosExecutor.get() != null) {
137135
throw new IllegalStateException("Cannot register lifecycle listener after DBOS is launched");
138136
}
@@ -226,7 +224,7 @@ public void registerQueues(@NonNull Queue... queues) {
226224
* @param instanceName optional instance name for the workflow (can be null)
227225
* @throws IllegalStateException if called after DBOS is launched
228226
*/
229-
public void registerWorkflow(
227+
private void registerWorkflow(
230228
@NonNull Workflow wfTag,
231229
@NonNull Object target,
232230
@NonNull Method method,
@@ -345,13 +343,7 @@ private DBOSExecutor ensureLaunched(String caller) {
345343
* @param duration amount of time to sleep
346344
*/
347345
public void sleep(@NonNull Duration duration) {
348-
if (!DBOSContext.inWorkflow()) {
349-
try {
350-
Thread.sleep(duration.toMillis());
351-
} catch (InterruptedException e) {
352-
Thread.currentThread().interrupt();
353-
}
354-
} else if (DBOSContext.inStep()) {
346+
if (!DBOSContext.inWorkflow() || DBOSContext.inStep()) {
355347
try {
356348
Thread.sleep(duration.toMillis());
357349
} catch (InterruptedException e) {
@@ -896,28 +888,29 @@ public void applySchedules(@NonNull WorkflowSchedule... schedules) {
896888
ensureLaunched("applySchedules").applySchedules(Arrays.asList(schedules));
897889
}
898890

899-
// /**
900-
// * Enqueue all executions of a schedule that would have run between {@code start} (exclusive)
901-
// and
902-
// * {@code end} (exclusive). Uses the same deterministic workflow IDs as the live scheduler, so
903-
// * already-executed times are skipped.
904-
// *
905-
// * @param scheduleName name of an existing schedule
906-
// * @param start start of the backfill window (exclusive)
907-
// * @param end end of the backfill window (exclusive)
908-
// * @return handles to the enqueued executions
909-
// */
891+
/**
892+
* Enqueue all executions of a schedule that would have run between {@code start} (exclusive) and
893+
* {@code end} (exclusive).
894+
*
895+
* <p>Uses the same deterministic workflow IDs as the live scheduler, so already-executed times
896+
* are skipped.
897+
*
898+
* @param scheduleName name of an existing schedule
899+
* @param start start of the backfill window (exclusive)
900+
* @param end end of the backfill window (exclusive)
901+
* @return handles to the enqueued executions
902+
*/
910903
public @NonNull List<WorkflowHandle<Object, Exception>> backfillSchedule(
911904
@NonNull String scheduleName, @NonNull Instant start, @NonNull Instant end) {
912905
return ensureLaunched("backfillSchedule").backfillSchedule(scheduleName, start, end);
913906
}
914907

915-
// /**
916-
// * Immediately enqueue the scheduled workflow at the current time.
917-
// *
918-
// * @param scheduleName name of an existing schedule
919-
// * @return handle to the enqueued execution
920-
// */
908+
/**
909+
* Immediately enqueue the scheduled workflow at the current time.
910+
*
911+
* @param scheduleName name of an existing schedule
912+
* @return handle to the enqueued execution
913+
*/
921914
public <T, E extends Exception> @NonNull WorkflowHandle<T, E> triggerSchedule(
922915
@NonNull String scheduleName) {
923916
return ensureLaunched("triggerSchedule").triggerSchedule(scheduleName);
@@ -1016,76 +1009,6 @@ public void setWorkflowDelay(@NonNull String workflowId, @NonNull Instant delayU
10161009
return ensureLaunched("listWorkflowSteps").listWorkflowSteps(workflowId, true, limit, offset);
10171010
}
10181011

1019-
/**
1020-
* Get all workflows registered with DBOS.
1021-
*
1022-
* @return list of all registered workflow methods
1023-
*/
1024-
public @NonNull Collection<RegisteredWorkflow> getRegisteredWorkflows() {
1025-
return ensureLaunched("getRegisteredWorkflows").getRegisteredWorkflows();
1026-
}
1027-
1028-
/**
1029-
* Finds a registered workflow by its workflow name, class name, and instance name.
1030-
*
1031-
* @param workflowName the name of the workflow
1032-
* @param className the name of the class containing the workflow
1033-
* @return an Optional containing the RegisteredWorkflow if found, otherwise empty
1034-
*/
1035-
public Optional<RegisteredWorkflow> getRegisteredWorkflow(
1036-
@NonNull String workflowName, @NonNull String className) {
1037-
return getRegisteredWorkflow(workflowName, className, "");
1038-
}
1039-
1040-
/**
1041-
* Finds a registered workflow by its workflow name, class name, and instance name.
1042-
*
1043-
* @param workflowName the name of the workflow
1044-
* @param className the name of the class containing the workflow
1045-
* @param instanceName the name of the workflow instance
1046-
* @return an Optional containing the RegisteredWorkflow if found, otherwise empty
1047-
*/
1048-
public Optional<RegisteredWorkflow> getRegisteredWorkflow(
1049-
@NonNull String workflowName, @NonNull String className, @NonNull String instanceName) {
1050-
return ensureLaunched("getRegisteredWorkflow")
1051-
.getRegisteredWorkflow(workflowName, className, instanceName);
1052-
}
1053-
1054-
/**
1055-
* Get all workflow classes registered with DBOS.
1056-
*
1057-
* @return list of all class instances containing registered workflow methods
1058-
*/
1059-
public @NonNull Collection<RegisteredWorkflowInstance> getRegisteredWorkflowInstances() {
1060-
return ensureLaunched("getRegisteredWorkflowInstances").getRegisteredWorkflowInstances();
1061-
}
1062-
1063-
/**
1064-
* Get a system database record stored by an external service A unique value is stored per
1065-
* combination of service, workflowName, and key
1066-
*
1067-
* @param service Identity of the service maintaining the record
1068-
* @param workflowName Fully qualified name of the workflow
1069-
* @param key Key assigned within the service+workflow
1070-
* @return Optional containing the value associated with the service+workflow+key combination, or
1071-
* empty if not found
1072-
*/
1073-
public Optional<ExternalState> getExternalState(String service, String workflowName, String key) {
1074-
return ensureLaunched("getExternalState").getExternalState(service, workflowName, key);
1075-
}
1076-
1077-
/**
1078-
* Insert or update a system database record stored by an external service A timestamped unique
1079-
* value is stored per combination of service, workflowName, and key
1080-
*
1081-
* @param state ExternalState containing the service, workflow, key, and value to store
1082-
* @return Value associated with the service+workflow+key combination, in case the stored value
1083-
* already had a higher version or timestamp
1084-
*/
1085-
public ExternalState upsertExternalState(ExternalState state) {
1086-
return ensureLaunched("upsertExternalState").upsertExternalState(state);
1087-
}
1088-
10891012
/**
10901013
* Marks a breaking change within a workflow. Returns true for new workflows (i.e. workflow sthat
10911014
* reach this point in the workflow after the breaking change was created) and false for old

0 commit comments

Comments
 (0)