Skip to content

Commit e437f66

Browse files
authored
App Version Support (#327)
fixes #314
1 parent 83acd2e commit e437f66

22 files changed

Lines changed: 917 additions & 75 deletions

build.gradle.kts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,12 @@ subprojects {
160160
tasks.withType<Test> {
161161
useJUnitPlatform()
162162
testLogging {
163-
events("passed", "skipped", "failed")
163+
events("failed")
164164
showStandardStreams = true
165+
showExceptions = true
166+
showCauses = true
167+
showStackTraces = true
168+
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
165169
}
166170
addTestListener(
167171
object : TestListener {

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import dev.dbos.transact.workflow.SerializationStrategy;
2020
import dev.dbos.transact.workflow.StepInfo;
2121
import dev.dbos.transact.workflow.StepOptions;
22+
import dev.dbos.transact.workflow.VersionInfo;
2223
import dev.dbos.transact.workflow.Workflow;
2324
import dev.dbos.transact.workflow.WorkflowClassName;
2425
import dev.dbos.transact.workflow.WorkflowHandle;
@@ -731,6 +732,33 @@ public void deleteWorkflows(@NonNull List<String> workflowIds, boolean deleteChi
731732
return forkWorkflow(workflowId, startStep, new ForkOptions());
732733
}
733734

735+
/**
736+
* List all registered application versions, ordered by timestamp descending.
737+
*
738+
* @return list of {@link VersionInfo} records
739+
*/
740+
public @NonNull List<VersionInfo> listApplicationVersions() {
741+
return ensureLaunched("listApplicationVersions").listApplicationVersions();
742+
}
743+
744+
/**
745+
* Get the most recently promoted application version.
746+
*
747+
* @return the latest {@link VersionInfo}
748+
*/
749+
public @NonNull VersionInfo getLatestApplicationVersion() {
750+
return ensureLaunched("getLatestApplicationVersion").getLatestApplicationVersion();
751+
}
752+
753+
/**
754+
* Promote a version to be the latest application version.
755+
*
756+
* @param versionName the version to promote
757+
*/
758+
public void setLatestApplicationVersion(@NonNull String versionName) {
759+
ensureLaunched("setLatestApplicationVersion").setLatestApplicationVersion(versionName);
760+
}
761+
734762
/**
735763
* Retrieve a handle to a workflow, given its ID. Note that a handle is always returned, whether
736764
* the workflow exists or not; getStatus() can be used to tell the difference

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import dev.dbos.transact.workflow.SerializationStrategy;
1212
import dev.dbos.transact.workflow.StepInfo;
1313
import dev.dbos.transact.workflow.Timeout;
14+
import dev.dbos.transact.workflow.VersionInfo;
1415
import dev.dbos.transact.workflow.WorkflowHandle;
1516
import dev.dbos.transact.workflow.WorkflowState;
1617
import dev.dbos.transact.workflow.WorkflowStatus;
@@ -516,13 +517,14 @@ public EnqueueOptions(
516517
options.deduplicationId,
517518
options.priority,
518519
options.queuePartitionKey,
520+
options.appVersion,
519521
false,
520522
false,
521523
serializationFormat),
522524
null,
523525
null,
524526
null,
525-
options.appVersion,
527+
null,
526528
systemDatabase,
527529
this.serializer);
528530

@@ -800,4 +802,31 @@ public void deleteWorkflows(@NonNull List<String> workflowIds, boolean deleteChi
800802
public @NonNull List<StepInfo> listWorkflowSteps(@NonNull String workflowId) {
801803
return systemDatabase.listWorkflowSteps(workflowId);
802804
}
805+
806+
/**
807+
* List all registered application versions, ordered by timestamp descending.
808+
*
809+
* @return list of {@link VersionInfo} records
810+
*/
811+
public @NonNull List<VersionInfo> listApplicationVersions() {
812+
return systemDatabase.listApplicationVersions();
813+
}
814+
815+
/**
816+
* Get the most recently promoted application version.
817+
*
818+
* @return the latest {@link VersionInfo}
819+
*/
820+
public @NonNull VersionInfo getLatestApplicationVersion() {
821+
return systemDatabase.getLatestApplicationVersion();
822+
}
823+
824+
/**
825+
* Promote an existing version to be the latest application version by updating its timestamp.
826+
*
827+
* @param versionName the version to promote; it must already exist
828+
*/
829+
public void setLatestApplicationVersion(@NonNull String versionName) {
830+
systemDatabase.updateApplicationVersionTimestamp(versionName, Instant.now());
831+
}
803832
}

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

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ public record StartWorkflowOptions(
3636
@Nullable String queueName,
3737
@Nullable String deduplicationId,
3838
@Nullable Integer priority,
39-
@Nullable String queuePartitionKey) {
39+
@Nullable String queuePartitionKey,
40+
@Nullable String appVersion) {
4041

4142
public StartWorkflowOptions {
4243
if (timeout instanceof Timeout.Explicit explicit) {
@@ -64,17 +65,17 @@ public record StartWorkflowOptions(
6465

6566
/** Construct with default options */
6667
public StartWorkflowOptions() {
67-
this(null, null, null, null, null, null, null);
68+
this(null, null, null, null, null, null, null, null);
6869
}
6970

7071
/** Construct with a specified workflow ID */
7172
public StartWorkflowOptions(String workflowId) {
72-
this(workflowId, null, null, null, null, null, null);
73+
this(workflowId, null, null, null, null, null, null, null);
7374
}
7475

7576
/** Construct with a specified queue */
7677
public StartWorkflowOptions(@NonNull Queue queue) {
77-
this(null, null, null, queue.name(), null, null, null);
78+
this(null, null, null, queue.name(), null, null, null, null);
7879
}
7980

8081
/** Produces a new StartWorkflowOptions that overrides the ID assigned to the started workflow */
@@ -86,7 +87,8 @@ public StartWorkflowOptions(@NonNull Queue queue) {
8687
this.queueName,
8788
this.deduplicationId,
8889
this.priority,
89-
this.queuePartitionKey);
90+
this.queuePartitionKey,
91+
this.appVersion);
9092
}
9193

9294
/** Produces a new StartWorkflowOptions that overrides timeout value for the started workflow */
@@ -98,7 +100,8 @@ public StartWorkflowOptions(@NonNull Queue queue) {
98100
this.queueName,
99101
this.deduplicationId,
100102
this.priority,
101-
this.queuePartitionKey);
103+
this.queuePartitionKey,
104+
this.appVersion);
102105
}
103106

104107
/** Produces a new StartWorkflowOptions that overrides timeout value for the started workflow */
@@ -125,7 +128,8 @@ public StartWorkflowOptions(@NonNull Queue queue) {
125128
this.queueName,
126129
this.deduplicationId,
127130
this.priority,
128-
this.queuePartitionKey);
131+
this.queuePartitionKey,
132+
this.appVersion);
129133
}
130134

131135
/** Produces a new StartWorkflowOptions that assigns the started workflow to a queue */
@@ -137,7 +141,8 @@ public StartWorkflowOptions(@NonNull Queue queue) {
137141
queue,
138142
this.deduplicationId,
139143
this.priority,
140-
this.queuePartitionKey);
144+
this.queuePartitionKey,
145+
this.appVersion);
141146
}
142147

