Skip to content
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

Add basic workflow #3

Merged
merged 6 commits into from
Oct 8, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Test Build
name: Integration Test
on:
pull_request:
push:
Expand All @@ -7,10 +7,12 @@ on:

jobs:
tests:
name: "Test build"
name: "Integration Test"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Set up xdb environment"
run: wget https://raw.githubusercontent.com/xdblab/xdb/main/docker-compose/docker-compose-postgres14-example.yaml && docker compose -f docker-compose-postgres14-example.yaml up -d
- uses: actions/setup-java@v3
with:
distribution: zulu
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# xdb-java-sdk

[![Coverage Status](https://codecov.io/github/xdblab/xdb-java-sdk/coverage.svg?branch=main)](https://app.codecov.io/gh/xdblab/xdb-java-sdk/branch/main)
[![Build status](https://github.com/xdblab/xdb-java-sdk/actions/workflows/ci-test.yml/badge.svg?branch=main)](https://github.com/xdblab/xdb-java-sdk/actions/workflows/ci-test.yml)
[![Build status](https://github.com/xdblab/xdb-java-sdk/actions/workflows/ci-integ-test.yml/badge.svg?branch=main)](https://github.com/xdblab/xdb-java-sdk/actions/workflows/ci-integ-test.yml)

Java SDK for [xdb](https://github.com/xdblab/xdb)

Expand Down
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ plugins {
id 'signing'
id 'jacoco'
id 'com.diffplug.spotless' version "6.13.0"
id "org.springframework.boot" version "2.7.16"
}

bootJar {
enabled = false
}

java {
Expand Down Expand Up @@ -46,6 +51,7 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
testCompileOnly 'org.projectlombok:lombok:1.18.30'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.30'
testImplementation 'org.springframework.boot:spring-boot-starter-web:2.7.16'
}

// open api
Expand Down
40 changes: 8 additions & 32 deletions src/main/java/io/xdb/core/client/BasicClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@
import feign.Retryer;
import io.xdb.core.ServerErrorDecoder;
import io.xdb.core.exception.XDBHttpException;
import io.xdb.core.process.BasicClientProcessOptions;
import io.xdb.core.process.ProcessOptions;
import io.xdb.gen.api.ApiClient;
import io.xdb.gen.api.DefaultApi;
import io.xdb.gen.models.ProcessExecutionDescribeRequest;
import io.xdb.gen.models.ProcessExecutionDescribeResponse;
import io.xdb.gen.models.ProcessExecutionStartRequest;
import io.xdb.gen.models.ProcessExecutionStartResponse;
import io.xdb.gen.models.ProcessStartConfig;

/**
* {@link BasicClient} serves as a foundational client without a process {@link io.xdb.core.registry}.
Expand All @@ -30,33 +27,7 @@ public BasicClient(final ClientOptions clientOptions) {
this.defaultApi = buildDefaultApi();
}

public String startProcess(
final String processType,
final String processId,
final String startStateId,
final Object input,
final BasicClientProcessOptions processOptions
) {
final ProcessExecutionStartRequest request = new ProcessExecutionStartRequest()
.processType(processType)
.processId(processId)
.workerUrl(clientOptions.getWorkerUrl())
.startStateId(startStateId)
.startStateInput(clientOptions.getObjectEncoder().encode(input));

if (processOptions.getProcessOptionsOptional().isPresent()) {
final ProcessOptions options = processOptions.getProcessOptionsOptional().get();
request.processStartConfig(
new ProcessStartConfig()
.idReusePolicy(options.getProcessIdReusePolicy())
.timeoutSeconds(options.getTimeoutSeconds())
);
}

if (processOptions.getStartStateConfig().isPresent()) {
request.startStateConfig(processOptions.getStartStateConfig().get());
}

public String startProcess(final ProcessExecutionStartRequest request) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use xdb public request as the input to BasicClient to reduce the SDK side wrapper.

final ProcessExecutionStartResponse response;
try {
response = defaultApi.apiV1XdbServiceProcessExecutionStartPost(request);
Expand All @@ -67,8 +38,13 @@ public String startProcess(
return response.getProcessExecutionId();
}

public ProcessExecutionDescribeResponse describeCurrentProcessExecution(final String processId) {
final ProcessExecutionDescribeRequest request = new ProcessExecutionDescribeRequest().processId(processId);
public ProcessExecutionDescribeResponse describeCurrentProcessExecution(
final String namespace,
final String processId
) {
final ProcessExecutionDescribeRequest request = new ProcessExecutionDescribeRequest()
.namespace(namespace)
.processId(processId);

try {
return defaultApi.apiV1XdbServiceProcessExecutionDescribePost(request);
Expand Down
65 changes: 41 additions & 24 deletions src/main/java/io/xdb/core/client/Client.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package io.xdb.core.client;

import io.xdb.core.process.BasicClientProcessOptions;
import io.xdb.core.process.Process;
import io.xdb.core.process.ProcessOptions;
import io.xdb.core.registry.Registry;
import io.xdb.core.state.AsyncState;
import io.xdb.core.utils.ProcessUtil;
import io.xdb.gen.models.AsyncStateConfig;
import java.util.Optional;
import io.xdb.gen.models.ProcessExecutionDescribeResponse;
import io.xdb.gen.models.ProcessExecutionStartRequest;
import java.time.Duration;

public class Client {

Expand All @@ -23,8 +22,8 @@ public Client(final Registry registry, final ClientOptions clientOptions) {
}

public String startProcess(final Process process, final String processId, final Object input) {
final String processType = ProcessUtil.getProcessType(process);
return startProcessInternal(processType, processId, input, process.getOptions());
final String processType = process.getOptions().getType(process.getClass());
return startProcessInternal(processType, processId, input);
}

/**
Expand All @@ -41,30 +40,48 @@ public String startProcess(
final Object input
) {
final String processType = ProcessUtil.getProcessType(processClass);
return startProcessInternal(processType, processId, input, null);
return startProcessInternal(processType, processId, input);
}

private String startProcessInternal(
final String processType,
final String processId,
final Object input,
final ProcessOptions processOptions
public ProcessExecutionDescribeResponse describeCurrentProcessExecution(final String processId) {
return describeCurrentProcessExecution(ProcessUtil.DEFAULT_NAMESPACE, processId);
}

public ProcessExecutionDescribeResponse describeCurrentProcessExecution(
final String namespace,
final String processId
) {
AsyncStateConfig asyncStateConfig = null;
String startingStateId = "";
return basicClient.describeCurrentProcessExecution(namespace, processId);
}

final Optional<AsyncState> startingState = registry.getProcessStartingState(processType);
if (startingState.isPresent()) {
asyncStateConfig =
new AsyncStateConfig().skipWaitUntil(AsyncState.shouldSkipWaitUntil(startingState.get()));
startingStateId = ProcessUtil.getStateId(startingState.get());
// TODO: placeholder to be used in integration test for now
public void getProcessResultWithWait(final String processId) {
System.out.println(processId);
try {
Thread.sleep(Duration.ofSeconds(2).toMillis());
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
}

private String startProcessInternal(final String processType, final String processId, final Object input) {
final Process process = registry.getProcess(processType);

final BasicClientProcessOptions basicClientProcessOptions = new BasicClientProcessOptions(
processOptions,
asyncStateConfig
);
final ProcessExecutionStartRequest request = new ProcessExecutionStartRequest()
.namespace(process.getOptions().getNamespace())
.processId(processId)
.processType(processType)
.workerUrl(clientOptions.getWorkerUrl())
.startStateInput(clientOptions.getObjectEncoder().encode(input))
.processStartConfig(process.getOptions().getProcessStartConfig());

final AsyncState startingState = process.getStateSchema().getStartingState();
if (startingState != null) {
request
.startStateId(startingState.getOptions().getId(startingState.getClass()))
.startStateConfig(ProcessUtil.getAsyncStateConfig(startingState));
}

return basicClient.startProcess(processType, processId, startingStateId, input, basicClientProcessOptions);
return basicClient.startProcess(request);
}
}
17 changes: 0 additions & 17 deletions src/main/java/io/xdb/core/process/BasicClientProcessOptions.java

This file was deleted.

3 changes: 3 additions & 0 deletions src/main/java/io/xdb/core/process/Process.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package io.xdb.core.process;

import io.xdb.core.state.StateSchema;
import lombok.NonNull;

/**
* The {@link Process} interface is used to define a process definition.
* It represents a fundamental concept at the top level in XDB.
*/
public interface Process {
@NonNull
default ProcessOptions getOptions() {
return ProcessOptions.builder().build();
}

@NonNull
default StateSchema getStateSchema() {
return StateSchema.builder().build();
}
Expand Down
25 changes: 20 additions & 5 deletions src/main/java/io/xdb/core/process/ProcessOptions.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
package io.xdb.core.process;

import io.xdb.gen.models.ProcessIdReusePolicy;
import static io.xdb.core.utils.ProcessUtil.DEFAULT_NAMESPACE;

import com.google.common.base.Strings;
import io.xdb.core.utils.ProcessUtil;
import io.xdb.gen.models.ProcessStartConfig;
import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
public class ProcessOptions {

// If not set, use the default value.
private final String namespace;
private final String type;
private final ProcessIdReusePolicy processIdReusePolicy;
private final Integer timeoutSeconds;
private final ProcessStartConfig processStartConfig;

public String getNamespace() {
return Strings.isNullOrEmpty(namespace) ? DEFAULT_NAMESPACE : namespace;
}

public String getType(final Class<? extends Process> processClass) {
Copy link
Contributor

@longquanzheng longquanzheng Oct 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It maybe better to move it to Process class as a package private method(, so that it doesn't require to pass in any parameter. ), or keep it in ProcessUtil

Passing a process class here is a bit strange

Copy link
Contributor

@longquanzheng longquanzheng Oct 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another idea: maybe it's even better to require passing in a Process instance when creating this ProcessOptions (is it possible in lombok to have a required field?, looks like by default all fields are optional in lombok --- looks like just put @NOT_NULL ?)

In the Process interface:

public interface Process{
 
   default getProcessOptions(){
         return ProcessOptions.build(this)
   }  
}

And we can make this build method as a helper to provide more parameters, like timeout, etc

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In lombok builder, the default value of each field is NULL. Adding @NonNull will make sure the specified field must be assigned with a non-null value at the RUNTIME.

Use ProcessOptionsBuilder builder(final Class<? extends Process> processClass) is a good idea to provide ProcessOptions.builder(this.getClass()).build().

Lombok also provides another way to genetate the ProcessOptionsBuilder by new ProcessOptions.ProcessOptionsBuilder(), I didn't find a way to disable it. So when using the new method to generate the builder, users have to be aware the processClass must be set explicitly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So when using the new method to generate the builder, users have to be aware the processClass must be set explicitly.
yeah that's true. That's why it would be nice if the field being required is clear.
Though, usually when there is a helper build which is easier to use, users would not use the raw/harder one.

return Strings.isNullOrEmpty(type) ? ProcessUtil.getProcessType(processClass) : type;
}

public ProcessStartConfig getProcessStartConfig() {
return processStartConfig;
}
}
28 changes: 12 additions & 16 deletions src/main/java/io/xdb/core/registry/Registry.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@
import io.xdb.core.exception.ProcessDefinitionException;
import io.xdb.core.process.Process;
import io.xdb.core.state.AsyncState;
import io.xdb.core.utils.ProcessUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import lombok.Getter;

@Getter
public class Registry {

// process type: process
Expand Down Expand Up @@ -37,21 +33,21 @@ public Process getProcess(final String type) {
return processStore.get(type);
}

public Optional<AsyncState> getProcessStartingState(final String type) {
final Process process = getProcess(type);
final AsyncState startingState = process.getStateSchema().getStartingState();
if (startingState == null) {
return Optional.empty();
}
return Optional.of(startingState);
}

public AsyncState getProcessState(final String type, final String stateId) {
if (!processStatesStore.containsKey(type) || !processStatesStore.get(type).containsKey(stateId)) {
throw new ProcessDefinitionException(
String.format(
"Process type %s or state id %s has not been registered in processStatesStore.",
type,
stateId
)
);
}
return processStatesStore.get(type).get(stateId);
}

private void registerProcess(final Process process) {
final String type = ProcessUtil.getProcessType(process);
final String type = process.getOptions().getType(process.getClass());

if (processStore.containsKey(type)) {
throw new ProcessDefinitionException(
Expand All @@ -63,12 +59,12 @@ private void registerProcess(final Process process) {
}

private void registerProcessStates(final Process process) {
final String processType = ProcessUtil.getProcessType(process);
final String processType = process.getOptions().getType(process.getClass());

final HashMap<String, AsyncState> stateMap = new HashMap<>();

for (final AsyncState state : process.getStateSchema().getAllStates()) {
final String stateId = ProcessUtil.getStateId(state);
final String stateId = state.getOptions().getId(state.getClass());

if (stateMap.containsKey(stateId)) {
throw new ProcessDefinitionException(
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/io/xdb/core/state/AsyncState.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.xdb.core.state;

import io.xdb.gen.models.CommandRequest;
import io.xdb.gen.models.StateDecision;
import java.lang.reflect.Method;
import lombok.NonNull;

public interface AsyncState<I> {
/**
Expand All @@ -16,8 +16,9 @@ public interface AsyncState<I> {
*
* @return the state options
*/
default StateOptions getOptions() {
return StateOptions.builder().build();
@NonNull
default AsyncStateOptions getOptions() {
return AsyncStateOptions.builder().build();
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/io/xdb/core/state/AsyncStateOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.xdb.core.state;

import com.google.common.base.Strings;
import io.xdb.core.utils.ProcessUtil;
import lombok.Builder;

@Builder
public class AsyncStateOptions {

private final String id;

public String getId(final Class<? extends AsyncState> stateClass) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar here, it seems better to pass in the state instance as required for builder?

return Strings.isNullOrEmpty(id) ? ProcessUtil.getStateId(stateClass) : id;
}
}
Loading