Skip to main content

Managing Metadata

Before you can run a WfRun, you need to create your WfSpec's! This guide shows you how to do that.

You can manage Metadata Objects (WfSpec, TaskDef, ExternalEventDef, and UserTaskDef) either using lhctl or with a grpc client. This section details how to manage them using the SDK's and grpc clients.

Please note that, in LittleHorse, all metadata requests are idempotent. Additionally, if you make a metadata request to Put an object with the exact same specifications of the object that already exists, the API will return the OK grpc status. This is useful so that you can safely use CI/CD pipelines to manage metadata, or use a script that runs upon application startup to manage metadata, without having to catch ALREADY_EXISTS errors.

TaskDef

In general, the easiest way to register a TaskDef in LittleHorse is through a LHTaskWorker object or struct.

Let's say I have a properly-annotated Task Worker class:

class Greeter {
@LHTaskMethod("greet")
public String greeting(String name) {
return "Hello there, " + name;
}
}

You can use the LHTaskWorker to create the TaskDef as follows:

LHConfig config = ...;
LHTaskWorker worker = new LHTaskWorker(new Greeter(), "greeting", config);
worker.registerTaskDef();

You can get a TaskDef or delete it using the grpc client:

LittleHorseBlockingStub client = ...;

// Get a TaskDef
TaskDefId taskId = TaskDefId.newBuilder().setName("my-task").build();
TaskDef myTask = client.getTaskDef(taskId);

// Delete the task
client.deleteTaskDef(DeleteTaskDefRequest.newBuilder().setId(taskId).build());

WfSpec

In LittleHorse, the easiest way to deploy a WfSpec is using the Workflow class or struct provided by our Java, Go, and Python SDK's. The Workflow class takes in a WorkflowThread function reference that defines your WfSpec logic (this is covered in the Developing Workflows Documentation), and has a compile() method which returns a PutWfSpecRequest.

Like other metadata requests, the rpc PutWfSpec is idempotent. However, as described in our WfSpec Versioning docs, WfSpec objects have compound versioning that enforces certain compatibility rules between versions. In the PutWfSpecRequest, you have the option to set the allowed_updates field of the PutWfSpecRequest. There are three values:

  1. ALL_UPDATES: both breaking changes and minor revisions are accepted.
  2. MINOR_REVISION_UPDATES: breaking changes are rejected, but minor revisions are accepted.
  3. NO_UPDATES: the request will fail if the specified new WfSpec differs from the latest version.

You can execute the PutWfSpecRequest with a specific AllowedUpdateType as follows:

package io.littlehorse.quickstart;

import java.io.IOException;
import io.littlehorse.sdk.common.LHLibUtil;
import io.littlehorse.sdk.common.config.LHConfig;
import io.littlehorse.sdk.common.proto.LittleHorseGrpc.LittleHorseBlockingStub;
import io.littlehorse.sdk.wfsdk.Workflow;
import io.littlehorse.sdk.wfsdk.WorkflowThread;
import io.littlehorse.sdk.common.proto.AllowedUpdateType;
import io.littlehorse.sdk.common.proto.PutWfSpecRequest;
import io.littlehorse.sdk.common.proto.WfSpec;

public class Main {

private static void wfFunc(WorkflowThread wf) {
// The `greet` TaskDef must already exist
wf.execute("greet", "some-name");
}

public static void main(String[] args) throws IOException {
LHConfig config = new LHConfig();
LittleHorseBlockingStub client = config.getBlockingStub();

Workflow workflow = Workflow.newWorkflow("my-wfspec", Main::wfFunc);

// Only allow updates that do not change the API of the WfSpec
workflow.withUpdateType(AllowedUpdateType.MINOR_REVISION_UPDATES);

PutWfSpecRequest request = workflow.compileWorkflow();
WfSpec result = client.putWfSpec(request);

System.out.println(LHLibUtil.protoToJson(result));
}
}

You can get or delete a WfSpec as follows:

package io.littlehorse.quickstart;

import java.io.IOException;
import io.littlehorse.sdk.common.LHLibUtil;
import io.littlehorse.sdk.common.config.LHConfig;
import io.littlehorse.sdk.common.proto.LittleHorseGrpc.LittleHorseBlockingStub;
import io.littlehorse.sdk.common.proto.DeleteWfSpecRequest;
import io.littlehorse.sdk.common.proto.WfSpec;
import io.littlehorse.sdk.common.proto.WfSpecId;

public class Main {

public static void main(String[] args) throws IOException {
LHConfig config = new LHConfig();
LittleHorseBlockingStub client = config.getBlockingStub();

WfSpecId wfSpecId = WfSpecId.newBuilder()
.setName("my-wfspec")
.setMajorVersion(0) // Set to whichever major version you want
.setRevision(0) // Set to whichever revision you want
.build();

WfSpec wfSpec = client.getWfSpec(wfSpecId);
System.out.println(LHLibUtil.protoToJson(wfSpec));

// Delete the WfSpec
DeleteWfSpecRequest req = DeleteWfSpecRequest.newBuilder().setId(wfSpecId).build();
client.deleteWfSpec(req);
}
}

ExternalEventDef

As of now, the only field required to create an ExternalEventDef is the name of the ExternalEventDef.

You can create, get, and delete an ExternalEventDef as follows:

package io.littlehorse.quickstart;

import java.io.IOException;
import io.littlehorse.sdk.common.LHLibUtil;
import io.littlehorse.sdk.common.config.LHConfig;
import io.littlehorse.sdk.common.proto.LittleHorseGrpc.LittleHorseBlockingStub;
import io.littlehorse.sdk.common.proto.DeleteExternalEventDefRequest;
import io.littlehorse.sdk.common.proto.ExternalEventDef;
import io.littlehorse.sdk.common.proto.ExternalEventDefId;
import io.littlehorse.sdk.common.proto.PutExternalEventDefRequest;

