Skip to content

Commit 896a46f

Browse files
committed
WIP: Add initial version of the how-to guide
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
1 parent 8a80a48 commit 896a46f

File tree

4 files changed

+718
-0
lines changed

4 files changed

+718
-0
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package io.quarkiverse.flow.it;
2+
3+
4+
import io.quarkiverse.flow.Flow;
5+
import io.quarkus.test.junit.QuarkusTest;
6+
import io.serverlessworkflow.api.types.Workflow;
7+
import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder;
8+
import io.serverlessworkflow.impl.WorkflowContextData;
9+
import io.serverlessworkflow.impl.WorkflowModel;
10+
import io.smallrye.mutiny.Uni;
11+
import jakarta.enterprise.context.ApplicationScoped;
12+
import jakarta.inject.Inject;
13+
import org.junit.jupiter.api.Assertions;
14+
import org.junit.jupiter.api.Test;
15+
16+
import java.net.URI;
17+
import java.util.Objects;
18+
import java.util.function.Function;
19+
20+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.function;
21+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.http;
22+
23+
@QuarkusTest
24+
public class Call4PapersTest {
25+
26+
@Inject
27+
Call4PapersFlow flow;
28+
29+
@Test
30+
void should_execute_correctly() {
31+
var submission = new Call4PapersFlow.ProposalSubmission(
32+
"Reactive Workflows with Quarkus",
33+
"This paper explores reactive workflow patterns...",
34+
"Jane Developer"
35+
);
36+
WorkflowModel workflowModel = flow.startInstance(submission).await().indefinitely();
37+
38+
Assertions.assertNotNull(workflowModel);
39+
}
40+
41+
@ApplicationScoped
42+
public static class Call4PapersFlow extends Flow {
43+
44+
@Override
45+
public Workflow descriptor() {
46+
return FuncWorkflowBuilder.workflow("call4papers")
47+
.tasks(
48+
// Step 1: Validate proposal with inputFrom transformation
49+
function("validateProposal", (Proposal input) -> {
50+
System.out.println("Validating proposal: " + input.title());
51+
return input;
52+
}, Proposal.class)
53+
.inputFrom((ProposalSubmission submission) -> new Proposal(
54+
submission.title(),
55+
submission.proposal(), // Maps to abstractText
56+
submission.author()), ProposalSubmission.class),
57+
58+
// Step 2: Score proposal with outputAs transformation
59+
function("scoreProposal", (Proposal input) -> {
60+
Integer score = calculateScore(input.abstractText());
61+
System.out.println("Score calculated having the result as: " + score);
62+
return score;
63+
}, Proposal.class)
64+
.outputAs((Long score) -> {
65+
return new ProposalScore(score, score >= 7);
66+
}, Long.class),
67+
68+
// Step 3: Prepare notification with exportAs using workflow context
69+
function("prepareNotification", proposalScore -> new ProposalScore(proposalScore.score, proposalScore.accepted), ProposalScore.class)
70+
.exportAs((ProposalScore proposalScore, WorkflowContextData workflowContext) -> {
71+
// Access original workflow input to get title and author
72+
WorkflowModel originalInput = workflowContext.instanceData().input();
73+
ProposalSubmission submission = originalInput.as(ProposalSubmission.class).orElseThrow();
74+
75+
// Create enriched payload combining original input with score
76+
return new NotificationPayload(
77+
submission.title(),
78+
submission.author(),
79+
proposalScore.score(),
80+
proposalScore.accepted());
81+
}, ProposalScore.class).inputFrom(Function.identity(), ProposalScore.class),
82+
83+
// Step 4: Send notification via HTTP
84+
http("sendNotification")
85+
.POST()
86+
.body("${.}") // Uses the NotificationPayload from exportAs
87+
.header("Content-Type", "application/json")
88+
.uri(URI.create("http://localhost:9999/notifications")))
89+
.build();
90+
}
91+
92+
/**
93+
* Calculate a score for the proposal based on its abstract.
94+
* In a real implementation, this might use NLP, keyword analysis, etc.
95+
*/
96+
private Integer calculateScore(String abstractText) {
97+
// Simple scoring: longer abstracts get higher scores
98+
int length = abstractText.length();
99+
if (length > 500)
100+
return 9;
101+
if (length > 300)
102+
return 7;
103+
if (length > 150)
104+
return 5;
105+
return 3;
106+
}
107+
108+
// Data types representing different stages of the workflow
109+
110+
/**
111+
* External DTO received from the API
112+
*/
113+
public record ProposalSubmission(String title, String proposal, String author) {
114+
}
115+
116+
/**
117+
* Internal domain model used for processing
118+
*/
119+
public record Proposal(String title, String abstractText, String author) {
120+
}
121+
122+
/**
123+
* Scoring result persisted in workflow data
124+
*/
125+
public record ProposalScore(long score, boolean accepted) {
126+
}
127+
128+
/**
129+
* Final notification payload enriched with data from multiple sources
130+
*/
131+
public record NotificationPayload(String title, String author, long score, boolean accepted) {
132+
}
133+
}
134+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.acme.dataflow;
2+
3+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.function;
4+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.http;
5+
6+
import java.net.URI;
7+
8+
import jakarta.enterprise.context.ApplicationScoped;
9+
10+
import io.quarkiverse.flow.Flow;
11+
import io.serverlessworkflow.api.types.Workflow;
12+
import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder;
13+
import io.serverlessworkflow.impl.WorkflowContextData;
14+
import io.serverlessworkflow.impl.WorkflowModel;
15+
16+
@ApplicationScoped
17+
public class Call4PapersFlow extends Flow {
18+
19+
@Override
20+
public Workflow descriptor() {
21+
return FuncWorkflowBuilder.workflow("call4papers")
22+
.tasks(
23+
// Step 1: Validate proposal with inputFrom transformation
24+
function("validateProposal", (Proposal input) -> {
25+
System.out.println("Validating proposal: " + input.title());
26+
return input;
27+
}, Proposal.class)
28+
.inputFrom((ProposalSubmission submission) -> new Proposal(
29+
submission.title(),
30+
submission.proposal(), // Maps to abstractText
31+
submission.author()), ProposalSubmission.class),
32+
33+
// Step 2: Score proposal with outputAs transformation
34+
function("scoreProposal", (Proposal input) -> {
35+
int score = calculateScore(input.abstractText());
36+
return score;
37+
}, Proposal.class)
38+
.outputAs((Integer score) -> new ProposalScore(score, score >= 7),
39+
Integer.class),
40+
41+
// Step 3: Prepare notification with exportAs using workflow context
42+
function("prepareNotification", (ProposalScore proposalScore) -> {
43+
return proposalScore; // Pass through
44+
}, ProposalScore.class)
45+
.exportAs((ProposalScore proposalScore, WorkflowContextData workflowContext) -> {
46+
// Access original workflow input to get title and author
47+
WorkflowModel originalInput = workflowContext.instanceData().input();
48+
ProposalSubmission submission = originalInput.as(ProposalSubmission.class).orElseThrow();
49+
50+
// Create enriched payload combining original input with score
51+
return new NotificationPayload(
52+
submission.title(),
53+
submission.author(),
54+
proposalScore.score(),
55+
proposalScore.accepted());
56+
}, ProposalScore.class),
57+
58+
// Step 4: Send notification via HTTP
59+
http("sendNotification")
60+
.POST()
61+
.body("${.}") // Uses the NotificationPayload from exportAs
62+
.header("Content-Type", "application/json")
63+
.uri(URI.create("http://localhost:9999/notifications")))
64+
.build();
65+
}
66+
67+
/**
68+
* Calculate a score for the proposal based on its abstract.
69+
* In a real implementation, this might use NLP, keyword analysis, etc.
70+
*/
71+
private int calculateScore(String abstractText) {
72+
// Simple scoring: longer abstracts get higher scores
73+
int length = abstractText.length();
74+
if (length > 500)
75+
return 9;
76+
if (length > 300)
77+
return 7;
78+
if (length > 150)
79+
return 5;
80+
return 3;
81+
}
82+
83+
// Data types representing different stages of the workflow
84+
85+
/**
86+
* External DTO received from the API
87+
*/
88+
public record ProposalSubmission(String title, String proposal, String author) {
89+
}
90+
91+
/**
92+
* Internal domain model used for processing
93+
*/
94+
public record Proposal(String title, String abstractText, String author) {
95+
}
96+
97+
/**
98+
* Scoring result persisted in workflow data
99+
*/
100+
public record ProposalScore(int score, boolean accepted) {
101+
}
102+
103+
/**
104+
* Final notification payload enriched with data from multiple sources
105+
*/
106+
public record NotificationPayload(String title, String author, int score, boolean accepted) {
107+
}
108+
}

docs/modules/ROOT/pages/data-flow.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ Here you can:
116116
This is handy for logging, debugging, or adding metadata without polluting your
117117
business types.
118118

119+
[[export-as]]
119120
=== 2.2 `exportAs` – pass data to the next step
120121

121122
`exportAs` controls what the task **exports** for downstream consumers (the next
@@ -266,6 +267,7 @@ Good when:
266267

267268
You can freely mix JQ and Java across tasks in the same workflow.
268269

270+
[[quick-reference]]
269271
== 5. Quick reference
270272

271273
* `inputFrom(String jq)` – slice workflow data using JQ.

0 commit comments

Comments
 (0)