143148
/** Produces a new StartWorkflowOptions that assigns the started workflow to a queue */
@@ -157,7 +162,8 @@ public StartWorkflowOptions(@NonNull Queue queue) {
157162
this.queueName,
158163
deduplicationId,
159164
this.priority,
160-
this.queuePartitionKey);
165+
this.queuePartitionKey,
166+
this.appVersion);
161167
}
162168

163169
/**
@@ -172,7 +178,8 @@ public StartWorkflowOptions(@NonNull Queue queue) {
172178
this.queueName,
173179
this.deduplicationId,
174180
priority,
175-
this.queuePartitionKey);
181+
this.queuePartitionKey,
182+
this.appVersion);
176183
}
177184

178185
/** Produces a new StartWorkflowOptions that assigns a queue partition key */
@@ -184,7 +191,21 @@ public StartWorkflowOptions(@NonNull Queue queue) {
184191
this.queueName,
185192
this.deduplicationId,
186193
this.priority,
187-
queuePartitionKey);
194+
queuePartitionKey,
195+
this.appVersion);
196+
}
197+
198+
/** Produces a new StartWorkflowOptions that assigns an app version */
199+
public @NonNull StartWorkflowOptions withAppVersion(@Nullable String appVersion) {
200+
return new StartWorkflowOptions(
201+
this.workflowId,
202+
this.timeout,
203+
this.deadline,
204+
this.queueName,
205+
this.deduplicationId,
206+
this.priority,
207+
this.queuePartitionKey,
208+
appVersion);
188209
}
189210

