goes icon indicating copy to clipboard operation
goes copied to clipboard

How to use `SoftRestorer`?

Open bounoable opened this issue 3 years ago • 3 comments

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

bounoable avatar Apr 16 '22 14:04 bounoable

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.Repository instead of aggregate.Repository

bounoable avatar Apr 21 '22 16:04 bounoable

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.Repository instead of aggregate.Repository

bounoable avatar Apr 21 '22 16:04 bounoable

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)
}

olpie101 avatar Oct 20 '23 14:10 olpie101