esja icon indicating copy to clipboard operation
esja copied to clipboard

[POC] Add snapshots

Open krzysztofreczek opened this issue 2 years ago • 5 comments

Here is a POC proposal for support of snapshots.

The Idea: I introduced an optional interface that informs that the Entity supports snapshots:

type EntityWithSnapshots[T any] interface {
	Entity[T]

	// Snapshot returns a Snapshot representing the current state of the Entity.
	Snapshot() Snapshot[T]
}

The snapshot is just an interface that devs will need to implement.

// Snapshot is an Event that stores and applies the current state back to the Entity.
type Snapshot[T any] interface {
	// ApplyTo applies the snapshot to the entity.
	ApplyTo(*T) error

	// SnapshotName should identify the snapshot and the version of its schema.
	SnapshotName() string
}

As part of the event store implementation, I started with an in-memory event store, and it could be later easily propagated to other databases. The idea is pretty simple:

  1. When we save the entity, we just look into the current version of the last event. If it is time to make a snapshot and the Entity implements the interface, we just create it and store it with the current version in a separate collection of snapshots.
  2. When we load the entity, we start by looking for the last snapshot created. If it exists we just load events with versions higher than the snapshot version. Eventually, we apply the snapshot and the later events to the empty Entity.

Testing: I created a new _example with a Counter entity to prove the POC works.

Here it is visible how simple it is to implement the snapshot support:

type Snapshot struct {
	ID           string
	CurrentValue int
}

func (s Snapshot) SnapshotName() string {
	return "CounterSnapshot_v1"
}

func (s Snapshot) ApplyTo(c *Counter) error {
	c.id = s.ID
	c.currentValue = s.CurrentValue
	return nil
}

...

func (c Counter) Snapshot() esja.Snapshot[Counter] {
	return Snapshot{
		ID:           c.id,
		CurrentValue: c.currentValue,
	}
}

krzysztofreczek avatar Jan 28 '23 16:01 krzysztofreczek

@krzysztofreczek I took a look at the implementation and the idea is brilliant in its simplicity. However, I would prefer to have a separate interface for the snapshot event, something like:

type SnapshotEvent[T any] interface {
	SnapshotName() string
	ApplyTo(*T) error
}

This would definitely save me confusion and make the API a bit more intuitive in case you read the code for the first time. At the same time, the implementation would be more robust. With the current approach, you allow to do weird things (see the code below) because from the domain perspective there is no difference between events and snapshots.

// you allow to do this - it is ok :)
func (c Counter) Snapshot() esja.Snapshot[Counter] {
	return Snapshot{
		ID:           c.id,
		CurrentValue: c.currentValue,
	}
}

// and you allow to do this - it is weird! :(
func (c Counter) Snapshot() esja.Snapshot[Counter] {
	return Created{
		ID:           c.id,
	}
}

ilkamo avatar Jan 29 '23 20:01 ilkamo

@krzysztofreczek I took a look at the implementation and the idea is brilliant in its simplicity. However, I would prefer to have a separate interface for the snapshot event, something like:

type SnapshotEvent[T any] interface {
	SnapshotName() string
	ApplyTo(*T) error
}

This would definitely save me confusion and make the API a bit more intuitive in case you read the code for the first time. At the same time, the implementation would be more robust. With the current approach, you allow to do weird things (see the code below) because from the domain perspective there is no difference between events and snapshots.

// you allow to do this - it is ok :)
func (c Counter) Snapshot() esja.Snapshot[Counter] {
	return Snapshot{
		ID:           c.id,
		CurrentValue: c.currentValue,
	}
}

// and you allow to do this - it is weird! :(
func (c Counter) Snapshot() esja.Snapshot[Counter] {
	return Created{
		ID:           c.id,
	}
}

@kamy22 , @m110 also pointed that out. Thank You. I will try to separate Snapshots from Events somehow. 👍

krzysztofreczek avatar Jan 29 '23 21:01 krzysztofreczek

@kamy22 @m110 , please check the results of Event/Snapshot separation: https://github.com/ThreeDotsLabs/esja/pull/30

krzysztofreczek avatar Jan 29 '23 22:01 krzysztofreczek

@krzysztofreczek LGTM 💪

ilkamo avatar Jan 30 '23 12:01 ilkamo

@vinitius JFYI, there will be snapshots in esja <3

ilkamo avatar Feb 05 '23 14:02 ilkamo