looplab / eventhorizon Goto Github PK
View Code? Open in Web Editor NEWEvent Sourcing for Go!
License: Apache License 2.0
Event Sourcing for Go!
License: Apache License 2.0
When I went over error handling I noticed that there is no error handling in the New*
functions. Wouldn't it a good idea to validate input as early as possible?
Holding aggregates in memory like actors for more effective performance.
There should be an interface for projections, maybe like this:
type Projection interface{
Project(Event, Model) (Model, error)
}
Event is the event to be projected and Model is a model struct created by a model factory with the latest DB model is unmarshaled into it (or a blank model if it doesn't yet exist in the DB). It returns a new model with the event projected onto it which will be saved to the DB. If the returned Model is nil it should be deleted.
There should also be a ProjectionManager or similar that maps events to projections and does the heavy lifting around each Projection.
Scaling out for command side would require sort of a sharding solution with a coordinator to load balance nodes properly. In AKKA it works based on a hashcode of the aggregate ID like this hash(id) mod NumberOfNodes.
I can't help to think the version handling in the event store is wrong. (using the mongodb implementation as example)
If I understand the code correctly it only makes sure that the aggregate has not changed from when it is read
for _, event := range events {
// Get an existing aggregate, if any.
var existing []mongoAggregateRecord
err := sess.DB(s.db).C("events").FindId(event.AggregateID().String()).
Select(bson.M{"version": 1}).Limit(1).All(&existing)
until it is written
// Increment record version before inserting.
r.Version = existing[0].Version + 1
// Increment aggregate version on insert of new event record, and
// only insert if version of aggregate is matching (ie not changed
// since the query above).
err = sess.DB(s.db).C("events").Update(
bson.M{
"_id": event.AggregateID().String(),
"version": existing[0].Version,
},
bson.M{
"$push": bson.M{"events": r},
"$inc": bson.M{"version": 1},
},
The purpose of the version check should be to make sure that the event is applied (saved) to the correct version of the aggregate when the event was first created. That is, the aggregate version that should be validated is the one that was instantiated when the command is applied and the event(s) is created.
A solution would be to add a Version attribute to the eventhorizon.Event
which holds the aggregate version which the event should be applied to. This can then be validated when storing the event.
Hope it makes sense, can be a bit hairy to explain :)
Ideas:
Imagine your projection is down and several commands reach to the command side and result into some events persisted into event store. Once the projection is up again, new events will not be collected.
The added error reporting from event handlers makes the async event handling effectively synchronous in the sense that it waits for all handling to complete before returning.
It will show up as a deadline exceeded error in some projection cases.
This is to support persistent aggregate storage, and specifically snapshots.
Example implementation: https://cqrs.wordpress.com/documents/building-event-storage/
Useful for passing metadata around etc.
This would make the event contain the metadata instead the event record, and the specific event data would be what is domain specific. This reduces a lot of extra code for domain events (for implementing the current Event interface) and also creates a better way to handle metadata that is the same for every event.
The order of the events coming to projection is implementation dependent. There must be a guarantee that projection will receive eventN, eventN+1 etc consequently just as they appear on the event stream.
It should be registered by default.
For example a validation method on the aggregate interface, or a middleware that runs before handling the command in the aggregate.
An implementation of the EventBus interface that uses the cloud.google.com/go/pubsub package.
An implementation of the EventStore interface using the cloud.google.com/go/datastore package.
The full toolkit should be thread safe.
This seems to be the broader term for what an event belongs to, it could be other things than aggregates.
The projection for guests in the example does not handle events that are out of order which makes the tests fail.
As I see, Eventhorizon store the struct ( maybe I can call it aggregate table ) in the eventStore, likes: {aggregateId: string, events: [] object}
, not the simple event table,like {aggregateId: string, event: object }
.
This makes some events need to update the record of the eventStore, and I think this is not safe with race condition.
Why not just do add operation for the eventStore?
Maybe I am wrong. Appreciate for read and answer my question.
Hi !
Today we use snowflake as UUID, stored as uint64
and transmitted as string
in base64.
Do you think is possible to change UUID to an interface? This way we'll be able to implement our custom UUID.
Please let me know what do you think, if you want I can help with this change
eh.WithNamespace()
.There should be a way to schedule commands as a "job" to be executed later.
There should be a simple query abstraction for read models.
Hi I ran into a problem when trying to create a Todo API using your framework.
I'm trying to send the following command
type UpdateTodo struct {
TodoID string
Name string
Done bool
}
id := eventhorizon.NewUUID()
cmd := UpdateTodo{TodoID: id, Name: "Todo 1", Done: false}
but in doing so it seems to complain about the field Done is missing and I think it is checkCommand method that is creating this problem for me.
here is demo of the issue as well https://play.golang.org/p/IRzcOGIAgS
Are you recommending sending boolean values in another way? this problmen probably holds true for integers as well. pretty much any default values.
There should be a subscriber/observer interface for instead of having this mixed with the EventBus interface. The Redis and GCP implementations would be implementations of the new interface instead of the EventBus.
To use a more standard encoding format for events, commands etc.
For example in
eventhorizon/eventbus/local/eventbus.go
Line 60 in ba8d55f
Suggestion is to either return the error and stop the loop or have some side channel for reporting errors in asynchronous mode.
Discovered this project. Looked interesting. Checked out the code, but can't seem to get the tests to run. Partially based on wercker.yml
:
➜ eventhorizon git:(master) go get golang.org/x/tools/cmd/cover
➜ eventhorizon git:(master) go get github.com/axw/gocov/gocov
➜ eventhorizon git:(master) go get github.com/mattn/goveralls
➜ eventhorizon git:(master) go build ./...
testing/checkers.go:20:2: cannot find package "gopkg.in/check.v1" in any of:
/usr/local/homebrew/Cellar/go/1.2/libexec/src/pkg/gopkg.in/check.v1 (from $GOROOT)
/Users/jens/Development/src/external_projects/eventhorizon/src/gopkg.in/check.v1 (from $GOPATH)
➜ eventhorizon git:(master)
When a burst of events is handled by a projector of the same model/aggregate id, all events will now wait for the correct minversion of the model at the same time. This causes the events to have almost the same backoff delay and will time out events with higher minversion waits easily.
This also causes unnecessary db lookups for higher minversion events.
A proposed solution:
Write a middleware or a type of event handler that can have an internal sorted queue for all incoming events (fan-in -> single out). This fan-in event handler should collect all events for all events that goes to the same model id and namespace and then always handle the event with the least minversion synchronously before continuing to the next event the least minversion.
Instead of:
func (d *DelegateDispatcher) handleCommand(handlerType reflect.Type, command Command) error {
// Create aggregate from its type
aggregate := d.createAggregate(command.AggregateID(), handlerType)
// Load aggregate events
events, _ := d.eventStore.Load(aggregate.AggregateID())
aggregate.ApplyEvents(events)
...
it will look like this:
func (d *DelegateDispatcher) handleCommand(handlerType reflect.Type, command Command) error {
// Load aggregate
aggregate, err := d.eventStore.LoadAggregate(aggregate.AggregateID())
...
Seems like registering handlers by event type could be fragile. I'm thinking of the case when you add an attribute to an event and then you want to replay old events in the store.
Do you see any benefits to using the object type as the key over a simple event name + version number combination? If you used (event_name,version) then you could keep old and new handlers registered and you wouldn't have to change the event type. (Also, from what I have read reflection is really slow in golang.)
I wonder how the .NET guys handle changes in event and command attributes.
I see that the simple/example
is not a standard go test. Is there reason why?
The events must have a version set when publishing, this does not happen at the moment. The version should either be set at event creation or saving.
An example:
// EventBus is an interface defining an event bus for distributing events.
type EventBus interface {
...
// AddHandler adds a handler for an event.
AddHandler(EventHandler, EventMatcher)
...
}
// EventMatcher is used to match events, used when adding event handlers.
type EventMatcher interface {
MatchesEvent(EventType) bool
}
// AnyEvent is an EventMatcher that matches any event.
type AnyEvent struct{}
// MatchesEvent imlpements the MatchesEvent method of the EventMatcher interface.
func (m AnyEvent) MatchesEvent(e EventType) bool {
return true
}
// OneEvent is an EventMatcher that matches a single event.
type OneEvent struct {
Event EventType
}
// MatchesEvent imlpements the MatchesEvent method of the EventMatcher interface.
func (m OneEvent) MatchesEvent(e EventType) bool {
return m.Event == e
}
// ManyEvents is an EventMatcher that matches if the event is in the slice.
type ManyEvents []EventType
// MatchesEvent imlpements the MatchesEvent method of the EventMatcher interface.
func (m OneEvent) MatchesEvent(e EventType) bool {
// return true if e is in the slice.
}
Have you already implemented a way to replay events or passing history events array as a ctor argument?
Sometimes it is very useful.
TIA
Add initial support for fetching a min versions of models in the read model. This is to be able to fetch a model with a version after a certain event has been applied.
This would be used to automatically add the handler for the commands that they can handle instead or SetHandler
multiple times.
The CommandBus would then get a AddHandler
instead instead of a SetHandler
because of the new semantics.
Some event storages are holding entities identified by both these classifiers.
Enhance method Load with Type argument!
This is needed to allow switching event data during maintenance operations.
Sagas should be implemented as an EventHandler.
When asking for a version other than 1 that still does not exist it returns model not found instead of retrying as it should do.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.