Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion command/error_recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ type ErrorRecorder struct {
// If unspecified, FailedCommandType will be used by default.
StreamType string

// StreamNameMapper maps to a StreamName value based on the command that failed.
// This is useful in case you want to use the same Aggregate id the command is targeting.
//
// If unspecified, the command name will be used instead.
StreamNameMapper func(cmd eventually.Command) string

// EventMapper should return the Domain Event type you defined for these commands.
//
// NOTE: this is necessary for (de)-serialization purposes while generics are
Expand All @@ -53,9 +59,14 @@ func (er ErrorRecorder) streamType() string {
}

func (er ErrorRecorder) buildStreamID(cmd eventually.Command) eventstore.StreamID {
streamName := cmd.Payload.Name()
if er.StreamNameMapper != nil {
streamName = er.StreamNameMapper(cmd)
}

return eventstore.StreamID{
Type: er.streamType(),
Name: cmd.Payload.Name(),
Name: streamName,
}
}

Expand Down
50 changes: 49 additions & 1 deletion command/error_recorder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ func TestErrorRecorder(t *testing.T) {
eventStore := inmemory.NewEventStore()
trackingEventStore := inmemory.NewTrackingEventStore(eventStore)

expectedStreamType := "mocks-command"
const expectedStreamType = "mocks-command"

expectedErr := errors.New("failed command")
expectedCommand := eventually.Command{
Payload: mockCommand{message: t.Name()},
Expand Down Expand Up @@ -184,4 +185,51 @@ func TestErrorRecorder(t *testing.T) {
},
}, trackingEventStore.Recorded())
})

t.Run("when handler fails, record event with custom stream name", func(t *testing.T) {
eventStore := inmemory.NewEventStore()
trackingEventStore := inmemory.NewTrackingEventStore(eventStore)

expectedStreamType := "mocks-command"
expectedErr := errors.New("failed command")
expectedCommand := eventually.Command{
Payload: mockCommand{message: t.Name()},
}

handler := command.ErrorRecorder{
Handler: command.HandlerFunc(func(ctx context.Context, cmd eventually.Command) error {
return expectedErr
}),
Appender: trackingEventStore,
StreamType: expectedStreamType,
StreamNameMapper: func(cmd eventually.Command) string {
return cmd.Payload.(mockCommand).message
},
EventMapper: func(err error, cmd eventually.Command) eventually.Payload {
return mockCommandHasFailed{
err: err,
command: cmd.Payload.(mockCommand),
}
},
}

err := handler.Handle(context.Background(), expectedCommand)

assert.Error(t, err)
assert.Equal(t, []eventstore.Event{
{
Version: 1,
Stream: eventstore.StreamID{
Type: expectedStreamType,
Name: expectedCommand.Payload.(mockCommand).message,
},
Event: eventually.Event{
Payload: mockCommandHasFailed{
err: expectedErr,
command: expectedCommand.Payload.(mockCommand),
},
},
},
}, trackingEventStore.Recorded())
})
}