Skip to content

Commit

Permalink
feature: #279 Stored setting up Default write controller
Browse files Browse the repository at this point in the history
* Added interface for event store
* Setup GORM based event store
* Changed the resourced repository to have the event dispatcher injected (instead of making it implement the event dispatcher)
  • Loading branch information
akeemphilbert committed Mar 30, 2024
1 parent d2cdb83 commit 56454cf
Show file tree
Hide file tree
Showing 13 changed files with 1,231 additions and 327 deletions.
2 changes: 1 addition & 1 deletion v2/rest/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var API = fx.Module("rest",
NewGORM,
NewGORMResourceRepository,
NewCommandDispatcher,
NewEventDispatcher,
NewGORMEventStore,
),
fx.Invoke(RouteInitializer, registerHooks),
)
8 changes: 6 additions & 2 deletions v2/rest/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"github.com/getkin/kin-openapi/openapi3"
"github.com/labstack/echo/v4"
"io"
"net/http"
"strconv"
)
Expand Down Expand Up @@ -64,9 +65,12 @@ func DefaultWriteController(logger Log, commandDispatcher CommandDispatcher, res
}

var resource *BasicResource
if err := ctxt.Bind(&resource); err != nil {
return NewControllerError("unexpected resource", err, http.StatusBadRequest)
body, err := io.ReadAll(ctxt.Request().Body)
if err != nil {
ctxt.Logger().Debugf("unexpected error reading request body: %s", err)
return NewControllerError("unexpected error reading request body", err, http.StatusBadRequest)
}
resource, err = new(BasicResource).FromSchema(api, body)
//not sure this is correct
payload, err := json.Marshal(&ResourceCreateParams{
Resource: resource,
Expand Down
64 changes: 64 additions & 0 deletions v2/rest/controllers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package rest_test

import (
"github.com/getkin/kin-openapi/openapi3"
"github.com/labstack/echo/v4"
"github.com/wepala/weos/v2/rest"
"golang.org/x/net/context"
"net/http"
"net/http/httptest"
"strings"
"testing"
)

func TestDefaultWriteController(t *testing.T) {
schema, err := openapi3.NewLoader().LoadFromFile("fixtures/blog.yaml")
if err != nil {
t.Fatalf("error encountered loading schema '%s'", err)
}
logger := &LogMock{
DebugfFunc: func(format string, args ...interface{}) {

},
DebugFunc: func(args ...interface{}) {

},
ErrorfFunc: func(format string, args ...interface{}) {

},
ErrorFunc: func(args ...interface{}) {

},
}
t.Run("create a simple resource", func(t *testing.T) {
repository := &RepositoryMock{
PersistFunc: func(ctxt context.Context, logger rest.Log, resources []rest.Resource) []error {
return nil
},
}
commandDispatcher := &CommandDispatcherMock{
DispatchFunc: func(ctx context.Context, command *rest.Command, repository rest.Repository, logger rest.Log) (interface{}, error) {
return nil, nil
},
}
controller := rest.DefaultWriteController(logger, commandDispatcher, repository, schema, nil, nil)
e := echo.New()
e.POST("/*", controller)
resp := httptest.NewRecorder()
req := httptest.NewRequest(echo.POST, "/blogs/test", strings.NewReader(`{
"@id": "/blogs/test",
"@type": "http://schema.org/Blog",
"title":"test"
}`))
req.Header.Set(echo.HeaderContentType, "application/ld+json")
e.ServeHTTP(resp, req)
if resp.Code != http.StatusCreated {
t.Errorf("expected status code %d, got %d", http.StatusCreated, resp.Code)
}

if len(commandDispatcher.DispatchCalls()) != 1 {
t.Errorf("expected dispatch to be called once, got %d", len(commandDispatcher.DispatchCalls()))
}

})
}
104 changes: 30 additions & 74 deletions v2/rest/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,8 @@ package rest

import (
"encoding/json"
"go.uber.org/fx"
"golang.org/x/net/context"
"sync"
)

type EventDispatcherParams struct {
fx.In
EventConfigs []EventHandlerConfig `group:"eventHandlers"`
}

type EventDispatcherResult struct {
fx.Out
Dispatcher EventDispatcher
}

func NewEventDispatcher(p EventDispatcherParams) EventDispatcherResult {
dispatcher := &DefaultEventDisptacher{
handlers: make(map[string][]EventHandler),
}
for _, config := range p.EventConfigs {
dispatcher.AddSubscriber(config)
}
return EventDispatcherResult{}
}

type EventHandlerConfig struct {
ResourceType string
Type string
Handler EventHandler
}

type DefaultEventDisptacher struct {
handlers map[string][]EventHandler
handlerPanicked bool
}

func (e *DefaultEventDisptacher) Dispatch(ctx context.Context, event Event) []error {
//mutex helps keep state between routines
var errors []error
var wg sync.WaitGroup
if handlers, ok := e.handlers[event.Type]; ok {
for i := 0; i < len(handlers); i++ {
//handler := handlers[i]
wg.Add(1)
go func() {
defer func() {
if r := recover(); r != nil {
e.handlerPanicked = true
}
wg.Done()
}()

//err := handler(ctx, event)
//if err != nil {
// errors = append(errors, err)
//}

}()
}
wg.Wait()
}

return errors
}

func (e *DefaultEventDisptacher) AddSubscriber(config EventHandlerConfig) {
if e.handlers == nil {
e.handlers = map[string][]EventHandler{}
}
e.handlers[config.Type] = append(e.handlers[config.Type], config.Handler)
}

func (e *DefaultEventDisptacher) GetSubscribers() map[string][]EventHandler {
return e.handlers
}

type Event struct {
ID string `json:"id"`
Type string `json:"type"`
Expand All @@ -97,3 +23,33 @@ type EventMeta struct {
AccountID string `json:"accountId"`
Created string `json:"created"`
}

func (e *Event) NewChange(event *Event) {
//TODO implement me
panic("implement me")
}

func (e *Event) GetNewChanges() []Resource {
//TODO implement me
panic("implement me")
}

func (e *Event) Persist() {
//TODO implement me
panic("implement me")
}

func (e *Event) GetType() string {
//TODO implement me
panic("implement me")
}

func (e *Event) GetSequenceNo() int {
//TODO implement me
panic("implement me")
}

func (e *Event) GetID() string {
//TODO implement me
panic("implement me")
}
124 changes: 124 additions & 0 deletions v2/rest/event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package rest_test

import (
"github.com/wepala/weos/v2/rest"
"golang.org/x/net/context"
"testing"
)

func TestDefaultEventDisptacher_AddSubscriber(t *testing.T) {
t.Run("add subscriber for event type only", func(t *testing.T) {
eventDispatcher := new(rest.GORMEventStore)
err := eventDispatcher.AddSubscriber(rest.EventHandlerConfig{
Type: "create",
Handler: func(ctx context.Context, logger rest.Log, event rest.Event) error {
return nil
},
})
if err != nil {
t.Errorf("expected no error, got %s", err)
}
handlers := eventDispatcher.GetSubscribers("")
if len(handlers) != 1 {
t.Errorf("expected 1 handler, got %d", len(handlers))
}
if handler, ok := handlers["create"]; !ok {
t.Errorf("expected handler for create event type")
} else {
if handler == nil {
t.Errorf("expected handler for create event type")
}
}
})
t.Run("add subscriber for resource type and event", func(t *testing.T) {
eventDispatcher := new(rest.GORMEventStore)
err := eventDispatcher.AddSubscriber(rest.EventHandlerConfig{
ResourceType: "Article",
Type: "create",
Handler: func(ctx context.Context, logger rest.Log, event rest.Event) error {
return nil
},
})
if err != nil {
t.Errorf("expected no error, got %s", err)
}
handlers := eventDispatcher.GetSubscribers("Article")
if len(handlers) != 1 {
t.Errorf("expected 1 handler, got %d", len(handlers))
}
if handler, ok := handlers["create"]; !ok {
t.Errorf("expected handler for create event type")
} else {
if handler == nil {
t.Errorf("expected handler for create event type")
}
}
})
t.Run("adding subscriber without handler should throw error", func(t *testing.T) {
eventDispatcher := new(rest.GORMEventStore)
err := eventDispatcher.AddSubscriber(rest.EventHandlerConfig{
Type: "create",
})
if err == nil {
t.Errorf("expected error, got nil")
}
})
}

func TestResourceRepository_Dispatch(t *testing.T) {
logger := &LogMock{
DebugfFunc: func(format string, args ...interface{}) {

},
DebugFunc: func(args ...interface{}) {

},
ErrorfFunc: func(format string, args ...interface{}) {

},
ErrorFunc: func(args ...interface{}) {

},
}
t.Run("should trigger resource specific handler and generic event type handler", func(t *testing.T) {
createHandlerHit := false
articleCreateHandlerHit := false
eventDispatcher := new(rest.GORMEventStore)
err := eventDispatcher.AddSubscriber(rest.EventHandlerConfig{
Type: "create",
Handler: func(ctx context.Context, logger rest.Log, event rest.Event) error {
createHandlerHit = true
return nil
},
})
if err != nil {
t.Errorf("expected no error, got %s", err)
}
err = eventDispatcher.AddSubscriber(rest.EventHandlerConfig{
Type: "create",
ResourceType: "Article",
Handler: func(ctx context.Context, logger rest.Log, event rest.Event) error {
articleCreateHandlerHit = true
return nil
},
})
if err != nil {
t.Errorf("expected no error, got %s", err)
}
errors := eventDispatcher.Dispatch(context.Background(), rest.Event{
Type: "create",
Meta: rest.EventMeta{
ResourceType: "Article",
},
}, logger)
if len(errors) != 0 {
t.Errorf("expected no errors, got %d", len(errors))
}
if !createHandlerHit {
t.Errorf("expected create handler to be hit")
}
if !articleCreateHandlerHit {
t.Errorf("expected article create handler to be hit")
}
})
}
1 change: 1 addition & 0 deletions v2/rest/fixtures/blog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -875,3 +875,4 @@ paths:
responses:
200:
description: Delete author

Loading

0 comments on commit 56454cf

Please sign in to comment.