resonatehq / resonate

a dead simple programming model for the cloud
https://www.resonatehq.io
Apache License 2.0
287 stars 23 forks source link

Add Announcements #268

Open dtornow opened 3 months ago

dtornow commented 3 months ago

Describe the problem you are facing

Resonate's Deterministic Simulation Testing cannot observe the Resonate Server beyond the request/response API. E.g. the Deterministic Simulation Testing cannot observe if notifications are actually sent.

Describe the solution you'd like

P-Lang style announcements & monitors. With announcements and monitors, DST will be able to verify that e.g. announcements are sent if a corresponding subscription exists.

Implementation Details

Step 1

Create a package announcements.

Step 2

Define an interface Announcement with a function

type Announcement interface {
    Announce(data map[string]string)
}

Step 3

Define two different implementations:

Step 4

Use a package-level private variable to hold a singleton instance. Instantiate the correct singleton instance based on config parameter on startup e.g.

package announcements

import "sync"

var (
    instance Announcement
    once     sync.Once
)

func Initialize(envType EnvironmentType) {
    once.Do(func() {
        switch envType {
        case Nop:
            instance = &NopAnnouncement{}
        case Dst:
            instance = &DstAnnouncement{}
        default:
            // Handle default case or throw an error
        }
    })
}

// Additional code to define EnvironmentType, NopAnnouncement, and DstAnnouncement follows...

Step 5

Implement the interface

dtornow commented 3 months ago

The network is a good candidate to add a first announcement

https://github.com/resonatehq/resonate/blob/7935e1cae3c90e04152d23f2170acdb101a73946/internal/kernel/t_aio/network.go#L22

dtornow commented 3 months ago

Add a custom event struct. The only required parameter is type, everything else is a key value map

type Event struct {
    Type string
    data map[string]interface{}
}

func NewEvent(type string, initialData ...map[string]interface{}) *Event {
    var data map[string]interface{}
    if len(initialData) > 0 {
        data = initialData[0] // Use the first map provided if any.
    } else {
        data = make(map[string]interface{}) 
    }

    return &Event{
        Type: eventType,
        data: data
    }
}

// Set adds or updates a key with a given value.
func (e *Event) Set(key string, value interface{}) {
    e.data[key] = value
}

// Get tries to retrieve a value of the specified type T from the event data. It returns the zero value and an error if the type does not match.
func [T any](e *Event) Get[T any](key string) (T, error) {
    value, exists := e.data[key]
    if !exists {
        var zero T
        panic("key %s does not exist in event of type %s", key, e.Type)
    }

    typedValue, ok := value.(T)
    if !ok {
        panic("value at key %s is not of the requested type in event of type %s", key, e.Type)
    }

    return typedValue, nil
}
dtornow commented 3 months ago

Regarding announcements: The announcement (the event) should contain all (relevant) application level information. For example, in the context of networking, the event should contain the url, http method, and http body