goes
goes copied to clipboard
How to use `SoftRestorer`?
Problem
When the event stream of an aggregate contains a SoftDeleter event, the aggregate can neither be queried nor fetched from the aggregate repository. How can an aggregate be restored if it cannot be fetched to raise the SoftRestorer event?
Example
package example
type RestoredEvent struct {}
func (RestoredEvent) SoftRestore() bool { return true }
func example(repo aggregate.Repository) {
var foo aggregate.Aggregate // soft-deleted aggregate
if err := repo.Fetch(context.TODO(), foo); err != nil {
// fails with repository.ErrDeleted
}
// we want to do this
aggregate.Next(foo, "restored", RestoredEvent{})
repo.Save(context.TODO(), foo)
}
Proposal – context.Context API
The repository package could provide a "hidden" API using context.Context.WithValue() to disable soft-deletion checks:
package example
func example(repo aggregate.Repository) {
var foo aggregate.Aggregate
ctx := repository.WithSoftDeleted(context.TODO())
repo.Fetch(ctx, foo)
aggregate.Next(foo, "restored", RestoredEvent{})
repo.Save(context.TODO(), foo)
}
Drawbacks
- hiding options behind a Context is considered bad design
Proposal – "Prepare" method
The repository.Repository type could provide a Prepare() method that "prepares" the next query/fetch.
package example
func example(repo *repository.Repository) {
var foo aggregate.Aggregate
repo.Prepare(repository.WithSoftDeleted())
repo.Fetch(ctx, foo)
aggregate.Next(foo, "restored", RestoredEvent{})
repo.Save(context.TODO(), foo)
}
Drawbacks
- clunky to use
- not concurrency-safe
- requires users to depend on
*repository.Repositoryinstead ofaggregate.Repository
Proposal – Add specialized methods
The repository.Repository type could provide a FetchDeleted() and a QueryDeleted() method.
package example
func example(repo *repository.Repository) {
var foo aggregate.Aggregate
repo.FetchDeleted(ctx, foo)
aggregate.Next(foo, "restored", RestoredEvent{})
repo.Save(context.TODO(), foo)
}
Drawbacks
- requires users to depend on
*repository.Repositoryinstead ofaggregate.Repository
Could variadic options not be added to the repository.Fetch method? It shouldn't be backwards incompatible since all previous calls would not include any extra arguments. This would bring it in line with aggregate stream implementation.
Proposal - Use variadic options
The repository.Fetch method could provide a repository.WithSoftDeleted(true) option
package example
func example(repo *repository.Repository) {
var foo aggregate.Aggregate // aggregate whose latest event was a SoftDelete => true
repo.Fetch(ctx, foo, repository.WithSoftDeleted(true))
aggregate.Next(foo, "restored", RestoredEvent{})
repo.Save(context.TODO(), foo)
}