Skip to content

Commit

Permalink
Add package overview documentation (#586)
Browse files Browse the repository at this point in the history
* Remove TODOs from documentation

* Separate paragraph for RecordHeatbeat details

* variadic is redundant with functions

* Transferring docs to package comments for GoDoc

* Added more to cadence overview
  • Loading branch information
dmetzgar authored and yiminc committed Oct 4, 2018
1 parent 8b32465 commit 16d6bae
Show file tree
Hide file tree
Showing 5 changed files with 910 additions and 31 deletions.
8 changes: 3 additions & 5 deletions activity/activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Package activity contains functions and types used to implement Cadence activities.
package activity

import (
"context"

"github.com/uber-go/tally"
"go.uber.org/cadence/internal"
"go.uber.org/zap"
Expand Down Expand Up @@ -64,7 +64,7 @@ func Register(activityFunc interface{}) {
// func sampleActivity() (result string, err error)
// func sampleActivity(arg1 bool) (result int, err error)
// func sampleActivity(arg1 bool) (err error)
// Serialization of all primitive types, structures is supported ... except channels, functions, variadic, unsafe pointer.
// Serialization of all primitive types, structures is supported ... except channels, functions, unsafe pointer.
// If function implementation returns activity.ErrResultPending then activity is not completed from the
// calling workflow point of view. See documentation of activity.ErrResultPending for more info.
// This method calls panic if activityFunc doesn't comply with the expected format.
Expand All @@ -90,9 +90,7 @@ func GetMetricsScope(ctx context.Context) tally.Scope {
// RecordHeartbeat sends heartbeat for the currently executing activity
// If the activity is either cancelled (or) workflow/activity doesn't exist then we would cancel
// the context with error context.Canceled.
// TODO: we don't have a way to distinguish between the two cases when context is cancelled because
// context doesn't support overriding value of ctx.Error.
// TODO: Implement automatic heartbeating with cancellation through ctx.
//
// details - the details that you provided here can be seen in the workflow when it receives TimeoutError, you
// can check error TimeOutType()/Details().
func RecordHeartbeat(ctx context.Context, details ...interface{}) {
Expand Down
194 changes: 194 additions & 0 deletions activity/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

/*
Package activity contains functions and types used to implement Cadence activities.
The activity is an implementation of a task to be performed as part of a larger workflow. There is no limitation of
what an activity can do. In the context of a workflow, it is in the activities where all operations that affect the
desired results must be implemented.
Overview
The client library for Cadence does all the heavy lifting of handling the async communication between the Cadence
managed service and the worker running the activity. As such, the implementation of the activity can, for the most
part, focus on the business logic. The sample code below shows the implementation of a simple activity that accepts a
string parameter, appends a word to it and then returns the result.
import (
"context"
"go.uber.org/cadence/activity"
"go.uber.org/zap"
)
func init() {
activity.Register(SimpleActivity)
}
func SimpleActivity(ctx context.Context, value string) (string, error) {
activity.GetLogger(ctx).Info("SimpleActivity called.", zap.String("Value", value))
return "Processed: ” + value, nil
}
The following sections explore the elements of the above code.
Declaration
In the Cadence programing model, an activity is implemented with a function. The function declaration specifies the
parameters the activity accepts as well as any values it might return. An activity function can take zero or many
activity specific parameters and can return one or two values. It must always at least return an error value. The
activity function can accept as parameters and return as results any serializable type.
func SimpleActivity(ctx context.Context, value string) (string, error)
The first parameter to the function is context.Context. This is an optional parameter and can be omitted. This
parameter is the standard Go context.
The second string parameter is a custom activity-specific parameter that can be used to pass in data into the activity
on start. An activity can have one or more such parameters. All parameters to an activity function must be
serializable, which essentially means that params can’t be channels, functions, variadic, or unsafe pointer.
The activity declares two return values: (string, error). The string return value is used to return the result of the
activity. The error return value is used to indicate an error was encountered during execution.
Implementation
There is nothing special about activity code. You can write activity implementation code the same way you would any
other Go service code. You can use the usual loggers and metrics collectors. You can use the standard Go concurrency
constructs.
Failing the activity
To mark an activity as failed, all that needs to happen is for the activity function to return an error via the error
return value.
Activity Heartbeating
For long running activities, Cadence provides an API for the activity code to report both liveness and progress back to
the Cadence managed service.
progress := 0
for hasWork {
// send heartbeat message to the server
activity.RecordActivityHeartbeat(ctx, progress)
// do some work
...
progress++
}
When the activity times out due to a missed heartbeat, the last value of the details (progress in the above sample) is
returned from the workflow.ExecuteActivity function as the details field of TimeoutError with TimeoutType_HEARTBEAT.
It is also possible to heartbeat an activity from an external source:
// instantiate a Cadence service Client
client.Client client = client.NewClient(...)
// record heartbeat
err := client.RecordActivityHeartbeat(taskToken, details)
The parameters of the RecordActivityHeartbeat function are:
- taskToken: This is the value of the binary “TaskToken” field of the
“ActivityInfo” struct retrieved inside the activity
- details: This is the serializable payload containing progress information
Activity Cancellation
When an activity is cancelled (or its workflow execution is completed or failed) the context passed into its function
is cancelled which sets its Done channel’s closed state. So an activity can use that to perform any necessary cleanup
and abort its execution. Currently cancellation is delivered only to activities that call RecordActivityHeartbeat.
Async/“Manual” Activity Completion
In certain scenarios completing an activity upon completion of its function is not possible or desirable.
One example would be the UberEATS order processing workflow that gets kicked off once an eater pushes the “Place Order”
button. Here is how that workflow could be implemented using Cadence and the “async activity completion”:
- Activity 1: send order to restaurant
- Activity 2: wait for restaurant to accept order
- Activity 3: schedule pickup of order
- Activity 4: wait for courier to pick up order
- Activity 5: send driver location updates to eater
- Activity 6: complete order
Activities 2 & 4 in the above flow require someone in the restaurant to push a button in the Uber app to complete the
activity. The activities could be implemented with some sort of polling mechanism. However, they can be implemented
much simpler and much less resource intensive as a Cadence activity that is completed asynchronously.
There are 2 parts to implementing an asynchronously completed activity. The first part is for the activity to provide
the information necessary to be able to be completed from an external system and notify the Cadence service that it is
waiting for that outside callback:
// retrieve activity information needed to complete activity asynchronously
activityInfo := activity.GetActivityInfo(ctx)
taskToken := activityInfo.TaskToken
// send the taskToken to external service that will complete the activity
...
// return from activity function indicating the Cadence should wait for an async completion message
return "", activity.ErrResultPending
The second part is then for the external service to call the Cadence service to complete the activity. To complete the
activity successfully you would do the following:
// instantiate a Cadence service Client
// the same client can be used complete or fail any number of activities
client.Client client = client.NewClient(...)
// complete the activity
client.CompleteActivity(taskToken, result, nil)
And here is how you would fail the activity:
// fail the activity
client.CompleteActivity(taskToken, nil, err)
The parameters of the CompleteActivity function are:
- taskToken: This is the value of the binary “TaskToken” field of the
“ActivityInfo” struct retrieved inside the activity.
- result: This is the return value that should be recorded for the activity.
The type of this value needs to match the type of the return value
declared by the activity function.
- err: The error code to return if the activity should terminate with an
error.
If error is not null the value of the result field is ignored.
For a full example of implementing this pattern see the Expense sample.
Registration
In order to for some workflow execution to be able to invoke an activity type, the worker process needs to be aware of
all the implementations it has access to. An activity is registered with the following call:
activity.RegisterActivity(SimpleActivity)
This call essentially creates an in-memory mapping inside the worker process between the fully qualified function name
and the implementation. Unlike in Amazon SWF, workflow and activity types are not registered with the managed service.
If the worker receives a request to start an activity execution for an activity type it does not know it will fail that
request.
*/
package activity
126 changes: 101 additions & 25 deletions cadence.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,107 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Package cadence and its subdirectories contain a Cadence client side framework.
//
// The Cadence service is a task orchestrator for your application’s tasks. Applications using Cadence can execute a logical flow of
// tasks, especially long-running business logic, asynchronously or synchronously. and can scale runtime on distributed
// systems without you, the service owner, worrying about infrastructure needs.
//
//A quick example illustrates its use case. Consider Uber Eats where Cadence manages the entire business flow from
// placing an order, accepting it, handling shopping cart processes (adding, updating, and calculating cart items),
// entering the order in a pipeline (for preparing food and coordinating delivery), to scheduling delivery as well as
// handling payments.
//
//Cadence consists of a programming framework (or client library) and a managed service (or backend). The framework
// enables developers to author and coordinate tasks in Go code.
//
// The root cadence package contains common data structures. The subpackages are:
//
// workflow - functions used to implement workflows
//
// activity - functions used to implement activities
//
// client - functions used to create Cadence service client used to start and monitor workflow executions.
//
// worker - functions used to create worker instance used to host workflow and activity code.
//
// testsuite - unit testing framework for activity and workflow testing.
/*
Package cadence and its subdirectories contain the Cadence client side framework.
The Cadence service is a task orchestrator for your application’s tasks. Applications using Cadence can execute a
logical flow of tasks, especially long-running business logic, asynchronously or synchronously. They can also scale at
runtime on distributed systems.
A quick example illustrates its use case. Consider Uber Eats where Cadence manages the entire business flow from
placing an order, accepting it, handling shopping cart processes (adding, updating, and calculating cart items),
entering the order in a pipeline (for preparing food and coordinating delivery), to scheduling delivery as well as
handling payments.
Cadence consists of a programming framework (or client library) and a managed service (or backend). The framework
enables developers to author and coordinate tasks in Go code.
The root cadence package contains common data structures. The subpackages are:
- workflow - functions used to implement workflows
- activity - functions used to implement activities
- client - functions used to create Cadence service client used to start and
monitor workflow executions.
- worker - functions used to create worker instance used to host workflow and
activity code.
- testsuite - unit testing framework for activity and workflow testing
How Cadence works
The Cadence hosted service brokers and persists events generated during workflow execution. Worker nodes owned and
operated by customers execute the coordination and task logic. To facilitate the implementation of worker nodes Cadence
provides a client-side library for the Go language.
In Cadence, you can code the logical flow of events separately as a workflow and code business logic as activities. The
workflow identifies the activities and sequences them, while an activity executes the logic.
Key Features
Dynamic workflow execution graphs - Determine the workflow execution graphs at runtime based on the data you are
processing. Cadence does not pre-compute the execution graphs at compile time or at workflow start time. Therefore, you
have the ability to write workflows that can dynamically adjust to the amount of data they are processing. If you need
to trigger 10 instances of an activity to efficiently process all the data in one run, but only 3 for a subsequent run,
you can do that.
Child Workflows - Orchestrate the execution of a workflow from within another workflow. Cadence will return the results
of the child workflow execution to the parent workflow upon completion of the child workflow. No polling is required in
the parent workflow to monitor status of the child workflow, making the process efficient and fault tolerant.
Durable Timers - Implement delayed execution of tasks in your workflows that are robust to worker failures. Cadence
provides two easy to use APIs, **workflow.Sleep** and **workflow.Timer**, for implementing time based events in your
workflows. Cadence ensures that the timer settings are persisted and the events are generated even if workers executing
the workflow crash.
Signals - Modify/influence the execution path of a running workflow by pushing additional data directly to the workflow
using a signal. Via the Signal facility, Cadence provides a mechanism to consume external events directly in workflow
code.
Task routing - Efficiently process large amounts of data using a Cadence workflow, by caching the data locally on a
worker and executing all activities meant to process that data on that same worker. Cadence enables you to choose the
worker you want to execute a certain activity by scheduling that activity execution in the worker's specific task-list.
Unique workflow ID enforcement - Use business entity IDs for your workflows and let Cadence ensure that only one
workflow is running for a particular entity at a time. Cadence implements an atomic "uniqueness check" and ensures that
no race conditions are possible that would result in multiple workflow executions for the same workflow ID. Therefore,
you can implement your code to attempt to start a workflow without checking if the ID is already in use, even in the
cases where only one active execution per workflow ID is desired.
Perpetual/ContinueAsNew workflows - Run periodic tasks as a single perpetually running workflow. With the
"ContinueAsNew" facility, Cadence allows you to leverage the "unique workflow ID enforcement" feature for periodic
workflows. Cadence will complete the current execution and start the new execution atomically, ensuring you get to
keep your workflow ID. By starting a new execution Cadence also ensures that workflow execution history does not grow
indefinitely for perpetual workflows.
At-most once activity execution - Execute non-idempotent activities as part of your workflows. Cadence will not
automatically retry activities on failure. For every activity execution Cadence will return a success result, a failure
result, or a timeout to the workflow code and let the workflow code determine how each one of those result types should
be handled.
Asynch Activity Completion - Incorporate human input or thrid-party service asynchronous callbacks into your workflows.
Cadence allows a workflow to pause execution on an activity and wait for an external actor to resume it with a
callback. During this pause the activity does not have any actively executing code, such as a polling loop, and is
merely an entry in the Cadence datastore. Therefore, the workflow is unaffected by any worker failures happening over
the duration of the pause.
Activity Heartbeating - Detect unexpected failures/crashes and track progress in long running activities early. By
configuring your activity to report progress periodically to the Cadence server, you can detect a crash that occurs 10
minutes into an hour-long activity execution much sooner, instead of waiting for the 60-minute execution timeout. The
recorded progress before the crash gives you sufficient information to determine whether to restart the activity from
the beginning or resume it from the point of failure.
Timeouts for activities and workflow executions - Protect against stuck and unresponsive activities and workflows with
appropriate timeout values. Cadence requires that timeout values are provided for every activity or workflow
invocation. There is no upper bound on the timeout values, so you can set timeouts that span days, weeks, or even
months.
Visibility - Get a list of all your active and/or completed workflow. Explore the execution history of a particular
workflow execution. Cadence provides a set of visibility APIs that allow you, the workflow owner, to monitor past and
current workflow executions.
Debuggability - Replay any workflow execution history locally under a debugger. The Cadence client library provides an
API to allow you to capture a stack trace from any failed workflow execution history.
*/
package cadence

import "go.uber.org/cadence/internal"
Expand Down
Loading

0 comments on commit 16d6bae

Please sign in to comment.