190211
/** Get the assigned workflow ID, replacing empty with null */

transact/src/main/java/dev/dbos/transact/conductor/Conductor.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import dev.dbos.transact.conductor.protocol.GetWorkflowRequest;
1818
import dev.dbos.transact.conductor.protocol.GetWorkflowResponse;
1919
import dev.dbos.transact.conductor.protocol.ImportWorkflowRequest;
20+
import dev.dbos.transact.conductor.protocol.ListApplicationVersionsResponse;
2021
import dev.dbos.transact.conductor.protocol.ListQueuedWorkflowsRequest;
2122
import dev.dbos.transact.conductor.protocol.ListStepsRequest;
2223
import dev.dbos.transact.conductor.protocol.ListStepsResponse;
@@ -26,6 +27,7 @@
2627
import dev.dbos.transact.conductor.protocol.RestartRequest;
2728
import dev.dbos.transact.conductor.protocol.ResumeRequest;
2829
import dev.dbos.transact.conductor.protocol.RetentionRequest;
30+
import dev.dbos.transact.conductor.protocol.SetLatestApplicationVersionRequest;
2931
import dev.dbos.transact.conductor.protocol.SuccessResponse;
3032
import dev.dbos.transact.conductor.protocol.WorkflowOutputsResponse;
3133
import dev.dbos.transact.conductor.protocol.WorkflowsOutput;
@@ -709,13 +711,15 @@ CompletableFuture<BaseResponse> getResponseAsync(BaseMessage message, WebSocket
709711
case GET_METRICS -> handleGetMetrics(this, message);
710712
case GET_WORKFLOW -> handleGetWorkflow(this, message);
711713
case IMPORT_WORKFLOW -> handleImportWorkflow(this, message);
714+
case LIST_APPLICATION_VERSIONS -> handleListApplicationVersions(this, message);
712715
case LIST_QUEUED_WORKFLOWS -> handleListQueuedWorkflows(this, message);
713716
case LIST_STEPS -> handleListSteps(this, message);
714717
case LIST_WORKFLOWS -> handleListWorkflows(this, message);
715718
case RECOVERY -> handleRecovery(this, message);
716719
case RESTART -> handleRestart(this, message);
717720
case RESUME -> handleResume(this, message);
718721
case RETENTION -> handleRetention(this, message);
722+
case SET_LATEST_APPLICATION_VERSION -> handleSetLatestApplicationVersion(this, message);
719723
};
720724
}
721725

@@ -878,6 +882,38 @@ static CompletableFuture<BaseResponse> handleListQueuedWorkflows(
878882
});
879883
}
880884

