📦 box

A lightweight wrapper making Go interfaces serialisable so they can be used as arguments for Temporal workflows and activities.

Usage

Suppose I have the following interface for a message request:

type MessageRequest interface {
    SendMessage() error
    ScheduleMessage(time.Time) error
}

With two concrete implementations:

type SlackMessageRequest struct {
    RecipientID string
    Content []byte
    ...
}

type EmailRequest struct {
    To string
    Subject string
    CC string
    Body string
    ...
}

As part of some Temporal workflow, I wish to send either kind of message using an activity. Ideally, I would want the activity to be type-agnostic w.r.t. the message request, and simply interact through the interface:

func (h *CommHandler) HandleMessageDelivery(ctx context.Context, msgRequest MessageRequest) error {
    ...
}

However, arguments to Temporal activities are serialised and de-serialised behind the scenes, and so that intuitive solution would not work: a concrete JSON embedding of one of the implementations of the interface cannot be directly de-serialised into an interface instance.

By slightly changing the activity code, Box solves this problem:

func (h *CommHandler) HandleMessageDelivery(ctx context.Context, msgRequest Box[MessageRequest]) error {
    err := msgRequest.Unbox(&SlackMessageRequest{}, &EmailRequest{})
    ...
    err = msgRequest.Data.SendMessage()
    ...
}

With Box, de-serialisisation of the representation of the struct implementing the interface MessageRequest is deferred until I explicitly Unbox it, providing concrete types that satisfy the interface and might be inside the Box.

After Unbox-ing, my activity can work with the interface as if it received it as an argument directly.

Creating a new Box

box, err := NewBox[MessageRequest](&SlackMessageRequest{
    ...
})

How a concrete type is chosen for Unbox-ing

Unbox iteratively attempts to de-serialise the raw JSON representation of the object inside the box into each of the concrete types given, stopping at the first success.

What objects can be put inside a Box

An object can be boxed if it satisfies the following interface:

type Boxable interface {
	Unbox(json.RawMessage) error
}

A straight-forward way to implement this interface is to delegate to the default json.Unmarshal function.

GitHub

View Github