go.uber.org/cadence@v1.2.9/workflow/doc.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  /*
    22  Package workflow contains functions and types used to implement Cadence workflows.
    23  
    24  A workflow is an implementation of coordination logic. The Cadence programming framework (aka client library) allows
    25  you to write the workflow coordination logic as simple procedural code that uses standard Go data modeling. The client
    26  library takes care of the communication between the worker service and the Cadence service, and ensures state
    27  persistence between events even in case of worker failures. Any particular execution is not tied to a
    28  particular worker machine. Different steps of the coordination logic can end up executing on different worker
    29  instances, with the framework ensuring that necessary state is recreated on the worker executing the step.
    30  
    31  In order to facilitate this operational model both the Cadence programming framework and the managed service impose
    32  some requirements and restrictions on the implementation of the coordination logic. The details of these requirements
    33  and restrictions are described in the "Implementation" section below.
    34  
    35  # Overview
    36  
    37  The sample code below shows a simple implementation of a workflow that executes one activity. The workflow also passes
    38  the sole parameter it receives as part of its initialization as a parameter to the activity.
    39  
    40  	package sample
    41  
    42  	import (
    43  		"time"
    44  
    45  		"go.uber.org/cadence/workflow"
    46  	)
    47  
    48  	func init() {
    49  		workflow.Register(SimpleWorkflow)
    50  	}
    51  
    52  	func SimpleWorkflow(ctx workflow.Context, value string) error {
    53  		ao := workflow.ActivityOptions{
    54  			TaskList:               "sampleTaskList",
    55  			ScheduleToCloseTimeout: time.Second * 60,
    56  			ScheduleToStartTimeout: time.Second * 60,
    57  			StartToCloseTimeout:    time.Second * 60,
    58  			HeartbeatTimeout:       time.Second * 10,
    59  			WaitForCancellation:    false,
    60  		}
    61  		ctx = workflow.WithActivityOptions(ctx, ao)
    62  
    63  		future := workflow.ExecuteActivity(ctx, SimpleActivity, value)
    64  		var result string
    65  		if err := future.Get(ctx, &result); err != nil {
    66  			return err
    67  		}
    68  		workflow.GetLogger(ctx).Info(“Done”, zap.String(“result”, result))
    69  		return nil
    70  	}
    71  
    72  The following sections describe what is going on in the above code.
    73  
    74  # Declaration
    75  
    76  In the Cadence programing model a workflow is implemented with a function. The function declaration specifies the
    77  parameters the workflow accepts as well as any values it might return.
    78  
    79  	func SimpleWorkflow(ctx workflow.Context, value string) error
    80  
    81  The first parameter to the function is ctx workflow.Context. This is a required parameter for all workflow functions
    82  and is used by the Cadence client library to pass execution context. Virtually all the client library functions that
    83  are callable from the workflow functions require this ctx parameter. This **context** parameter is the same concept as
    84  the standard context.Context provided by Go. The only difference between workflow.Context and context.Context is that
    85  the Done() function in workflow.Context returns workflow.Channel instead of the standard go chan.
    86  
    87  The second string parameter is a custom workflow parameter that can be used to pass in data into the workflow on start.
    88  A workflow can have one or more such parameters. All parameters to an workflow function must be serializable, which
    89  essentially means that params can’t be channels, functions, variadic, or unsafe pointer.
    90  
    91  Since it only declares error as the return value it means that the workflow does not return a value. The error return
    92  value is used to indicate an error was encountered during execution and the workflow should be terminated.
    93  
    94  # Implementation
    95  
    96  In order to support the synchronous and sequential programming model for the workflow implementation there are certain
    97  restrictions and requirements on how the workflow implementation must behave in order to guarantee correctness. The
    98  requirements are that:
    99  
   100    - Execution must be deterministic
   101    - Execution must be idempotent
   102  
   103  A simplistic way to think about these requirements is that the workflow code:
   104  
   105    - Can only read and manipulate local state or state received as return values
   106      from Cadence client library functions
   107    - Should really not affect changes in external systems other than through
   108      invocation of activities
   109    - Should interact with time only through the functions provided by the
   110      Cadence client library (i.e. workflow.Now(), workflow.Sleep())
   111    - Should not create and interact with goroutines directly, it should instead
   112      use the functions provided by the Cadence client library. (i.e.
   113      workflow.Go() instead of go, workflow.Channel instead of chan,
   114      workflow.Selector instead of select)
   115    - Should do all logging via the logger provided by the Cadence client
   116      library (i.e. workflow.GetLogger())
   117    - Should not iterate over maps using range as order of map iteration is
   118      randomized
   119  
   120  Now that we laid out the ground rules we can take a look at how to implement some common patterns inside workflows.
   121  
   122  # Special Cadence client library functions and types
   123  
   124  The Cadence client library provides a number of functions and types as alternatives to some native Go functions and
   125  types. Usage of these replacement functions/types is necessary in order to ensure that the workflow code execution is
   126  deterministic and repeatable within an execution context.
   127  
   128  Coroutine related constructs:
   129  
   130    - workflow.Go : This is a replacement for the the go statement
   131    - workflow.Channel : This is a replacement for the native chan type. Cadence
   132      provides support for both buffered and unbuffered channels
   133    - workflow.Selector : This is a replacement for the select statement
   134  
   135  Time related functions:
   136  
   137    - workflow.Now() : This is a replacement for time.Now()
   138    - workflow.Sleep() : This is a replacement for time.Sleep()
   139  
   140  # Failing a Workflow
   141  
   142  To mark a workflow as failed, return an error from your workflow function via the err return value.
   143  Note that failed workflows do not record the non-error return's value: you cannot usefully return both a
   144  value and an error, only the error will be recorded.
   145  
   146  # Ending a Workflow externally
   147  
   148  Inside a workflow, to end you must finish your function by returning a result or error.
   149  
   150  Externally, two tools exist to stop workflows from outside the workflow itself, by using the CLI or RPC client:
   151  cancellation and termination.  Termination is forceful, cancellation allows a workflow to exit gracefully.
   152  
   153  Workflows can also time out, based on their ExecutionStartToClose duration.  A timeout behaves the same as
   154  termination (it is a hard deadline on the workflow), but a different close status and final event will be reported.
   155  
   156  # Terminating a Workflow
   157  
   158  Terminating is roughly equivalent to using `kill -9` on a process - the workflow will be ended immediately,
   159  and no further decisions will be made.  It cannot be prevented or delayed by the workflow, or by any configuration.
   160  Any in-progress decisions or activities will fail whenever they next communicate with Cadence's servers, i.e. when
   161  they complete or when they next heartbeat.
   162  
   163  Because termination does not allow for any further code to be run, this also means your workflow has no
   164  chance to clean up after itself (e.g. running a cleanup Activity to adjust a database record).
   165  If you need to run additional logic when your workflow, use cancellation instead.
   166  
   167  # Canceling a Workflow
   168  
   169  Canceling marks a workflow as canceled (this is a one-time, one-way operation), and immediately wakes the workflow
   170  up to process the cancellation (schedules a new decision task).  When the workflow resumes after being canceled,
   171  the context that was passed into the workflow (and thus all derived contexts) will be canceled, which changes the
   172  behavior of many workflow.* functions.
   173  
   174  # Canceled workflow.Context behavior
   175  
   176  A workflow's context can be canceled by either canceling the workflow, or calling the cancel-func returned from
   177  a worfklow.WithCancel(ctx) call.  Both behave identically.
   178  
   179  At any time, you can convert a canceled (or could-be-canceled) context into a non-canceled context by using
   180  workflow.NewDisconnectedContext.  The resulting context will ignore cancellation from the context it is derived from.
   181  Disconnected contexts like this can be created before or after a context has been canceled, and it does not matter
   182  how the cancellation occurred.
   183  Because this context will not be canceled, this can be useful for using context cancellation as a way to request that
   184  some behavior be shut down, while allowing you to run cleanup logic in activities or elsewhere.
   185  
   186  As a general guideline, doing anything with I/O with a canceled context (e.g. executing an activity, starting a
   187  child workflow, sleeping) will fail rather than cause external changes.  Detailed descriptions are available in
   188  documentation on functions that change their behavior with a canceled context; if it does not mention canceled-context
   189  behavior, its behavior does not change.
   190  For exact behavior, make sure to read the documentation on functions that you are calling.
   191  
   192  As an incomplete summary, these actions will all fail immediately, and the associated error returns (possibly within
   193  a Future) will be a workflow.CanceledError:
   194  
   195    - workflow.Await
   196    - workflow.Sleep
   197    - workflow.Timer
   198  
   199  Child workflows will:
   200  
   201    - ExecuteChildWorkflow will synchronously fail with a CanceledError if canceled before it is called
   202      (in v0.18.4 and newer.  See https://github.com/uber-go/cadence-client/pull/1138 for details.)
   203    - be canceled if the child workflow is running
   204    - wait to complete their future.Get until the child returns, and the future will contain the final result
   205      (which may be anything that was returned, not necessarily a CanceledError)
   206  
   207  Activities have configurable cancellation behavior.  For workflow.ExecuteActivity and workflow.ExecuteLocalActivity,
   208  see the activity package's documentation for details.  In summary though:
   209  
   210    - ExecuteActivity will synchronously fail with a CanceledError if canceled before it is called
   211    - the activity's future.Get will by default return a CanceledError immediately when canceled,
   212      unless activityoptions.WaitForCancellation is true
   213    - the activity's context will be canceled at the next heartbeat event, or not at all if that does not occur
   214  
   215  And actions like this will be completely unaffected:
   216  
   217    - future.Get
   218      (futures derived from the calls above may return a CanceledError, but this is not guaranteed for all futures)
   219    - selector.Select
   220      (Select is completely unaffected, similar to a native select statement.  if you wish to unblock when your
   221      context is canceled, consider using an AddReceive with the context's Done() channel, as with a native select)
   222    - channel.Send, channel.Receive, and channel.ReceiveAsync
   223      (similar to native chan read/write operations, use a selector to wait for send/receive or some other action)
   224    - workflow.Go
   225      (the context argument in the callback is derived and may be canceled, but this does not stop the goroutine,
   226      nor stop new ones from being started)
   227    - workflow.GetVersion, workflow.GetLogger, workflow.GetMetricsScope, workflow.Now, many others
   228  
   229  # Execute Activity
   230  
   231  The primary responsibility of the workflow implementation is to schedule activities for execution. The most
   232  straightforward way to do that is via the library method workflow.ExecuteActivity:
   233  
   234  	ao := workflow.ActivityOptions{
   235  		TaskList:               "sampleTaskList",
   236  		ScheduleToCloseTimeout: time.Second * 60,
   237  		ScheduleToStartTimeout: time.Second * 60,
   238  		StartToCloseTimeout:    time.Second * 60,
   239  		HeartbeatTimeout:       time.Second * 10,
   240  		WaitForCancellation:    false,
   241  	}
   242  	ctx = workflow.WithActivityOptions(ctx, ao)
   243  
   244  	future := workflow.ExecuteActivity(ctx, SimpleActivity, value)
   245  	var result string
   246  	if err := future.Get(ctx, &result); err != nil {
   247  		return err
   248  	}
   249  
   250  Before calling workflow.ExecuteActivity(), ActivityOptions must be configured for the invocation. These are for the
   251  most part options to customize various execution timeouts. These options are passed in by creating a child context from
   252  the initial context and overwriting the desired values. The child context is then passed into the
   253  workflow.ExecuteActivity() call. If multiple activities are sharing the same exact option values then the same context
   254  instance can be used when calling workflow.ExecuteActivity().
   255  
   256  The first parameter to the call is the required workflow.Context object. This type is an exact copy of context.Context
   257  with the Done() method returning workflow.Channel instead of native go chan.
   258  
   259  The second parameter is the function that we registered as an activity function. This parameter can also be the a
   260  string representing the fully qualified name of the activity function. The benefit of passing in the actual function
   261  object is that in that case the framework can validate activity parameters.
   262  
   263  The remaining parameters are the parameters to pass to the activity as part of the call. In our example we have a
   264  single parameter: **value**. This list of parameters must match the list of parameters declared by the activity
   265  function. Like mentioned above the Cadence client library will validate that this is indeed the case.
   266  
   267  The method call returns immediately and returns a workflow.Future. This allows for more code to be executed without
   268  having to wait for the scheduled activity to complete.
   269  
   270  When we are ready to process the results of the activity we call the Get() method on the future object returned. The
   271  parameters to this method are the ctx object we passed to the workflow.ExecuteActivity() call and an output parameter
   272  that will receive the output of the activity. The type of the output parameter must match the type of the return value
   273  declared by the activity function. The Get() method will block until the activity completes and results are available.
   274  
   275  The result value returned by workflow.ExecuteActivity() can be retrieved from the future and used like any normal
   276  result from a synchronous function call. If the result above is a string value we could use it as follows:
   277  
   278  	var result string
   279  	if err := future.Get(ctx1, &result); err != nil {
   280  		return err
   281  	}
   282  
   283  	switch result {
   284  	case “apple”:
   285  		// do something
   286  	case “bannana”:
   287  		// do something
   288  	default:
   289  		return err
   290  	}
   291  
   292  In the example above we called the Get() method on the returned future immediately after workflow.ExecuteActivity().
   293  However, this is not necessary. If we wish to execute multiple activities in parallel we can repeatedly call
   294  workflow.ExecuteActivity() store the futures returned and then wait for all activities to complete by calling the
   295  Get() methods of the future at a later time.
   296  
   297  To implement more complex wait conditions on the returned future objects, use the workflow.Selector class. Take a look
   298  at our Pickfirst sample for an example of how to use of workflow.Selector.
   299  
   300  # Child Workflow
   301  
   302  workflow.ExecuteChildWorkflow enables the scheduling of other workflows from within a workflow's implementation. The
   303  parent workflow has the ability to "monitor" and impact the life-cycle of the child workflow in a similar way it can do
   304  for an activity it invoked.
   305  
   306  	cwo := workflow.ChildWorkflowOptions{
   307  		// Do not specify WorkflowID if you want cadence to generate a unique ID for child execution
   308  		WorkflowID:                   "BID-SIMPLE-CHILD-WORKFLOW",
   309  		ExecutionStartToCloseTimeout: time.Minute * 30,
   310  	}
   311  	childCtx = workflow.WithChildOptions(ctx, cwo)
   312  
   313  	var result string
   314  	future := workflow.ExecuteChildWorkflow(childCtx, SimpleChildWorkflow, value)
   315  	if err := future.Get(ctx, &result); err != nil {
   316  		workflow.GetLogger(ctx).Error("SimpleChildWorkflow failed.", zap.Error(err))
   317  		return err
   318  	}
   319  
   320  Before calling workflow.ExecuteChildWorkflow(), ChildWorkflowOptions must be configured for the invocation. These are
   321  for the most part options to customize various execution timeouts. These options are passed in by creating a child
   322  context from the initial context and overwriting the desired values. The child context is then passed into the
   323  workflow.ExecuteChildWorkflow() call. If multiple activities are sharing the same exact option values then the same
   324  context instance can be used when calling workflow.ExecuteChildWorkflow().
   325  
   326  The first parameter to the call is the required workflow.Context object. This type is an exact copy of context.Context
   327  with the Done() method returning workflow.Channel instead of the native go chan.
   328  
   329  The second parameter is the function that we registered as a workflow function. This parameter can also be a string
   330  representing the fully qualified name of the workflow function. What's the benefit? When you pass in the actual
   331  function object, the framework can validate workflow parameters.
   332  
   333  The remaining parameters are the parameters to pass to the workflow as part of the call. In our example we have a
   334  single parameter: value. This list of parameters must match the list of parameters declared by the workflow function.
   335  
   336  The method call returns immediately and returns a workflow.Future. This allows for more code to be executed without
   337  having to wait for the scheduled workflow to complete.
   338  
   339  When we are ready to process the results of the workflow we call the Get() method on the future object returned. The
   340  parameters to this method are the ctx object we passed to the workflow.ExecuteChildWorkflow() call and an output
   341  parameter that will receive the output of the workflow. The type of the output parameter must match the type of the
   342  return value declared by the workflow function. The Get() method will block until the workflow completes and results
   343  are available.
   344  
   345  The workflow.ExecuteChildWorkflow() function is very similar to the workflow.ExecuteActivity() function. All the
   346  patterns described for using the workflow.ExecuteActivity() apply to the workflow.ExecuteChildWorkflow() function as
   347  well.
   348  
   349  Child workflows can also be configured to continue to exist once their parent workflow is closed. When using this
   350  pattern, extra care needs to be taken to ensure the child workflow is started before the parent workflow finishes.
   351  
   352  	cwo := workflow.ChildWorkflowOptions{
   353  		// Do not specify WorkflowID if you want cadence to generate a unique ID for child execution
   354  		WorkflowID:                   "BID-SIMPLE-CHILD-WORKFLOW",
   355  		ExecutionStartToCloseTimeout: time.Minute * 30,
   356  
   357  		// Do not terminate when parent closes.
   358  		ParentClosePolicy: client.ParentClosePolicyAbandon,
   359  	}
   360  	childCtx = workflow.WithChildOptions(ctx, cwo)
   361  
   362  	future := workflow.ExecuteChildWorkflow(childCtx, SimpleChildWorkflow, value)
   363  
   364  	// Wait for the child workflow to start
   365  	if err := future.GetChildWorkflowExecution().Get(ctx, nil); err != nil {
   366  		// Problem starting workflow.
   367  		return err
   368  	}
   369  
   370  # Error Handling
   371  
   372  Activities and child workflows can fail. You could handle errors differently based on different error cases. If the
   373  activity returns an error as errors.New() or fmt.Errorf(), those errors will be converted to workflow.GenericError. If the
   374  activity returns an error as workflow.NewCustomError("err-reason", details), that error will be converted to
   375  *workflow.CustomError. There are other types of errors like workflow.TimeoutError, workflow.CanceledError and workflow.PanicError.
   376  So the error handling code would look like:
   377  
   378  	err := workflow.ExecuteActivity(ctx, YourActivityFunc).Get(ctx, nil)
   379  	switch err := err.(type) {
   380  	case *workflow.CustomError:
   381  		switch err.Reason() {
   382  		case "err-reason-a":
   383  			// handle error-reason-a
   384  			var details YourErrorDetailsType
   385  			err.Details(&details)
   386  			// deal with details
   387  		case "err-reason-b":
   388  			// handle error-reason-b
   389  		default:
   390  			// handle all other error reasons
   391  		}
   392  	case *workflow.GenericError:
   393  		switch err.Error() {
   394  		case "err-msg-1":
   395  			// handle error with message "err-msg-1"
   396  		case "err-msg-2":
   397  			// handle error with message "err-msg-2"
   398  		default:
   399  			// handle all other generic errors
   400  		}
   401  	case *workflow.TimeoutError:
   402  		switch err.TimeoutType() {
   403  		case shared.TimeoutTypeScheduleToStart:
   404  			// handle ScheduleToStart timeout
   405  		case shared.TimeoutTypeStartToClose:
   406  			// handle StartToClose timeout
   407  		case shared.TimeoutTypeHeartbeat:
   408  			// handle heartbeat timeout
   409  		default:
   410  		}
   411  	case *workflow.PanicError:
   412  		// handle panic error
   413  	case *workflow.CanceledError:
   414  		// handle canceled error
   415  	default:
   416  		// all other cases (ideally, this should not happen)
   417  	}
   418  
   419  # Signals
   420  
   421  Signals provide a mechanism to send data directly to a running workflow. Previously, you had two options for passing
   422  data to the workflow implementation:
   423  
   424    - Via start parameters
   425    - As return values from activities
   426  
   427  With start parameters, we could only pass in values before workflow execution begins.
   428  
   429  Return values from activities allowed us to pass information to a running workflow, but this approach comes with its
   430  own complications. One major drawback is reliance on polling. This means that the data needs to be stored in a
   431  third-party location until it's ready to be picked up by the activity. Further, the lifecycle of this activity requires
   432  management, and the activity requires manual restart if it fails before acquiring the data.
   433  
   434  Signals, on the other hand, provides a fully asynch and durable mechanism for providing data to a running workflow.
   435  When a signal is received for a running workflow, Cadence persists the event and the payload in the workflow history.
   436  The workflow can then process the signal at any time afterwards without the risk of losing the information. The
   437  workflow also has the option to stop execution by blocking on a signal channel.
   438  
   439  	var signalVal string
   440  	signalChan := workflow.GetSignalChannel(ctx, signalName)
   441  
   442  	s := workflow.NewSelector(ctx)
   443  	s.AddReceive(signalChan, func(c workflow.Channel, more bool) {
   444  		c.Receive(ctx, &signalVal)
   445  		workflow.GetLogger(ctx).Info("Received signal!", zap.String("signal", signalName), zap.String("value", signalVal))
   446  	})
   447  	s.Select(ctx)
   448  
   449  	if len(signalVal) > 0 && signalVal != "SOME_VALUE" {
   450  		return errors.New("signalVal")
   451  	}
   452  
   453  In the example above, the workflow code uses workflow.GetSignalChannel to open a workflow.Channel for the named signal.
   454  We then use a workflow.Selector to wait on this channel and process the payload received with the signal.
   455  
   456  # ContinueAsNew Workflow Completion
   457  
   458  Workflows that need to rerun periodically could naively be implemented as a big for loop with a sleep where the entire
   459  logic of the workflow is inside the body of the for loop. The problem with this approach is that the history for that
   460  workflow will keep growing to a point where it reaches the maximum size enforced by the service.
   461  
   462  ContinueAsNew is the low level construct that enables implementing such workflows without the risk of failures down the
   463  road. The operation atomically completes the current execution and starts a new execution of the workflow with the same
   464  workflow ID. The new execution will not carry over any history from the old execution. To trigger this behavior, the
   465  workflow function should terminate by returning the special ContinueAsNewError error:
   466  
   467  	func SimpleWorkflow(workflow.Context ctx, value string) error {
   468  	    ...
   469  	    return workflow.NewContinueAsNewError(ctx, SimpleWorkflow, value)
   470  	}
   471  
   472  For a complete example implementing this pattern please refer to the Cron example.
   473  
   474  # SideEffect API
   475  
   476  workflow.SideEffect executes the provided function once, records its result into the workflow history, and doesn't
   477  re-execute upon replay. Instead, it returns the recorded result. Use it only for short, nondeterministic code snippets,
   478  like getting a random value or generating a UUID. It can be seen as an "inline" activity. However, one thing to note
   479  about workflow.SideEffect is that whereas for activities Cadence guarantees "at-most-once" execution, no such guarantee
   480  exists for workflow.SideEffect. Under certain failure conditions, workflow.SideEffect can end up executing the function
   481  more than once.
   482  
   483  The only way to fail SideEffect is to panic, which causes decision task failure. The decision task after timeout is
   484  rescheduled and re-executed giving SideEffect another chance to succeed. Be careful to not return any data from the
   485  SideEffect function any other way than through its recorded return value.
   486  
   487  	encodedRandom := SideEffect(func(ctx workflow.Context) interface{} {
   488  		return rand.Intn(100)
   489  	})
   490  
   491  	var random int
   492  	encodedRandom.Get(&random)
   493  	if random < 50 {
   494  		....
   495  	} else {
   496  		....
   497  	}
   498  
   499  # Query API
   500  
   501  A workflow execution could be stuck at some state for longer than expected period. Cadence provide facilities to query
   502  the current call stack of a workflow execution. You can use cadence-cli to do the query, for example:
   503  
   504  	cadence-cli --domain samples-domain workflow query -w my_workflow_id -r my_run_id -qt __stack_trace
   505  
   506  The above cli command uses __stack_trace as the query type. The __stack_trace is a built-in query type that is
   507  supported by cadence client library. You can also add your own custom query types to support thing like query current
   508  state of the workflow, or query how many activities the workflow has completed. To do so, you need to setup your own
   509  query handler using workflow.SetQueryHandler in your workflow code:
   510  
   511  	func MyWorkflow(ctx workflow.Context, input string) error {
   512  	   currentState := "started" // this could be any serializable struct
   513  	   err := workflow.SetQueryHandler(ctx, "state", func() (string, error) {
   514  		 return currentState, nil
   515  	   })
   516  	   if err != nil {
   517  		 return err
   518  	   }
   519  	   // your normal workflow code begins here, and you update the currentState as the code makes progress.
   520  	   currentState = "waiting timer"
   521  	   err = NewTimer(ctx, time.Hour).Get(ctx, nil)
   522  	   if err != nil {
   523  		 currentState = "timer failed"
   524  		 return err
   525  	   }
   526  	   currentState = "waiting activity"
   527  	   ctx = WithActivityOptions(ctx, myActivityOptions)
   528  	   err = ExecuteActivity(ctx, MyActivity, "my_input").Get(ctx, nil)
   529  	   if err != nil {
   530  		 currentState = "activity failed"
   531  		 return err
   532  	   }
   533  	   currentState = "done"
   534  	   return nil
   535  	}
   536  
   537  The above sample code sets up a query handler to handle query type "state". With that, you should be able to query with
   538  cli:
   539  
   540  	cadence-cli --domain samples-domain workflow query -w my_workflow_id -r my_run_id -qt state
   541  
   542  Besides using cadence-cli, you can also issue query from code using QueryWorkflow() API on cadence Client object.
   543  
   544  # Registration
   545  
   546  For some client code to be able to invoke a workflow type, the worker process needs to be aware of all the
   547  implementations it has access to. A workflow is registered with the following call:
   548  
   549  	workflow.Register(SimpleWorkflow)
   550  
   551  This call essentially creates an in memory mapping inside the worker process between the fully qualified function name
   552  and the implementation. It is safe to call this registration method from an **init()** function. If the worker
   553  receives tasks for a workflow type it does not know it will fail that task. However, the failure of the task will not
   554  cause the entire workflow to fail.
   555  
   556  # Testing
   557  
   558  The Cadence client library provides a test framework to facilitate testing workflow implementations. The framework is
   559  suited for implementing unit tests as well as functional tests of the workflow logic.
   560  
   561  The code below implements the unit tests for the SimpleWorkflow sample.
   562  
   563  	package sample
   564  
   565  	import (
   566  		"errors"
   567  		"testing"
   568  
   569  		"code.uber.internal/devexp/cadence-worker/activity"
   570  
   571  		"github.com/stretchr/testify/mock"
   572  		"github.com/stretchr/testify/suite"
   573  
   574  		"go.uber.org/cadence/testsuite"
   575  	)
   576  
   577  	type UnitTestSuite struct {
   578  		suite.Suite
   579  		testsuite.WorkflowTestSuite
   580  
   581  		env *testsuite.TestWorkflowEnvironment
   582  	}
   583  
   584  	func (s *UnitTestSuite) SetupTest() {
   585  		s.env = s.NewTestWorkflowEnvironment()
   586  	}
   587  
   588  	func (s *UnitTestSuite) AfterTest(suiteName, testName string) {
   589  		s.env.AssertExpectations(s.T())
   590  	}
   591  
   592  	func (s *UnitTestSuite) Test_SimpleWorkflow_Success() {
   593  		s.env.ExecuteWorkflow(SimpleWorkflow, "test_success")
   594  
   595  		s.True(s.env.IsWorkflowCompleted())
   596  		s.NoError(s.env.GetWorkflowError())
   597  	}
   598  
   599  	func (s *UnitTestSuite) Test_SimpleWorkflow_ActivityParamCorrect() {
   600  		s.env.OnActivity(SimpleActivity, mock.Anything, mock.Anything).Return(func(ctx context.Context, value string) (string, error) {
   601  			s.Equal("test_success", value)
   602  			return value, nil
   603  		})
   604  		s.env.ExecuteWorkflow(SimpleWorkflow, "test_success")
   605  
   606  		s.True(s.env.IsWorkflowCompleted())
   607  		s.NoError(s.env.GetWorkflowError())
   608  	}
   609  
   610  	func (s *UnitTestSuite) Test_SimpleWorkflow_ActivityFails() {
   611  		s.env.OnActivity(SimpleActivity, mock.Anything, mock.Anything).Return("", errors.New("SimpleActivityFailure"))
   612  		s.env.ExecuteWorkflow(SimpleWorkflow, "test_failure")
   613  
   614  		s.True(s.env.IsWorkflowCompleted())
   615  
   616  		s.NotNil(s.env.GetWorkflowError())
   617  		_, ok := s.env.GetWorkflowError().(*workflow.GenericError)
   618  		s.True(ok)
   619  		s.Equal("SimpleActivityFailure", s.env.GetWorkflowError().Error())
   620  	}
   621  
   622  	func TestUnitTestSuite(t *testing.T) {
   623  		suite.Run(t, new(UnitTestSuite))
   624  	}
   625  
   626  # Setup
   627  
   628  First, we define a "test suite" struct that absorbs both the basic suite functionality from testify
   629  http://godoc.org/github.com/stretchr/testify/suite via suite.Suite and the suite functionality from the Cadence test
   630  framework via testsuite.WorkflowTestSuite. Since every test in this suite will test our workflow we add a property to
   631  our struct to hold an instance of the test environment. This will allow us to initialize the test environment in a
   632  setup method. For testing workflows we use a testsuite.TestWorkflowEnvironment.
   633  
   634  We then implement a SetupTest method to setup a new test environment before each test. Doing so ensure that each test
   635  runs in it's own isolated sandbox. We also implement an AfterTest function where we assert that all mocks we setup were
   636  indeed called by invoking s.env.AssertExpectations(s.T()).
   637  
   638  Finally, we create a regular test function recognized by "go test" and pass the struct to suite.Run.
   639  
   640  # A Simple Test
   641  
   642  The simplest test case we can write is to have the test environment execute the workflow and then evaluate the results.
   643  
   644  	func (s *UnitTestSuite) Test_SimpleWorkflow_Success() {
   645  		s.env.ExecuteWorkflow(SimpleWorkflow, "test_success")
   646  
   647  		s.True(s.env.IsWorkflowCompleted())
   648  		s.NoError(s.env.GetWorkflowError())
   649  	}
   650  
   651  Calling s.env.ExecuteWorkflow(...) will execute the workflow logic and any invoked activities inside the test process.
   652  The first parameter to s.env.ExecuteWorkflow(...) is the workflow functions and any subsequent parameters are values
   653  for custom input parameters declared by the workflow function. An important thing to note is that unless the activity
   654  invocations are mocked or activity implementation replaced (see next section), the test environment will execute the
   655  actual activity code including any calls to outside services.
   656  
   657  In the example above, after executing the workflow we assert that the workflow ran through to completion via the call
   658  to s.env.IsWorkflowComplete(). We also assert that no errors where returned by asserting on the return value of
   659  s.env.GetWorkflowError(). If our workflow returned a value, we we can retrieve that value via a call to
   660  s.env.GetWorkflowResult(&value) and add asserts on that value.
   661  
   662  # Activity Mocking and Overriding
   663  
   664  When testing workflows, especially unit testing workflows, we want to test the workflow logic in isolation.
   665  Additionally, we want to inject activity errors during our tests runs. The test framework provides two mechanisms that
   666  support these scenarios: activity mocking and activity overriding. Both these mechanisms allow you to change the
   667  behavior of activities invoked by your workflow without having to modify the actual workflow code.
   668  
   669  Lets first take a look at a test that simulates a test failing via the "activity mocking" mechanism.
   670  
   671  	func (s *UnitTestSuite) Test_SimpleWorkflow_ActivityFails() {
   672  		s.env.OnActivity(SimpleActivity, mock.Anything, mock.Anything).Return("", errors.New("SimpleActivityFailure"))
   673  		s.env.ExecuteWorkflow(SimpleWorkflow, "test_failure")
   674  
   675  		s.True(s.env.IsWorkflowCompleted())
   676  
   677  		s.NotNil(s.env.GetWorkflowError())
   678  		_, ok := s.env.GetWorkflowError().(*workflow.GenericError)
   679  		s.True(ok)
   680  		s.Equal("SimpleActivityFailure", s.env.GetWorkflowError().Error())
   681  	}
   682  
   683  In this test we want to simulate the execution of the activity SimpleActivity invoked by our workflow SimpleWorkflow
   684  returning an error. We do that by setting up a mock on the test environment for the SimpleActivity that returns an
   685  error.
   686  
   687  	s.env.OnActivity(SimpleActivity, mock.Anything, mock.Anything).Return("", errors.New("SimpleActivityFailure"))
   688  
   689  With the mock set up we can now execute the workflow via the s.env.ExecuteWorkflow(...) method and assert that the
   690  workflow completed successfully and returned the expected error.
   691  
   692  Simply mocking the execution to return a desired value or error is a pretty powerful mechanism to isolate workflow
   693  logic. However, sometimes we want to replace the activity with an alternate implementation to support a more complex
   694  test scenario. For our simple workflow lets assume we wanted to validate that the activity gets called with the
   695  correct parameters.
   696  
   697  	func (s *UnitTestSuite) Test_SimpleWorkflow_ActivityParamCorrect() {
   698  		s.env.OnActivity(SimpleActivity, mock.Anything, mock.Anything).Return(func(ctx context.Context, value string) (string, error) {
   699  			s.Equal("test_success", value)
   700  			return value, nil
   701  		})
   702  		s.env.ExecuteWorkflow(SimpleWorkflow, "test_success")
   703  
   704  		s.True(s.env.IsWorkflowCompleted())
   705  		s.NoError(s.env.GetWorkflowError())
   706  	}
   707  
   708  In this example, we provide a function implementation as the parameter to Return. This allows us to provide an
   709  alternate implementation for the activity SimpleActivity. The framework will execute this function whenever the
   710  activity is invoked and pass on the return value from the function as the result of the activity invocation.
   711  Additionally, the framework will validate that the signature of the "mock" function matches the signature of the
   712  original activity function.
   713  
   714  Since this can be an entire function, there really is no limitation as to what we can do in here. In this example, to
   715  assert that the "value" param has the same content to the value param we passed to the workflow.
   716  */
   717  package workflow