-
Notifications
You must be signed in to change notification settings - Fork 17.4k
Go-SDK: Support TaskFlow #68311
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Go-SDK: Support TaskFlow #68311
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,7 +60,48 @@ func main() { | |
| } | ||
| } | ||
|
|
||
| func extract(ctx context.Context, client sdk.Client, log *slog.Logger) (any, error) { | ||
| // ExtractResult is extract's return value. Returning it from the task pushes it | ||
| // as the task's return_value XCom, ready for a downstream task to pull. | ||
| type ExtractResult struct { | ||
| GoVersion string `json:"go_version"` | ||
| Timestamp int64 `json:"timestamp"` | ||
| } | ||
|
|
||
| // ExtractInput declares extract's XCom inputs. The `xcom:"python_task_1"` tag | ||
| // binds this field to the return_value of the upstream Python task | ||
| // `python_task_1`, so the Go task receives a Python task's output without | ||
| // calling client.GetXCom itself. The runtime pulls and decodes it before | ||
| // extract runs. | ||
| type ExtractInput struct { | ||
| FromPython string `xcom:"python_task_1"` | ||
| } | ||
|
|
||
| // TransformInput binds transform's only XCom input to extract's return_value. | ||
| // Because the field is a dedicated struct, decoding is strict: a renamed or | ||
| // unexpected key fails the task instead of silently leaving fields zero. To | ||
| // decode loosely (e.g. for an evolving or cross-language producer), type the | ||
| // field map[string]any instead. | ||
| type TransformInput struct { | ||
| Extracted ExtractResult `xcom:"extract"` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am very, very unsure about the This feels like a shadow way of defining a dag. Additionally, while accepting a struct is helpful if a task takes many inputs, I would much rather we were able to define the go task function as this like this: func extract(
ctx context.Context,
extracted ExtractResult) {
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just duplicated our discussion here for further reviewer reference.
For current stage with the above example, the both Java or Go side still need to define corresponding argument with upstream Defining edges on Lang-SDK side means no-thing at this moment (we're not supporting defining whole Dag in Lang-SDK in this release, but will in the next release) type TransformInput struct {
country string `xcom:transform_upstream_1`
input any `xcom:transform_upstream_2`
}
func transform(
ctx context.Context,
transformed TransformInput
) {
...
}Since we will respect the Dag structure from Python side. Java-SDK TaskFlow is able to define the upstream task_id reference inline like: public long transformValue(Client client, @Builder.XCom(task = "extract") long extracted) {
}This is why I choose |
||
| } | ||
|
|
||
| // TransformResult is transform's return value. | ||
| type TransformResult struct { | ||
| Variable string `json:"variable"` | ||
| Extracted ExtractResult `json:"extracted"` | ||
| } | ||
|
Comment on lines
+88
to
+92
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the return type, user should define the all the fields with |
||
|
|
||
| // LoadInput binds load's parameter to transform's return_value. | ||
| type LoadInput struct { | ||
| Transformed TransformResult `xcom:"transform"` | ||
| } | ||
|
|
||
| func extract( | ||
| ctx context.Context, | ||
| client sdk.Client, | ||
| log *slog.Logger, | ||
| in ExtractInput, | ||
| ) (ExtractResult, error) { | ||
| log.Info("Hello from task") | ||
|
|
||
| // Log every field the runtime context exposes. The fields are namespaced | ||
|
|
@@ -105,37 +146,56 @@ func extract(ctx context.Context, client sdk.Client, log *slog.Logger) (any, err | |
| // Once per loop,.check if we've been asked to cancel! | ||
| select { | ||
| case <-ctx.Done(): | ||
| return nil, ctx.Err() | ||
| return ExtractResult{}, ctx.Err() | ||
| default: | ||
| } | ||
| log.Info("After the beep the time will be", "time", time.Now()) | ||
| time.Sleep(2 * time.Second) | ||
| } | ||
| log.Info("Goodbye from task") | ||
|
|
||
| ret := map[string]any{ | ||
| "go_version": runtime.Version(), | ||
| "timestamp": time.Now().UnixNano(), | ||
| } | ||
|
|
||
| return ret, nil | ||
| return ExtractResult{ | ||
| GoVersion: runtime.Version(), | ||
| Timestamp: time.Now().UnixNano(), | ||
| }, nil | ||
| } | ||
|
|
||
| func transform(ctx context.Context, client sdk.VariableClient, log *slog.Logger) error { | ||
| // This function takes a VariableClient and not a Client to make unit testing it easier. See | ||
| // `./main_test.go` for an example unit of this task fn. Functionally taking a `sdk.Client` is the same (as | ||
| // Client includes VariableClient) but by using the dedicated type it can be easier to write unit tests. | ||
| // | ||
| // It also gives a better indication of what features the tasks use | ||
| func transform( | ||
| ctx context.Context, | ||
| client sdk.VariableClient, | ||
| log *slog.Logger, | ||
| in TransformInput, | ||
| ) (TransformResult, error) { | ||
| // `in.Extracted` is the Taskflow-injected return value of the upstream | ||
| // `extract` task. The explicit client-pull pattern still works alongside | ||
| // it: here we also read a Variable directly. transform takes a | ||
| // VariableClient (not the full sdk.Client) to make unit testing easier -- | ||
| // see `./main_test.go`. | ||
| // Note: avoid an attribute literally named "timestamp" — it is a reserved | ||
| // field in the structured log records the coordinator sends to the | ||
| // supervisor (which parses it as an RFC3339 datetime). | ||
| log.Info("Got upstream XCom from 'extract'", | ||
| "go_version", in.Extracted.GoVersion, | ||
| "extracted_timestamp", in.Extracted.Timestamp, | ||
| ) | ||
|
|
||
| key := "my_variable" | ||
| val, err := client.GetVariable(ctx, key) | ||
| if err != nil { | ||
| return err | ||
| return TransformResult{}, err | ||
| } | ||
| log.Info("Obtained variable", key, val) | ||
| return nil | ||
|
|
||
| return TransformResult{Variable: val, Extracted: in.Extracted}, nil | ||
| } | ||
|
|
||
| func load() error { | ||
| func load(log *slog.Logger, in LoadInput) error { | ||
| log.Info( | ||
| "Got upstream XCom from 'transform'", | ||
| "variable", | ||
| in.Transformed.Variable, | ||
| "extracted", | ||
| in.Transformed.Extracted, | ||
| ) | ||
| return fmt.Errorf("Please fail") | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No matter how many upstream tasks there are, we will only inject all the upstream XCom result as one struct.
User should define all the upstream
task_idin thexcom:<task_id>(orxcom:<task_id>,<key>if the they are not using defaultreturn_valuekey) tag.Other directions I had consider:
AddTasklevel -> This will be ambiguous for user IMO as we still need to define the Dag structure at Python side at this stage. If we make the interface likeAddTask(<task_id>, <other task function handle> ... <or task id>). User will be confuse about: Are we defining the edge in Go slide instead of Python side.