This is an alternative to the supplied EvaluateExpression
construct. EvaluateExpression
is intended to provide some sugar
over creating a lambda when defining step functions, by allowing a text representation of javascript code to be passed
(as expression
).
This has a number of disadvantages, such as lack of IDE syntax checking, and access to state input is done via a limited regular expression matching. There have been issues raised and pull requests created, but none have made their way into the CDK (in part because they only solve parts of the overall problem).
The existing implementation in the CDK uses eval()
to process the expression, and I think this futher hampered the design
decisions. A safer way to implement is to use the ability of Function
to create a callable function from a string, as this
isolates any variables in scope to those explictly passed (whereas eval
has access to all).
This allows us to inject variables for $
(state input) and $$
(step function context) and requires no manipulation of the
provided expression.
Further, we can provide the desired input parameters to the EvaluateExpression instantiation, which allows for customisation
of what variables are injected (still relative to $
).
As a bonus, in this implementation we can specify expression as a function, which means we can preserve IDE syntax checking. We must only be careful to avoid referencing variables from the CDK scope, but even if we did that would provoke a runtime error.
A typical use case is getting a TTL value for Dynamo DB items.
const getExpiryTime = new EvaluateExpression<MyNodeFunctionProps>(scope, 'Get Expiry Time', {
expression: 'Math.floor(new Date($$.Execution.StartTime).getTime() / 1000) + $.timeToLive',
functionClass: MyNodeFunction,
parameters: {
timeToLive: TimeToLive.toSeconds(),
},
resultPath: '$.expiresAt',
})
Here we use the ability to provide a function instead of a string expression. We keep syntax checking in our IDE, and we can add types, both to help document and enhance syntax checking. It also makes it easier for code to run over several lines for clarity.
const getExpiryTime = new EvaluateExpression<MyNodeFunctionProps>(scope, 'Get Expiry Time', {
// NB: $ is inferred from parameters (Record<string, any> when not supplied)
// $$ is Context (step function context, e.g. $$.Execution.Id etc)
expression: ($, $$) => {
const date = new Date($$.Execution.StartTime)
date.setSeconds(date.getSeconds() + $.timeToLive)
return Math.floor(date.getTime() / 1000)
},
functionClass: MyNodeFunction,
parameters: {
timeToLive: TimeToLive.toSeconds(),
},
resultPath: '$.expiresAt',
})
EvaluateExpression
is intended as a shortcut to implementating a lambda anytime you want to do some minor computation that
is beyond that provided by Step Function Intrinsics.
We must remember that we cannot import modules (even though they might be available in the runtime, such as the AWS SDK),
nor our own. If you find yourself needing to do this, that's when you could be taking the longer route to a full Lambda
implementation (via LambdaInvoke
etc).
Using EvaluateExpression
will add a Lambda to your stack, but only one is added for each runtime (so only one if you
stick to the default or only specify a particular runtime). It will have a log group created for it, as is normal for
Lambda, and will be named after the stack name (and stage if used), and the runtime (e.g.
"/aws/lambda/Dev-Replicated-SfnTasksEvaluateExpression-nodejs18x
")