codec

package
v0.23.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2025 License: MIT Imports: 2 Imported by: 0

README

Event Data Codec Registry

The codec registry provides a pluggable system for encoding and decoding event data across different transport mechanisms. This system allows domain-specific types to be encoded without double-encoding through generic JSON marshaling.

Overview

The codec registry enables:

  • Stable wire format: Consistent event data encoding across all transports
  • Domain-specific codecs: Custom encoding/decoding for complex types
  • Pluggable architecture: Easy to add new codecs for different data types
  • Fallback compatibility: Graceful fallback to JSON for unknown types
  • Transport agnostic: Works with HTTP, SSE, and future transport implementations

Key Components

Codec Interface
type Codec interface {
    Kind() string                        // Unique identifier for this codec
    Encode(any) (json.RawMessage, error) // Encode value to JSON raw message
    Decode(json.RawMessage) (any, error) // Decode JSON raw message to value
}
Registry
type Registry struct { /* ... */ }

// Create a new registry
registry := codec.NewRegistry()

// Register a codec
registry.Register(myCodec)

// Get a codec by kind
codec, found := registry.Get("my_kind")
Default Registry

A global registry is available for convenience:

// Register with the default registry
codec.Register(myCodec)

// Get from the default registry
codec, found := codec.Get("my_kind")

Usage

1. Define a Codec
type UserProfileCodec struct{}

func (c *UserProfileCodec) Kind() string {
    return "user_profile"
}

func (c *UserProfileCodec) Encode(v any) (json.RawMessage, error) {
    profile, ok := v.(*UserProfile)
    if !ok {
        return nil, fmt.Errorf("expected *UserProfile, got %T", v)
    }
    
    // Custom encoding logic (validation, transformation, etc.)
    return json.Marshal(profile)
}

func (c *UserProfileCodec) Decode(raw json.RawMessage) (any, error) {
    var profile UserProfile
    if err := json.Unmarshal(raw, &profile); err != nil {
        return nil, err
    }
    
    // Custom decoding logic (validation, transformation, etc.)
    return &profile, nil
}
2. Register the Codec
registry := codec.NewRegistry()
registry.Register(&UserProfileCodec{})

// Or use the default registry
codec.Register(&UserProfileCodec{})
3. Use with Events

To use a codec with events, add the codec kind to the event metadata:

event := &MyEvent{
    // ... other fields ...
    metadata: map[string]interface{}{
        "kind": "user_profile", // This tells the system which codec to use
    },
}

The system checks these metadata keys in order:

  1. kind
  2. codec_kind
  3. data_kind
4. Integration with HTTP Transport
// Create codec-aware encoder
registry := codec.NewRegistry()
registry.Register(&UserProfileCodec{})
encoder := httptransport.NewCodecAwareEncoder(registry, true) // true = enable fallback

// Configure server options
serverOpts := httptransport.DefaultServerOptions()
serverOpts.CodecAwareEncoder = encoder

// Configure client options
clientOpts := httptransport.DefaultClientOptions()
clientOpts.CodecAwareEncoder = encoder

// Create transport with codec support
transport := httptransport.NewTransport(baseURL, httpClient, versionParser, clientOpts)
handler := httptransport.NewSyncHandler(store, logger, versionParser, serverOpts)

Wire Format

Events with codec support use the WireEvent format:

type WireEvent struct {
    ID          string                 `json:"id"`
    Type        string                 `json:"type"`
    AggregateID string                 `json:"aggregate_id"`
    Data        json.RawMessage        `json:"data"`         // Encoded data
    DataKind    string                 `json:"data_kind,omitempty"` // Codec identifier
    Metadata    map[string]interface{} `json:"metadata"`
}

When a codec is used:

  • Data contains the codec-encoded data
  • DataKind contains the codec identifier
  • During decoding, the system looks up the codec by DataKind

When no codec is available:

  • Data contains JSON-encoded data
  • DataKind is empty
  • Standard JSON unmarshaling is used

Fallback Behavior

The CodecAwareEncoder supports fallback mode:

encoder := httptransport.NewCodecAwareEncoder(registry, true) // fallback enabled

With fallback enabled:

  • If no codec is found for a kind, falls back to JSON encoding
  • If codec encoding fails, falls back to JSON encoding
  • If codec decoding fails, falls back to JSON decoding

With fallback disabled:

  • Returns errors when codecs are not found
  • Returns errors when codec operations fail
  • Useful for strict validation scenarios

Thread Safety

The registry is thread-safe and supports concurrent access:

  • Multiple goroutines can safely register codecs
  • Multiple goroutines can safely retrieve codecs
  • All operations use proper locking

Best Practices

  1. Unique Kind Names: Use unique, descriptive names for codec kinds
  2. Version Compatibility: Design codecs to handle version changes gracefully
  3. Error Handling: Provide clear error messages in codec implementations
  4. Testing: Test codec round-trips thoroughly
  5. Documentation: Document the expected data types for each codec

Examples

See examples/codec-aware-transport/main.go for a complete working example demonstrating:

  • Custom codec implementations
  • Registry usage
  • Event encoding/decoding
  • HTTP transport integration
  • Round-trip testing

Compatibility

The codec system is designed for compatibility with:

  • Existing Code: Works alongside existing JSON-based events
  • Future Transports: SSE, WebSocket, gRPC transports can use the same codecs
  • Different Languages: Wire format is language-agnostic JSON
  • Legacy Systems: Falls back gracefully for unknown types

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultRegistry = NewRegistry()

DefaultRegistry provides a global registry instance for convenience. Applications can use this directly or create their own registry instances.

Functions

func Register

func Register(c Codec)

Register is a convenience function that registers a codec with the default registry.

Types

type Codec

type Codec interface {
	// Kind returns the unique identifier for this codec type
	Kind() string
	// Encode converts any value to JSON raw message
	Encode(any) (json.RawMessage, error)
	// Decode converts JSON raw message back to typed value
	Decode(json.RawMessage) (any, error)
}

Codec defines an interface for encoding and decoding event data with a specific kind identifier for registry lookup.

func Get

func Get(kind string) (Codec, bool)

Get is a convenience function that retrieves a codec from the default registry.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry manages codec registration and lookup with thread safety. It provides a centralized way to register domain-specific codecs and retrieve them by kind for event data encoding/decoding.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new codec registry instance.

func (*Registry) Get

func (r *Registry) Get(kind string) (Codec, bool)

Get retrieves a codec by its kind identifier. Returns the codec and true if found, nil and false otherwise.

func (*Registry) Kinds

func (r *Registry) Kinds() []string

Kinds returns all registered codec kinds for debugging/introspection.

func (*Registry) Register

func (r *Registry) Register(c Codec)

Register adds a codec to the registry using its Kind() as the key. This allows domain-specific types to be encoded/decoded without double-encoding through generic JSON marshaling.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL