Skip to content

Commit

Permalink
Merge pull request #34 from cludden/cludden/codec
Browse files Browse the repository at this point in the history
feat: adds experimental codec server support
  • Loading branch information
cludden authored Jan 12, 2024
2 parents 771cd7b + 640d317 commit 8e5cffd
Show file tree
Hide file tree
Showing 15 changed files with 1,082 additions and 249 deletions.
101 changes: 100 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ A protoc plugin for generating typed Temporal clients and workers in Go from pro
- [CLI](#cli)
- [Test Client](#test-client)
- [Cross-Namespace (XNS)](#cross-namespace-xns)
- [Codec](#codec)
- [Documentation](#documentation)
- [License](#license)

Expand All @@ -36,7 +37,6 @@ Generated **Client** with:
- default `client.StartWorkflowOptions` and `client.UpdateWorkflowWithOptionsRequest`
- dynamic workflow ids, update ids, and search attributes via [Bloblang expressions](#bloblang-expressions)
- default timeouts, id reuse policies, retry policies, wait policies
- experimental [cross-namespace (xns)](#cross-namespace-xns) support


Generated **Worker** resources with:
Expand All @@ -54,6 +54,12 @@ Optional **CLI** with:
- typed flags for conventiently specifying workflow, query, and signal inputs


Generated [Cross-Namespace (XNS)](#cross-namespace-xns) helpers: **[Experimental]**
- with support for invoking a service's workflows, queries, signals, and updates from workflows in a different temporal namespace

Generated [Remote Codec Server](#codec) helpers **[Experimental]**


## Getting Started
1. Install [buf](https://docs.buf.build/installation)

Expand Down Expand Up @@ -472,6 +478,7 @@ via:
| cli-categories | `bool` | enables cli categories | `true` |
| cli-enabled | `bool` | enables cli generation | `false` |
| disable-workflow-input-rename | `bool` | disables renamed workflow input suffix | `false` |
| enable-codec | `bool` | enables [experimental codec-server support](#codec) | `false` |
| enable-patch-support | `bool` | enables experimental support for [protoc-gen-go-patch](https://github.com/alta/protopatch) | `false` |
| enable-xns | `bool` | enables [experimental cross-namespace support](#cross-namespace-xns) | `false` |
| workflow-update-enabled | `bool` | enables experimental workflow update | `false` |
Expand Down Expand Up @@ -648,6 +655,98 @@ The generated code includes resources that are compatible with the Temporal Go S
This plugin provides experimental support for cross-namespace and/or cross-cluster integration by enabling the `enable-xns` plugin option. When enabled, the plugin will generate an additional `path/to/generated/code/<package>xns` go package containing types, methods, and helpers for calling workflows, queries, signals, and updates from other Temporal workflows via activities. The activities use [heartbeating](https://docs.temporal.io/activities#activity-heartbeat) to maintain liveness for long-running workflows or updates, and their associated timeouts can be configured using the generated options helpers. For an example of xns integration, see the [example/external](./example/external/external.go) package.
## Codec
*__Experimental__*
Temporal's [default data converter](https://pkg.go.dev/go.temporal.io/sdk/converter#GetDefaultDataConverter) will serialize protobuf types using the `json/protobuf` encoding provided by the [ProtoJSONPayloadConverter](https://pkg.go.dev/go.temporal.io/sdk/converter#ProtoJSONPayloadConverter), which allows the Temporal UI to automatically decode the underlying payload and render it as JSON. If you'd prefer to take advantage of protobuf's binary format for smaller payloads, you can provide an alternative data converter to the Temporal client at initialization that prioritizes the [ProtoPayloadConverter](https://pkg.go.dev/go.temporal.io/sdk/converter#ProtoPayloadConverter) ahead of the `ProtoJSONPayloadConverter`. See below for an example.
If you choose to use `binary/protobuf` encoding, you'll lose the ability to view decoded payloads in the Temporal UI unless you configure the [Remote Codec Server](https://docs.temporal.io/dataconversion#codec-server) integration. This plugin can generate helpers that simplify the process of implementing a remote codec server for use with the Temporal UI to support conversion between `binary/protobuf` and `json/protobuf` or `json/plain` payload encodings. See below for a simple example. For a more advanced example that supports different codecs per namespace, cors, and authentication, see the [codec-server](https://github.com/temporalio/samples-go/blob/main/codec-server/codec-server/main.go) go sample.
**Example:** *custom data converter that uses `binary/protobuf` for encoding, but supports all of `binary/protobuf`, `json/protobuf`, and `json/plain` for decoding.*
```go
package main
import (
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/converter"
)
func main() {
client, _ := client.Dial(client.Options{
DataConverter: converter.NewCompositeDataConverter(
// Order is important here, as the first match (ProtoPayload in this case) will always
// be used for serialization, and both ProtoJsonPayload and ProtoPayload converters
// check for the same proto.Message interface.
// Deserialization is controlled by metadata, therefore both converters can deserialize
// corresponding data format (JSON or binary proto).
converter.NewNilPayloadConverter(),
converter.NewByteSlicePayloadConverter(),
converter.NewProtoPayloadConverter(),
converter.NewProtoJSONPayloadConverterWithOptions(converter.ProtoJSONPayloadConverterOptions{
AllowUnknownFields: true, // Prevent errors when the underlying protobuf payload has added fields
}),
converter.NewJSONPayloadConverter(),
),
})
}
```
**Example:** *basic Remote Codec Server implementation*
```go
package main
import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"syscall"
examplev1 "github.com/cludden/protoc-gen-go-temporal/gen/example/v1"
"github.com/cludden/protoc-gen-go-temporal/pkg/codec"
"github.com/cludden/protoc-gen-go-temporal/pkg/scheme"
"go.temporal.io/sdk/converter"
)
func main() {
// initialize codec handler using this plugin's `pkg/codec` and `pkg/scheme` packages
// along with the generated scheme helpers
handler := converter.NewPayloadCodecHTTPHandler(
codec.NewProtoJSONCodec(
scheme.New(
examplev1.WithExampleSchemeTypes(),
),
),
)
// initialize http server with codec handler
srv := &http.Server{
Addr: "0.0.0.0:8080",
Handler: handler,
}
// handle graceful shutdown
go func() {
shutdownCh := make(chan os.Signal, 1)
signal.Notify(shutdownCh, syscall.SIGINT, syscall.SIGTERM)
<-shutdownCh
if err := srv.Shutdown(context.Background()); err != nil {
log.Fatalf("error shutting down server: %v", err)
}
}()
// start remote codec server
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("server error: %v", err)
}
}
```
## Documentation
- [Generated code reference](./docs/generated.md)
Expand Down
2 changes: 1 addition & 1 deletion buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins:
opt: plugin=go,paths=source_relative
- plugin: go_temporal
out: gen
opt: paths=source_relative,cli-enabled=true,cli-categories=true,workflow-update-enabled=true,enable-patch-support=true,enable-xns=true
opt: paths=source_relative,cli-enabled=true,cli-categories=true,workflow-update-enabled=true,enable-patch-support=true,enable-xns=true,enable-codec=true
strategy: all
- plugin: doc
out: docs/api
Expand Down
42 changes: 42 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ package main

import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"

"github.com/cludden/protoc-gen-go-temporal/example/external"
examplev1 "github.com/cludden/protoc-gen-go-temporal/gen/example/v1"
"github.com/cludden/protoc-gen-go-temporal/gen/example/v1/examplev1xns"
"github.com/cludden/protoc-gen-go-temporal/pkg/codec"
"github.com/cludden/protoc-gen-go-temporal/pkg/scheme"
"github.com/urfave/cli/v2"
"go.temporal.io/sdk/activity"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/converter"
"go.temporal.io/sdk/worker"
"go.temporal.io/sdk/workflow"
logger "go.temporal.io/server/common/log"
Expand Down Expand Up @@ -157,6 +164,41 @@ func main() {
}
app.Commands = append(app.Commands, external)

app.Commands = append(app.Commands, &cli.Command{
Name: "codec",
Usage: "run remote codec server",
Action: func(cmd *cli.Context) error {
handler := converter.NewPayloadCodecHTTPHandler(
codec.NewProtoJSONCodec(
scheme.New(
examplev1.WithExampleSchemeTypes(),
examplev1.WithExternalSchemeTypes(),
),
),
)

srv := &http.Server{
Addr: "0.0.0.0:8080",
Handler: handler,
}

go func() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan

if err := srv.Shutdown(context.Background()); err != nil {
log.Fatalf("error shutting down server: %v", err)
}
}()

if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("server error: %v", err)
}
return nil
},
})

// run cli
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
Expand Down
22 changes: 21 additions & 1 deletion gen/example/v1/example_temporal.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion gen/example/v1/examplev1xns/example_xns_temporal.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 8e5cffd

Please sign in to comment.