Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 0 additions & 12 deletions .github/workflows/build-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,6 @@ jobs:
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'microsoft'

- name: set JDK_11 environment variable test compiling and running
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
run: echo ::set-env name=JDK_11::$(echo $JAVA_HOME)

- name: Set up JDK 8
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'temurin'

- name: Setup Gradle
Expand All @@ -57,7 +46,6 @@ jobs:

- name: Run Unit Tests with Gradle
run: |
export JAVA_HOME=$JDK_11
./gradlew clean test || echo "UNIT_TEST_FAILED=true" >> $GITHUB_ENV
continue-on-error: true

Expand Down
49 changes: 48 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,52 @@
Verify Compilation:
```shell
./gradlew compileJava
```

Build & test:

```shell
./gradlew build
```
```

## Verify Build Components

To ensure all build components work correctly after making changes:

```shell
# Build without tests
./gradlew build -x test

# Run unit tests
./gradlew test

# Run SpotBugs
./gradlew spotbugsMain spotbugsTest
```

## Updating Protobuf Definitions

When updating the protobuf definitions in `internal/durabletask-protobuf/protos/orchestrator_service.proto`:

1. Manually copy the updated protobuf file from dapr/durabletask-protobuf
2. Update the commit hash in `internal/durabletask-protobuf/PROTO_SOURCE_COMMIT_HASH` to reflect the new commit
3. Regenerate the Java classes from the protobuf definitions:

```shell
./gradlew generateProto
```

## Test locally from dapr/java-sdk

```shell
./gradlew publishToMavenLocal
```

or simply `./gradlew build publishToMavenLocal`

Check if it was released locally with:
```shell
ls ~/.m2/repository/io/dapr/durabletask-client/
```

Then update the durabletask-client in the java-sdk pom.xml file to the newest version you released locally.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# Durable Task Client SDK for Java

[![Build](https://github.com/microsoft/durabletask-java/actions/workflows/build-validation.yml/badge.svg)](https://github.com/microsoft/durabletask-java/actions/workflows/build-validation.yml)
[![Build](https://github.com/dapr/durabletask-java/actions/workflows/build-validation.yml/badge.svg)](https://github.com/dapr/durabletask-java/actions/workflows/build-validation.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

This repo contains the Java SDK for the Durable Task Framework as well as classes and annotations to support running [Azure Durable Functions](https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-overview?tabs=java) for Java. With this SDK, you can define, schedule, and manage durable orchestrations using ordinary Java code.

## Requirements

- **Java 11 or higher** - This SDK requires Java 11 as the minimum version
- **Gradle** - The project uses [Gradle 7.6.4](gradle/wrapper/gradle-wrapper.properties)

### Simple, fault-tolerant sequences

```java
Expand Down Expand Up @@ -78,8 +83,7 @@ The following packages are produced from this repo.

