Overview
Messages crossing process boundaries must be serialized. GoAkt v4 ships three pluggable serializers and supports custom implementations via theSerializer interface.
- ProtoSerializer: Wire format: protobuf (default). Best for:
proto.Messagetypes: schema-driven, compact, language-portable. - CBORSerializer: Wire format: CBOR (binary). Best for: Plain Go structs: compact binary, faster than JSON, schema-less.
- JSONSerializer: Wire format: JSON (via sonic). Best for: Plain Go structs where interoperability or human-readability is wanted.
ProtoSerializer (default)
Protobuf messages use the default serializer automatically. No registration needed forproto.Message types:
NewConfig registers ProtoSerializer for the proto.Message interface, and proto’s own protoregistry.GlobalTypes
(populated by generated init() functions) resolves the concrete type on the receiver.
CBOR for plain Go structs
For plain Go structs, useremote.WithSerializers(new(MyMessage), remote.NewCBORSerializer()) when creating the remote
config. Types are registered automatically in the type registry. Both sender and receiver must register the same types
via WithSerializers; for receive-only types, register them the same way: the type is auto-registered for
deserialization.
For bulk registration, remote.WithSerializables(new(MyMessage), new(OtherMessage)) registers each type with a shared
CBOR serializer instance.
JSON for plain Go structs
JSON works identically to CBOR: same registration path, same wire frame, different encoding.JSON for plain Go structs
JSON for plain Go structs
JSONSerializer is backed by bytedance/sonic configured for maximum throughput
(sonic.ConfigFastest: no HTML escaping, no JSON-marshaler validation). On amd64 and arm64 sonic uses JIT-accelerated
fast paths; on other architectures it transparently falls back to encoding/json.
CBOR vs JSON
Pick CBOR when both ends are goAkt and you want the smallest payloads and fastest decode. Pick JSON when payloads need to be human-readable in logs, inspected withjq, or consumed by non-goAkt tooling. Both share the same
registry plumbing: switching is a one-line change in your config.
How Go types are magically serialized
When you pass a concrete Go type toWithSerializers with CBORSerializer or JSONSerializer, the type is
automatically registered in a global type registry. There is no separate registration step: no
RegisterSerializableTypes or similar.
One line does it all:
How Go types are magically serialized
-
On config build: When
WithSerializers(new(MyMessage), serializer)is applied, GoAkt detects that the serializer uses the registry (CBOR or JSON) and the type is a concrete non-proto struct. It registersMyMessagein a global type registry keyed by the type’s reflected name. - On serialize: When a message is sent, the serializer looks up the type name in the registry, encodes the value as CBOR or JSON, and prepends a self-describing frame (total length, type name length, type name, payload). The receiver can reconstruct the exact Go type from the type name.
- On deserialize: When bytes arrive, the frame header is parsed, the type name is extracted, and the registry is consulted to resolve the concrete Go type. A new instance is allocated and the payload is unmarshaled into it.
WithSerializers. For types you only receive (never send),
register them the same way: the type is auto-registered for deserialization. Proto message types are excluded from this
registry; they use protobuf’s own type resolution.
The Serializer interface
Custom serializers must implement:The Serializer interface
- Serialize: Encode a message into bytes. The encoding must be self-describing so the receiver can reconstruct the concrete type without out-of-band coordination.
- Deserialize: Decode bytes back into the original Go value. The dynamic type must match what was passed to Serialize.
WithSerializers(msgType, serializer) on the remote
config.