885+
static CompletableFuture<BaseResponse> handleListApplicationVersions(
886+
Conductor conductor, BaseMessage message) {
887+
return CompletableFuture.supplyAsync(
888+
() -> {
889+
try {
890+
var output = conductor.systemDatabase.listApplicationVersions();
891+
return new ListApplicationVersionsResponse(message, output);
892+
} catch (Exception e) {
893+
logger.error("Exception encountered when listing application versions", e);
894+
return new ListApplicationVersionsResponse(message, e);
895+
}
896+
});
897+
}
898+
899+
static CompletableFuture<BaseResponse> handleSetLatestApplicationVersion(
900+
Conductor conductor, BaseMessage message) {
901+
return CompletableFuture.supplyAsync(
902+
() -> {
903+
SetLatestApplicationVersionRequest request = (SetLatestApplicationVersionRequest) message;
904+
try {
905+
conductor.dbosExecutor.setLatestApplicationVersion(request.version_name);
906+
return new SuccessResponse(request, true);
907+
} catch (Exception e) {
908+
logger.error(
909+
"Exception encountered when setting latest application version to {}",
910+
request.version_name,
911+
e);
912+
return new SuccessResponse(request, e);
913+
}
914+
});
915+
}
916+
881917
static CompletableFuture<BaseResponse> handleListSteps(Conductor conductor, BaseMessage message) {
882918
return CompletableFuture.supplyAsync(
883919
() -> {

transact/src/main/java/dev/dbos/transact/conductor/protocol/BaseMessage.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,19 @@
2020
@JsonSubTypes.Type(value = GetMetricsRequest.class, name = "get_metrics"),
2121
@JsonSubTypes.Type(value = GetWorkflowRequest.class, name = "get_workflow"),
2222
@JsonSubTypes.Type(value = ImportWorkflowRequest.class, name = "import_workflow"),
23+
@JsonSubTypes.Type(
24+
value = ListApplicationVersionsRequest.class,
25+
name = "list_application_versions"),
2326
@JsonSubTypes.Type(value = ListQueuedWorkflowsRequest.class, name = "list_queued_workflows"),
2427
@JsonSubTypes.Type(value = ListStepsRequest.class, name = "list_steps"),
2528
@JsonSubTypes.Type(value = ListWorkflowsRequest.class, name = "list_workflows"),
2629
@JsonSubTypes.Type(value = RecoveryRequest.class, name = "recovery"),
2730
@JsonSubTypes.Type(value = RestartRequest.class, name = "restart"),
2831
@JsonSubTypes.Type(value = ResumeRequest.class, name = "resume"),
2932
@JsonSubTypes.Type(value = RetentionRequest.class, name = "retention"),
33+
@JsonSubTypes.Type(
34+
value = SetLatestApplicationVersionRequest.class,
35+
name = "set_latest_application_version"),
3036
})
3137
@JsonIgnoreProperties(ignoreUnknown = true)
3238
public abstract class BaseMessage {

transact/src/main/java/dev/dbos/transact/conductor/protocol/GetMetricsResponse.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@
44

55
import java.util.Collections;
66
import java.util.List;
7-
import java.util.stream.Collectors;
87

98
public class GetMetricsResponse extends BaseResponse {
10-
public static record MetricsDataOutput(String metric_type, String metric_name, long value) {}
9+
public static record MetricsDataOutput(String metric_type, String metric_name, long value) {
10+
public static MetricsDataOutput fromMetricData(MetricData m) {
11+
return new MetricsDataOutput(m.metricType(), m.metricName(), m.value());
12+
}
13+
}
1114

1215
public List<MetricsDataOutput> metrics;
1316

1417
public GetMetricsResponse() {}
1518

1619
public GetMetricsResponse(BaseMessage message, List<MetricData> metrics) {
1720
super(message.type, message.request_id);
18-
this.metrics =
19-
metrics.stream()
20-
.map(m -> new MetricsDataOutput(m.metricType(), m.metricName(), m.value()))
21-
.collect(Collectors.toList());
21+
this.metrics = metrics.stream().map(MetricsDataOutput::fromMetricData).toList();
2222
}
2323

2424
public GetMetricsResponse(BaseMessage message, Exception ex) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package dev.dbos.transact.conductor.protocol;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
5+
@JsonIgnoreProperties(ignoreUnknown = true)
6+
public class ListApplicationVersionsRequest extends BaseMessage {}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package dev.dbos.transact.conductor.protocol;
2+
3+
import dev.dbos.transact.workflow.VersionInfo;
4+
5+
import java.util.Collections;
6+
import java.util.List;
7+
8+
public class ListApplicationVersionsResponse extends BaseResponse {
9+
public static record AppVersionInfo(
10+
String version_id, String version_name, long version_timestamp, long created_at) {
11+
public static AppVersionInfo fromVersionInfo(VersionInfo v) {
12+
return new AppVersionInfo(
13+
v.versionId(),
14+
v.versionName(),
15+
v.versionTimestamp().toEpochMilli(),
16+
v.createdAt().toEpochMilli());
17+
}
18+
}
19+
20+
public List<AppVersionInfo> output;
21+
22+
public ListApplicationVersionsResponse() {}
23+
24+
public ListApplicationVersionsResponse(BaseMessage message, List<VersionInfo> versions) {
25+
super(message.type, message.request_id);
26+
this.output = versions.stream().map(AppVersionInfo::fromVersionInfo).toList();
27+
}
28+
29+
public ListApplicationVersionsResponse(BaseMessage message, Exception ex) {
30+
super(message.type, message.request_id, ex.getMessage());
31+
this.output = Collections.emptyList();
32+
}
33+
}

0 commit comments

Comments
 (0)