| Package | Latest version |
| - | - |
| Durable Task - Client | [![Maven Central](https://img.shields.io/maven-central/v/com.microsoft/durabletask-client?label=durabletask-client)](https://mvnrepository.com/artifact/com.microsoft/durabletask-client/1.0.0) |
| Durable Task - Azure Functions | [![Maven Central](https://img.shields.io/maven-central/v/com.microsoft/durabletask-azure-functions?label=durabletask-azure-functions)](https://mvnrepository.com/artifact/com.microsoft/durabletask-azure-functions/1.0.1) |
| Durable Task - Client | [![Maven Central](https://img.shields.io/maven-central/v/io.dapr/durabletask-client?label=durabletask-client)](https://mvnrepository.com/artifact/io.dapr/durabletask-client/1.5.7) |

## Getting started with Azure Functions

Expand Down
2 changes: 1 addition & 1 deletion azurefunctions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ In this article, you follow steps to create and run a simple azure durable funct

The following are the requirements for you local environment:

- The Java Developer Kit, version 8 or 11, is required. Between the two, JDK 11 is recommended.
- The Java Developer Kit, version 11 or higher, is required.
- The `JAVA_HOME` environment variable must be set to the install location of the correct version of the JDK.
- [Apache Maven](https://maven.apache.org/), version 3.0 or above for azure function app creation, is required for using automatic project creation tools.
- If Maven isn't your preferred development tool, check out our similar tutorials to [create a function app](https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-java?tabs=bash%2Cazure-cli%2Cbrowser). This README also contains instructions for [Gradle](https://gradle.org/).
Expand Down
56 changes: 32 additions & 24 deletions client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@ plugins {
}

group 'io.dapr'
version = '1.5.7'
version = '1.5.8'
archivesBaseName = 'durabletask-client'

def grpcVersion = '1.69.0'
def protocVersion = '3.25.5'
def jacksonVersion = '2.15.3'
// When build on local, you need to set this value to your local jdk11 directory.
// Java11 is used to compile and run all the tests.
// Example for Windows: C:/Program Files/Java/openjdk-11.0.12_7/
def PATH_TO_TEST_JAVA_RUNTIME = System.env.JDK_11 ?: System.getProperty("java.home")
// Java 11 is now the minimum required version for both compilation and testing

dependencies {

// https://github.com/grpc/grpc-java#download
implementation "io.grpc:grpc-protobuf:${grpcVersion}"
implementation "io.grpc:grpc-stub:${grpcVersion}"
implementation 'com.google.protobuf:protobuf-java:3.25.5'
runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}"

compileOnly "org.apache.tomcat:annotations-api:6.0.53"
Expand All @@ -44,49 +42,59 @@ dependencies {

// Netty dependencies for TLS
implementation "io.grpc:grpc-netty-shaded:${grpcVersion}"
implementation "io.netty:netty-handler:4.1.94.Final"
implementation 'io.netty:netty-handler:4.1.119.Final'
implementation "io.netty:netty-tcnative-boringssl-static:2.0.59.Final"

// Add Netty dependencies to test classpath
testImplementation "io.grpc:grpc-netty-shaded:${grpcVersion}"
testImplementation "io.netty:netty-handler:4.1.94.Final"
testImplementation 'io.netty:netty-handler:4.1.119.Final'
testImplementation "io.netty:netty-tcnative-boringssl-static:2.0.59.Final"

testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70'
testImplementation 'org.bouncycastle:bcpkix-jdk15on:1.70'
}

compileJava {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
compileTestJava {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
options.fork = true
options.forkOptions.executable = "${PATH_TO_TEST_JAVA_RUNTIME}/bin/javac"
}

task downloadProtoFiles {
ext.branch = project.hasProperty('protoBranch') ? project.protoBranch : 'main'

doLast {
def protoDir = file("${rootProject.projectDir}/internal/durabletask-protobuf/protos")
def protoFile = new File(protoDir, 'orchestrator_service.proto')
def commitHashFile = new File("${rootProject.projectDir}/internal/durabletask-protobuf/PROTO_SOURCE_COMMIT_HASH")
protoDir.mkdirs()

// Download the proto file
new URL("https://raw.githubusercontent.com/dapr/durabletask-protobuf/${ext.branch}/protos/orchestrator_service.proto")
.withInputStream { i ->
new File(protoDir, 'orchestrator_service.proto').withOutputStream { it << i }
protoFile.withOutputStream { it << i }
}

// Get and save the commit hash
def commitHashFile = new File("${rootProject.projectDir}/internal/durabletask-protobuf/PROTO_SOURCE_COMMIT_HASH")
def commitApiUrl = new URL("https://api.github.com/repos/dapr/durabletask-protobuf/commits?path=protos/orchestrator_service.proto&sha=${ext.branch}&per_page=1")
def connection = commitApiUrl.openConnection()
connection.setRequestProperty('Accept', 'application/vnd.github.v3+json')
def commitHash = new groovy.json.JsonSlurper().parse(connection.inputStream)[0].sha
commitHashFile.text = commitHash

try {
def commitApiUrl = new URL("https://api.github.com/repos/dapr/durabletask-protobuf/commits?path=protos/orchestrator_service.proto&sha=${ext.branch}&per_page=1")
def connection = commitApiUrl.openConnection()
connection.setRequestProperty('Accept', 'application/vnd.github.v3+json')
connection.setRequestProperty('User-Agent', 'durabletask-java-build')

// Add GitHub token if available (automatically provided in GitHub Actions)
if (System.env.GITHUB_TOKEN) {
connection.setRequestProperty('Authorization', "token ${System.env.GITHUB_TOKEN}")
}

def commitHash = new groovy.json.JsonSlurper().parse(connection.inputStream)[0].sha
commitHashFile.text = commitHash
logger.info("Successfully updated proto commit hash: ${commitHash}")
} catch (Exception e) {
logger.error("Failed to fetch commit hash from GitHub API: ${e.message}")
}
}
}

Expand Down Expand Up @@ -115,10 +123,6 @@ sourceSets {
}
}

tasks.withType(Test) {
executable = new File("${PATH_TO_TEST_JAVA_RUNTIME}", 'bin/java')
}

test {
useJUnitPlatform {
// Skip tests tagged as "integration" since those are slower
Expand Down Expand Up @@ -208,6 +212,10 @@ java {
withJavadocJar()
}

tasks.named('sourcesJar').configure {
dependsOn tasks.named('generateProto')
}

spotbugs {
toolVersion = '4.9.2'
effort = 'max'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public final class DurableTaskGrpcWorker implements AutoCloseable {
private final DataConverter dataConverter;
private final Duration maximumTimerInterval;
private final ExecutorService workerPool;
private final String appId; // App ID for cross-app routing

private final TaskHubSidecarServiceBlockingStub sidecarClient;
private final boolean isExecutorServiceManaged;
Expand All @@ -45,6 +46,7 @@ public final class DurableTaskGrpcWorker implements AutoCloseable {
DurableTaskGrpcWorker(DurableTaskGrpcWorkerBuilder builder) {
this.orchestrationFactories.putAll(builder.orchestrationFactories);
this.activityFactories.putAll(builder.activityFactories);
this.appId = builder.appId;

Channel sidecarGrpcChannel;
if (builder.channel != null) {
Expand Down Expand Up @@ -128,7 +130,8 @@ public void startAndBlock() {
this.orchestrationFactories,
this.dataConverter,
this.maximumTimerInterval,
logger);
logger,
this.appId);
TaskActivityExecutor taskActivityExecutor = new TaskActivityExecutor(
this.activityFactories,
this.dataConverter,
Expand All @@ -143,6 +146,9 @@ public void startAndBlock() {
RequestCase requestType = workItem.getRequestCase();
if (requestType == RequestCase.ORCHESTRATORREQUEST) {
OrchestratorRequest orchestratorRequest = workItem.getOrchestratorRequest();
logger.log(Level.FINEST,
String.format("Processing orchestrator request for instance: {0}",
orchestratorRequest.getInstanceId()));

// TODO: Error handling
this.workerPool.submit(() -> {
Expand All @@ -159,6 +165,9 @@ public void startAndBlock() {

try {
this.sidecarClient.completeOrchestratorTask(response);
logger.log(Level.FINEST,
"Completed orchestrator request for instance: {0}",
orchestratorRequest.getInstanceId());
} catch (StatusRuntimeException e) {
if (e.getStatus().getCode() == Status.Code.UNAVAILABLE) {
logger.log(Level.WARNING,
Expand All @@ -177,7 +186,12 @@ public void startAndBlock() {
});
} else if (requestType == RequestCase.ACTIVITYREQUEST) {
ActivityRequest activityRequest = workItem.getActivityRequest();
logger.log(Level.FINEST,
String.format("Processing activity request: %s for instance: %s}",
activityRequest.getName(),
activityRequest.getOrchestrationInstance().getInstanceId()));

// TODO: Error handling
this.workerPool.submit(() -> {
String output = null;
TaskFailureDetails failureDetails = null;
Expand Down Expand Up @@ -228,7 +242,8 @@ public void startAndBlock() {
} else if (requestType == RequestCase.HEALTHPING) {
// No-op
} else {
logger.log(Level.WARNING, "Received and dropped an unknown '{0}' work-item from the sidecar.",
logger.log(Level.WARNING,
"Received and dropped an unknown '{0}' work-item from the sidecar.",
requestType);
}
}
Expand Down
Loading
Loading