public class Main {

public static void main(String[] args) throws IOException, InterruptedException {
LHConfig config = new LHConfig();
LittleHorseBlockingStub client = config.getBlockingStub();

PutExternalEventDefRequest request = PutExternalEventDefRequest.newBuilder()
.setName("my-external-event-def")
.build();

client.putExternalEventDef(request);

// Metadata requests in LittleHorse take 50-100 ms to propagate to the global
// store.
Thread.sleep(100);

// Retrieve the ExternalEventDef
ExternalEventDefId id = ExternalEventDefId.newBuilder()
.setName("my-external-event-def")
.build();
ExternalEventDef eventDef = client.getExternalEventDef(id);
System.out.println(LHLibUtil.protoToJson(eventDef));

// Delete the ExternalEventDef
DeleteExternalEventDefRequest deleteRequest = DeleteExternalEventDefRequest.newBuilder()
.setId(id)
.build();
client.deleteExternalEventDef(deleteRequest);
}
}

UserTaskDef

A UserTaskDef specifies the data that needs to be filled out in a form for a human to complete a UserTaskRun.

Note that a UserTaskDef is a versioned object (unlike a WfSpec, however, there is only a version number and no revision). As such, getting and deleting UserTaskDefs follows a similar pattern: we provide a GetLatestUserTaskDef rpc call which allows you to get the latest UserTaskDef with a given name.

The easiest way to create a UserTaskDef in Java is using the UserTaskSchema class. Note that it infers the schema of the UserTaskDef from our MyForm class using the UserTaskField annotation.

The below example shows you how to create a UserTaskDef, get it, and delete it.

package io.littlehorse.quickstart;

import java.io.IOException;
import io.littlehorse.sdk.common.LHLibUtil;
import io.littlehorse.sdk.common.config.LHConfig;
import io.littlehorse.sdk.common.proto.LittleHorseGrpc.LittleHorseBlockingStub;
import io.littlehorse.sdk.usertask.UserTaskSchema;
import io.littlehorse.sdk.usertask.annotations.UserTaskField;
import io.littlehorse.sdk.common.proto.DeleteUserTaskDefRequest;
import io.littlehorse.sdk.common.proto.PutUserTaskDefRequest;
import io.littlehorse.sdk.common.proto.UserTaskDef;
import io.littlehorse.sdk.common.proto.UserTaskDefId;

// This Java class defines our form for the UserTaskDef
class SomeForm {
@UserTaskField(
displayName = "Approved?",
description = "Reply 'true' if this is an acceptable request."
)
public boolean isApproved;

@UserTaskField(
displayName = "Explanation",
description = "Explain your answer",
required = false
)
public String explanation;
}


public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
LHConfig config = new LHConfig();
LittleHorseBlockingStub client = config.getBlockingStub();

String userTaskDefName = "my-user-task-def";

// Compile the above Java class into a UserTaskDef
UserTaskSchema userTask = new UserTaskSchema(new SomeForm(), userTaskDefName);
PutUserTaskDefRequest putRequest = userTask.compile();

// Register the UserTaskDef into LittleHorse
client.putUserTaskDef(putRequest);

// Get the UserTaskDef. Note that metadata creation takes 50-100ms to propagate
// through the LittleHorse cluster.
Thread.sleep(200);

UserTaskDefId id = UserTaskDefId.newBuilder()
.setName(userTaskDefName)
.setVersion(0)
.build();
UserTaskDef result = client.getUserTaskDef(id);
System.out.println(LHLibUtil.protoToJson(result));

// Delete the UserTaskDef
DeleteUserTaskDefRequest deleteRequest = DeleteUserTaskDefRequest.newBuilder()
.setId(id)
.build();

client.deleteUserTaskDef(deleteRequest);
}
}

WorkflowEventDef

You can create, get, and delete a WorkflowEventDef as follows:

package io.littlehorse.quickstart;

import java.io.IOException;
import io.littlehorse.sdk.common.LHLibUtil;
import io.littlehorse.sdk.common.config.LHConfig;
import io.littlehorse.sdk.common.proto.LittleHorseGrpc.LittleHorseBlockingStub;
import io.littlehorse.sdk.common.proto.DeleteWorkflowEventDefRequest;
import io.littlehorse.sdk.common.proto.PutWorkflowEventDefRequest;
import io.littlehorse.sdk.common.proto.VariableType;
import io.littlehorse.sdk.common.proto.WorkflowEventDef;
import io.littlehorse.sdk.common.proto.WorkflowEventDefId;

public class Main {

public static void main(String[] args) throws IOException, InterruptedException {
LHConfig config = new LHConfig();
LittleHorseBlockingStub client = config.getBlockingStub();

PutWorkflowEventDefRequest request = PutWorkflowEventDefRequest.newBuilder()
.setName("my-workflow-event-def")
.setType(VariableType.STR)
.build();


client.putWorkflowEventDef(request);

// Metadata requests in LittleHorse take 50-100 ms to propagate to the global
// store.
Thread.sleep(100);

// Retrieve the WorkflowEventDef
WorkflowEventDefId id = WorkflowEventDefId.newBuilder()
.setName("my-workflow-event-def")
.build();
WorkflowEventDef eventDef = client.getWorkflowEventDef(id);
System.out.println(LHLibUtil.protoToJson(eventDef));

// Delete the WorkflowEventDef
DeleteWorkflowEventDefRequest deleteRequest = DeleteWorkflowEventDefRequest.newBuilder()
.setId(id)
.build();
client.deleteWorkflowEventDef(deleteRequest);
}
}