Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] Add snapshots #30

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open

[POC] Add snapshots #30

wants to merge 7 commits into from

Conversation

krzysztofreczek
Copy link
Collaborator

@krzysztofreczek krzysztofreczek commented Jan 28, 2023

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 krzysztofreczek linked an issue Jan 28, 2023 that may be closed by this pull request
@ilkamo
Copy link
Collaborator

ilkamo commented Jan 29, 2023

@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,
	}
}

@krzysztofreczek
Copy link
Collaborator Author

krzysztofreczek commented Jan 29, 2023

@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
Copy link
Collaborator Author

@kamy22 @m110 , please check the results of Event/Snapshot separation: #30

@ilkamo
Copy link
Collaborator

ilkamo commented Jan 30, 2023

@krzysztofreczek LGTM 💪

@ilkamo
Copy link
Collaborator

ilkamo commented Feb 5, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Explore potential support for snapshots
2 participants