Replies: 9 comments 3 replies
-
Hello, Thank you for reaching out, and I'm pleased to know you're finding value in the library. Out of curiosity, could you share some of your use cases for To address your question about atomic Save operations: While the current implementation of the aggregate repository in the IdeaInternally, the Here's an idea:
package example
import "github.com/modernice/goes/backend/mongo"
func example(store *mongo.EventStore) {
tx := store.Transaction(context.TODO())
err := tx.Execute(func() error {
// perform some operations
return nil
})
err = tx.Execute(func() error {
// perform some more operations
return nil
})
if err == nil {
err = tx.Commit()
} else {
err = tx.Abort()
}
}
package example
import "github.com/modernice/goes/aggregate"
func example(repo *repository.Repository, foo, bar aggregate.Aggregate) {
err := repo.Use(context.TODO(), foo, func() {
// Operations with foo
return repo.Use(context.TODO(), bar, func() {
// Operations with both foo and bar
return nil
})
})
} Do you think this approach would meet your requirements? Best |
Beta Was this translation helpful? Give feedback.
-
Hi, Thanks for a quick response! Thank you for the example. The problem with the Let me share an example of what I implemented (it is similar to BeforeInsert and AfterInsert). .../mongo/store.go type EventHandler func(context.Context, ...event.Event) error
type EventStore struct {
// ...
txEventHandlers []EventHandler
// ...
}
func WithTxEventHandler(th EventHandler) EventStoreOption {
return func(s *EventStore) {
s.transactions = true
s.txEventHandlers = append(s.txEventHandlers, th)
}
}
func (s *EventStore) Insert(ctx context.Context, events ...event.Event) error {
// ...
beginTx(...)
insert(ctx, events)
// ...
for _, fn := range s.txEventHandlers {
if handlerErr := fn(ctx, events...); handlerErr != nil {
fmt.Println("aborting transaction...")
if abortError := ctx.AbortTransaction(ctx); abortError != nil {
return fmt.Errorf("abort transaction: %w", abortError)
}
return fmt.Errorf("tx event handler: %w", handlerErr)
}
}
commitTx(...)
} package example
func example(){
handler := mongo.EventHandler(func(ctx context.Context, events ...event.Event) error {
// Do something as part of the transaction...
return nil
})
eventStore := mongotest.NewEventStore(
// ...
mongo.WithTxEventHandler(handler),
)
} Let me know what you think. |
Beta Was this translation helpful? Give feedback.
-
Okay I get it now. What you're aiming for is to execute actions within a single aggregate's transaction. After giving this some thought, I think I found a potential solution:
Do you think this design would cover your use cases or is it too restricting? |
Beta Was this translation helpful? Give feedback.
-
hello, any update on this? |
Beta Was this translation helpful? Give feedback.
-
sorry, I'm very busy at the moment so I hadn't had the time to check on this again. I think for your use case where you just want to generally hook into all transactions, a simple options for the event store like the one in your example should work out. I would change the signature of the transaction handler a bit and add a flag to specify when to call these hooks: package mongo
var (
// Call hook before inserting events into the event store.
PreInsert = TransactionHook("pre:insert")
// Call hook after inserting events into the event store.
PostInsert = TransactionHook("post:insert")
)
type TransactionHook string
type Transaction interface {
Session() mongo.SessionContext
// EventStore() returns the event store itself to allow inserting (or deleting)
// more events within the same transaction.
EventStore() *EventStore
// Returns all events that were inserted during this transaction.
// They won't be inserted if the transaction is aborted.
// When calling EventStore().Insert(), the inserted events will be added to this list.
InsertedEvents() []event.Event
}
// mongo.SessionContext uses the same pattern, so I would like to stick to that.
type TransactionContext interface {
context.Context
Transaction
}
func WithTransactionHook(hook TransactionHook, fn func(TransactionContext) error) EventStoreOption {...} package example
import (
"github.com/modernice/goes/backend/mongo"
)
func example() {
store := mongo.NewEventStore(
codec.New(),
mongo.WithTransactionHook(mongo.PostInsert, func(ctx mongo.TransactionContext) {
// insert events
err := ctx.EventStore().Insert(ctx, ...)
// or use the mongo collection directly for more advanced use cases.
// insertions won't be tracked by Transaction.InsertedEvents()
col := ctx.EventStore().Collection()
}),
)
} The event store would need to persist the I don't see a reason to not add this feature to the event store. Unfortunately I'm pretty loaded with work at the moment so I don't know whether I'm able to implement this soon. If you'd like to create a PR for this, I would gladly accept that. Otherwise I'll implement this when I got some free time on hand 👍🏼 |
Beta Was this translation helpful? Give feedback.
-
no worries, thank you for your reply. Also, it should be possible to register multiple hooks, right? I was thinking, I could implement the simplified version of this (without EventStore() and InsertedEvents) which would satisfy my requirements. Then, in the future, one of us can implement the rest? Would you be happy with that and accept such a PR? Thanks |
Beta Was this translation helpful? Give feedback.
-
I don't have a specific scenario in mind but the PreInsert hook could be useful in scenarios where the order of the insertions matter. The event store could for example connect to an event bus that publishes the events in the same order as they were inserted, so I think this could provide flexibility for more advanced use cases. Also, we could provide additional hooks in the future to hook into other methods of the event store by introducing for example
Yes, this should be possible. Each registered hook should be called sequentially and each one should be able to make the transaction fail. If you implement a simplified version of this, I would be happy to merge the PR 👍🏼 type Transaction interface {
Session() mongo.SessionContext
} And thank you for your contribution 🙂 |
Beta Was this translation helpful? Give feedback.
-
hey, I've created a draft PR for you to take a look: #146 |
Beta Was this translation helpful? Give feedback.
-
implemented in #148 |
Beta Was this translation helpful? Give feedback.
-
Hello,
We recently started using GOES and it looks really good.
One thing that we would like to be able to do is to atomically perform a Save operation with another operation. In case, any of the operations fail, they should both be rolled back. I looked at the code and couldnt find a way of achieving this - if it is possible, please point me in the right direction (I saw BeforeInsert and AfterInsert functions but they are not part of the transaction).
Otherwise, we were planning to register a handler (callback function) that would be called during the save operation - inside of the mongo transaction.
Thank you
Beta Was this translation helpful? Give feedback.
All reactions