Examples
The following examples show common patterns with the LittleHorse Quarkus extension. Full source code is available in the lh-quarkus repository.
Basic
A minimal workflow that executes two tasks: greetings and print.
@LHTask
public class GreetingsTask {
public static final String GREETINGS_TASK = "greetings";
public static final String PRINT_TASK = "print";
public static final String NAME_VARIABLE = "name";
public static final String GREETINGS_WORKFLOW = "greetings";
@LHWorkflow(GREETINGS_WORKFLOW)
public void greetingsWorkflow(WorkflowThread wf) {
WfRunVariable name = wf.declareStr(NAME_VARIABLE);
TaskNodeOutput message = wf.execute(GREETINGS_TASK, name);
wf.execute(PRINT_TASK, message);
}
@LHTaskMethod(GREETINGS_TASK)
public String greetings(String name) {
return "Hello %s".formatted(name);
}
@LHTaskMethod(PRINT_TASK)
public void print(String message) {
System.out.println(message);
}
}
Run the workflow:
lhctl run greetings name Anakin
User Tasks
Use @LHUserTaskForm to define a user task form. The extension registers it at runtime.
@LHUserTaskForm(ApproveForm.APPROVE_USER_TASK)
public class ApproveForm {
public static final String APPROVE_USER_TASK = "approve-user-task";
@UserTaskField(
displayName = "Approved?",
description = "Reply 'true' if this is an acceptable request.")
public boolean isApproved;
}
Run the workflow and complete the user task:
lhctl run execute-order-66 executor Anakin
lhctl execute userTaskRun <wfRunId> <userTaskGuid>
Structs
Use @LHStructDef to define a StructDef for structured data.
@LHStructDef("person")
public class Person {
private String firstName;
private String lastName;
public Person() {}
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
}
Use the struct in workflows and tasks:
@LHWorkflow("my-workflow")
public void workflow(WorkflowThread wf) {
WfRunVariable person = wf.declareStruct("person", Person.class).required();
wf.execute("my-task", person);
}
@LHTaskMethod("my-task")
public void myTask(Person person) {
System.out.println(person);
}
Child Workflow
Define parent and child workflows with the parent attribute of @LHWorkflow:
@LHTask
public class ChildWorkflow {
public static final String GREETINGS_TASK = "child-wf-greetings";
public static final String NAME_VARIABLE = "name";
public static final String PARENT_WF = "parent-wf";
public static final String CHILD_WF = "child-wf";
@LHWorkflow(PARENT_WF)
public void parent(WorkflowThread wf) {
wf.execute(GREETINGS_TASK, PARENT_WF, wf.declareStr(NAME_VARIABLE).asPublic());
}
@LHWorkflow(value = CHILD_WF, parent = PARENT_WF)
public void child(WorkflowThread wf) {
wf.execute(GREETINGS_TASK, CHILD_WF, wf.declareStr(NAME_VARIABLE).asInherited());
}
@LHTaskMethod(GREETINGS_TASK)
public void greetings(String wfName, String name) {
System.out.printf("Hello %s from %s %n", name, wfName);
}
}
Run parent and child:
lhctl run --wfRunId my-parent-wf parent-wf name Anakin
lhctl run --parentWfRunId my-parent-wf --wfRunId my-child-wf child-wf
Reactive
Use LittleHorseReactiveStub for reactive workflows:
@ApplicationScoped
public class ReactiveService {
private final LittleHorseReactiveStub reactiveStub;
public ReactiveService(LittleHorseReactiveStub reactiveStub) {
this.reactiveStub = reactiveStub;
}
public Uni<String> runWf(String id, String message) {
String wfRunId = StringUtils.isBlank(id)
? UUID.randomUUID().toString().replace("-", "") : id;
RunWfRequest request = RunWfRequest.newBuilder()
.setWfSpecName(ReactiveWorkflow.REACTIVE_WORKFLOW)
.putVariables(ReactiveWorkflow.MESSAGE_VARIABLE, LHLibUtil.objToVarVal(message))
.setId(wfRunId)
.build();
return reactiveStub
.runWf(request)
.map(wfRun -> AwaitWorkflowEventRequest.newBuilder()
.addEventDefIds(WorkflowEventDefId.newBuilder()
.setName(ReactiveWorkflow.NOTIFY_EVENT))
.setWfRunId(wfRun.getId())
.build())
.chain(reactiveStub::awaitWorkflowEvent)
.map(wfEvent -> wfEvent.getId().getWfRunId().getId());
}
}
REST Endpoint
Inject LittleHorseBlockingStub and call it from a REST resource:
@ApplicationScoped
public class GreetingsService {
private final LittleHorseBlockingStub blockingStub;
public GreetingsService(LittleHorseBlockingStub blockingStub) {
this.blockingStub = blockingStub;
}
public String runWf(String id, String name) {
String wfRunId = StringUtils.isBlank(id)
? UUID.randomUUID().toString().replace("-", "") : id;
RunWfRequest request = RunWfRequest.newBuilder()
.setWfSpecName(GreetingsWorkflow.GREETINGS_WORKFLOW)
.putVariables(GreetingsWorkflow.NAME_VARIABLE, LHLibUtil.objToVarVal(name))
.setId(wfRunId)
.build();
return blockingStub.runWf(request).getId().getId();
}
}
@Path("/hello")
public class GreetingsResource {
private final GreetingsService service;
public GreetingsResource(GreetingsService service) {
this.service = service;
}
@GET
public String hello(@QueryParam("name") String name, @QueryParam("id") String id) {
return service.runWf(id, name);
}
}
Call the endpoint:
http :8080/hello name=="Anakin"
http :8080/hello name=="Leia" id==my-id-1