Merge branch 'master' into dependabot/maven/dapr-spring/ch.qos.logback-logback-core-1.5.13

This commit is contained in:
Cassie Coyle 2025-06-02 10:06:24 -05:00 committed by GitHub
commit 8df0f200a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 358 additions and 1 deletions

View File

@ -62,6 +62,7 @@
<commons-cli.version>1.9.0</commons-cli.version> <commons-cli.version>1.9.0</commons-cli.version>
<commons-io.version>2.14.0</commons-io.version> <commons-io.version>2.14.0</commons-io.version>
<zipkin.version>3.4.0</zipkin.version> <zipkin.version>3.4.0</zipkin.version>
<microcks.version>0.3.1</microcks.version>
</properties> </properties>
<distributionManagement> <distributionManagement>

View File

@ -40,6 +40,12 @@
<artifactId>rest-assured</artifactId> <artifactId>rest-assured</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>io.github.microcks</groupId>
<artifactId>microcks-testcontainers</artifactId>
<version>${microcks.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -13,9 +13,12 @@ limitations under the License.
package io.dapr.springboot.examples.wfp; package io.dapr.springboot.examples.wfp;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog; import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration @Configuration
public class WorkflowPatternsConfiguration { public class WorkflowPatternsConfiguration {
@ -23,4 +26,14 @@ public class WorkflowPatternsConfiguration {
public CleanUpLog cleanUpLog(){ public CleanUpLog cleanUpLog(){
return new CleanUpLog(); return new CleanUpLog();
} }
@Bean
public RestTemplate restTemplate() {
return new RestTemplateBuilder().build();
}
@Bean
public ObjectMapper mapper() {
return new ObjectMapper();
}
} }

View File

@ -22,6 +22,8 @@ import io.dapr.springboot.examples.wfp.externalevent.Decision;
import io.dapr.springboot.examples.wfp.externalevent.ExternalEventWorkflow; import io.dapr.springboot.examples.wfp.externalevent.ExternalEventWorkflow;
import io.dapr.springboot.examples.wfp.fanoutin.FanOutInWorkflow; import io.dapr.springboot.examples.wfp.fanoutin.FanOutInWorkflow;
import io.dapr.springboot.examples.wfp.fanoutin.Result; import io.dapr.springboot.examples.wfp.fanoutin.Result;
import io.dapr.springboot.examples.wfp.remoteendpoint.Payload;
import io.dapr.springboot.examples.wfp.remoteendpoint.RemoteEndpointWorkflow;
import io.dapr.workflows.client.DaprWorkflowClient; import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus; import io.dapr.workflows.client.WorkflowInstanceStatus;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -53,6 +55,7 @@ public class WorkflowPatternsRestController {
private Map<String, String> ordersToApprove = new HashMap<>(); private Map<String, String> ordersToApprove = new HashMap<>();
/** /**
* Run Chain Demo Workflow * Run Chain Demo Workflow
* @return the output of the ChainWorkflow execution * @return the output of the ChainWorkflow execution
@ -137,4 +140,17 @@ public class WorkflowPatternsRestController {
return workflowInstanceStatus.readOutputAs(CleanUpLog.class); return workflowInstanceStatus.readOutputAs(CleanUpLog.class);
} }
@PostMapping("wfp/remote-endpoint")
public Payload remoteEndpoint(@RequestBody Payload payload)
throws TimeoutException {
String instanceId = daprWorkflowClient.scheduleNewWorkflow(RemoteEndpointWorkflow.class, payload);
logger.info("Workflow instance " + instanceId + " started");
WorkflowInstanceStatus workflowInstanceStatus = daprWorkflowClient
.waitForInstanceCompletion(instanceId, null, true);
System.out.printf("workflow instance with ID: %s completed.", instanceId);
return workflowInstanceStatus.readOutputAs(Payload.class);
}
} }

View File

@ -0,0 +1,51 @@
/*
* Copyright 2023 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples.wfp.remoteendpoint;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Component
public class CallRemoteEndpointActivity implements WorkflowActivity {
private Logger logger = LoggerFactory.getLogger(CallRemoteEndpointActivity.class);
@Value("${application.process-base-url:}")
private String processBaseURL;
@Autowired
private RestTemplate restTemplate;
@Override
public Object run(WorkflowActivityContext ctx) {
logger.info("Starting Activity: " + ctx.getName());
var payload = ctx.getInput(Payload.class);
HttpEntity<Payload> request =
new HttpEntity<>(payload);
payload = restTemplate.postForObject(processBaseURL + "/process", request, Payload.class);
logger.info("Payload from the remote service: " + payload);
return payload;
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples.wfp.remoteendpoint;
public class Payload {
private String id;
private String content;
private Boolean processed = false;
public Payload(String id, String content) {
this.id = id;
this.content = content;
}
public Payload() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Boolean getProcessed() {
return processed;
}
public void setProcessed(Boolean processed) {
this.processed = processed;
}
@Override
public String toString() {
return "Payload{" +
"id='" + id + '\'' +
", content='" + content + '\'' +
", processed=" + processed +
'}';
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2023 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples.wfp.remoteendpoint;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import io.dapr.workflows.WorkflowTaskOptions;
import io.dapr.workflows.WorkflowTaskRetryPolicy;
import org.springframework.stereotype.Component;
import java.time.Duration;
@Component
public class RemoteEndpointWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
ctx.getLogger().info("Starting Workflow: " + ctx.getName());
Payload payload = ctx.getInput(Payload.class);
payload = ctx.callActivity(CallRemoteEndpointActivity.class.getName(), payload ,
new WorkflowTaskOptions(new WorkflowTaskRetryPolicy(5,
Duration.ofSeconds(2), 1.0, Duration.ofSeconds(10), Duration.ofSeconds(20))),
Payload.class).await();
ctx.getLogger().info("Workflow finished with result: " + payload);
ctx.complete(payload);
};
}
}

View File

@ -15,11 +15,19 @@ package io.dapr.springboot.examples.wfp;
import io.dapr.testcontainers.Component; import io.dapr.testcontainers.Component;
import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.DaprContainer;
import io.github.microcks.testcontainers.MicrocksContainersEnsemble;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.test.context.DynamicPropertyRegistrar;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.Network;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG; import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
@ -28,16 +36,66 @@ public class DaprTestContainersConfig {
@Bean @Bean
@ServiceConnection @ServiceConnection
public DaprContainer daprContainer() { public DaprContainer daprContainer(Network network) {
return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG) return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
.withAppName("workflow-patterns-app") .withAppName("workflow-patterns-app")
.withComponent(new Component("kvstore", "state.in-memory", "v1", Collections.singletonMap("actorStateStore", String.valueOf(true)))) .withComponent(new Component("kvstore", "state.in-memory", "v1", Collections.singletonMap("actorStateStore", String.valueOf(true))))
.withAppPort(8080) .withAppPort(8080)
.withNetwork(network)
.withAppHealthCheckPath("/actuator/health") .withAppHealthCheckPath("/actuator/health")
.withAppChannelAddress("host.testcontainers.internal"); .withAppChannelAddress("host.testcontainers.internal");
} }
@Bean
MicrocksContainersEnsemble microcksEnsemble(Network network) {
return new MicrocksContainersEnsemble(network, "quay.io/microcks/microcks-uber:1.11.2")
.withAccessToHost(true) // We need this to access our webapp while it runs
.withMainArtifacts("third-parties/remote-http-service.yaml");
}
@Bean
public DynamicPropertyRegistrar endpointsProperties(MicrocksContainersEnsemble ensemble) {
// We need to replace the default endpoints with those provided by Microcks.
return (properties) -> {
properties.add("application.process-base-url", () -> ensemble.getMicrocksContainer()
.getRestMockEndpoint("API Payload Processor", "1.0.0"));
};
}
@Bean
public Network getDaprNetwork(Environment env) {
boolean reuse = env.getProperty("reuse", Boolean.class, false);
if (reuse) {
Network defaultDaprNetwork = new Network() {
@Override
public String getId() {
return "dapr-network";
}
@Override
public void close() {
}
@Override
public Statement apply(Statement base, Description description) {
return null;
}
};
List<com.github.dockerjava.api.model.Network> networks = DockerClientFactory.instance().client().listNetworksCmd()
.withNameFilter("dapr-network").exec();
if (networks.isEmpty()) {
Network.builder().createNetworkCmdModifier(cmd -> cmd.withName("dapr-network")).build().getId();
return defaultDaprNetwork;
} else {
return defaultDaprNetwork;
}
} else {
return Network.newNetwork();
}
}
} }

View File

@ -16,6 +16,8 @@ package io.dapr.springboot.examples.wfp;
import io.dapr.client.DaprClient; import io.dapr.client.DaprClient;
import io.dapr.springboot.DaprAutoConfiguration; import io.dapr.springboot.DaprAutoConfiguration;
import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog; import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog;
import io.dapr.springboot.examples.wfp.remoteendpoint.Payload;
import io.github.microcks.testcontainers.MicrocksContainersEnsemble;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -39,6 +41,9 @@ class WorkflowPatternsAppTests {
@Autowired @Autowired
private DaprClient daprClient; private DaprClient daprClient;
@Autowired
private MicrocksContainersEnsemble ensemble;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
RestAssured.baseURI = "http://localhost:" + 8080; RestAssured.baseURI = "http://localhost:" + 8080;
@ -139,4 +144,20 @@ class WorkflowPatternsAppTests {
assertEquals(5, cleanUpLog.getCleanUpTimes()); assertEquals(5, cleanUpLog.getCleanUpTimes());
} }
@Test
void testRemoteEndpoint() {
Payload payload = given().contentType(ContentType.JSON)
.body(new Payload("123", "content goes here"))
.when()
.post("/wfp/remote-endpoint")
.then()
.statusCode(200).extract().as(Payload.class);
assertEquals(true, payload.getProcessed());
assertEquals(2, ensemble.getMicrocksContainer()
.getServiceInvocationsCount("API Payload Processor", "1.0.0"));
}
} }

View File

@ -0,0 +1,87 @@
---
openapi: 3.0.2
info:
title: API Payload Processor
version: 1.0.0
description: API definition of API Payload Processor sample app
contact:
name: Salaboy
url: http://github.com/salaboy
email: salaboy@gmail.com
license:
name: MIT License
url: https://opensource.org/licenses/MIT
paths:
/process:
summary: Process payload
post:
tags:
- process
x-microcks-operation:
dispatcher: SCRIPT
dispatcherRules: |
def retries = store.get("retries") ?:"first"
if (retries == "first") {
store.put("retries", "second", 60)
return "Error"
}
store.delete("retries")
return "Payload"
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Payload'
required: true
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/Payload'
examples:
Payload:
value:
id: 123
content: payload content here
processed: true
description: Process payload
"500":
content:
application/json:
schema:
type: object
properties:
message:
type: string
description: Error message
examples:
Error:
value:
message: Something unexpected happened
description: Error payload
operationId: Process
summary: Process incoming payload
components:
schemas:
Payload:
title: Payload to be processed
description: Payload to be processed following the Payload type's schema.
type: object
properties:
id:
description: Payload Id
type: string
content:
description: Payload Content
type: string
processed:
description: Is the Payload processed
type: boolean
required:
- id
- content
additionalProperties: false
tags:
- name: payload
description: Payload resource