forked from looplab/eventhorizon
/
eventstore_postgres.go
239 lines (201 loc) · 5.6 KB
/
eventstore_postgres.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package eventhorizon
import (
"database/sql"
"encoding/json"
"errors"
"time"
"github.com/doubledutch/lager"
"github.com/jmoiron/sqlx"
)
// ErrCouldNotSaveEvent returned when an event could not be saved.
var ErrCouldNotSaveEvent = errors.New("could not save event")
// PostgresEventStore implements an EventStore for Postgres.
type PostgresEventStore struct {
eventBus EventBus
db *sqlx.DB
factories map[string]func() Event
lgr lager.ContextLager
}
type postgresAggregateRecord struct {
AggregateID string `db:"id"`
Version int
}
type postgresEventRecord struct {
AggregrateID string
Type string
Version int
Timestamp time.Time
Data []byte
}
// NewPostgresEventStore creates a new PostgresEventStore.
func NewPostgresEventStore(eventBus EventBus, conn string) (*PostgresEventStore, error) {
lgr := lager.Child()
db, err := initDB(conn)
if err != nil {
lgr.WithError(err).Errorf("Unable to initialize database")
return nil, err
}
if _, err = db.Exec(`
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public;
CREATE TABLE IF NOT EXISTS aggregrates (
id uuid NOT NULL,
version int NOT NULL
);
CREATE TABLE IF NOT EXISTS events(
aggregrateid uuid NOT NULL,
type text,
version int,
timestamp timestamp without time zone default (now() at time zone 'utc'),
data jsonb
)
`); err != nil {
db.Close()
lgr.WithError(err).Errorf("Unable to initialize tables")
return nil, ErrCouldNotCreateTables
}
return &PostgresEventStore{
eventBus: eventBus,
db: db,
factories: make(map[string]func() Event),
lgr: lgr,
}, nil
}
// Save appends all events in the event stream to the store.
func (s *PostgresEventStore) Save(events []Event) error {
if len(events) == 0 {
return ErrNoEventsToAppend
}
tx, err := s.db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
for _, event := range events {
// Get an existing aggregate, if any
var existing []postgresAggregateRecord
err := s.db.Select(&existing,
`SELECT * FROM aggregrates WHERE id=$1 LIMIT 2`, event.AggregateID())
if (err != nil && err != sql.ErrNoRows) || len(existing) > 1 {
return ErrCouldNotLoadAggregate
}
// Marshal event data
b, err := json.Marshal(event)
if err != nil {
return ErrCouldNotMarshalEvent
}
// Create the event record with timestamp
r := &postgresEventRecord{
AggregrateID: event.AggregateID(),
Type: event.EventType(),
Version: 1,
Timestamp: time.Now(),
Data: b,
}
if len(existing) == 0 {
aggregrate := postgresAggregateRecord{
AggregateID: event.AggregateID(),
Version: 1,
}
// Save aggregrate
_, err = s.db.NamedExec(
`INSERT INTO aggregrates (id,version)
VALUES (:id,:version)`, aggregrate)
if err != nil {
return ErrCouldNotSaveAggregate
}
_, err = s.db.NamedExec(
`INSERT INTO events (aggregrateid,type,version,timestamp,data)
VALUES (:aggregrateid,:type,:version,:timestamp,:data)`, r)
if err != nil {
return ErrCouldNotSaveEvent
}
} else {
// Increment record version before inserting.
version := existing[0].Version + 1
_, err = s.db.NamedExec(
`UPDATE aggregrates SET version=:version WHERE id=:id`, existing[0])
if err != nil {
return ErrCouldNotSaveAggregate
}
r.Version = version
_, err = s.db.NamedExec(
`INSERT INTO events (aggregrateid,type,version,timestamp,data)
VALUES (:aggregrateid,:type,:version,:timestamp,:data)`, r)
if err != nil {
return ErrCouldNotSaveEvent
}
}
}
if err := tx.Commit(); err != nil {
return err
}
for _, event := range events {
// Publish event on the bus.
if s.eventBus != nil {
s.eventBus.PublishEvent(event)
}
}
return nil
}
// Load loads all events for the aggregate id from the store.
func (s *PostgresEventStore) Load(id string) ([]Event, error) {
var aggregrate postgresAggregateRecord
err := s.db.Get(&aggregrate,
`SELECT * FROM aggregrates WHERE id=$1 LIMIT 1`, id)
if err != nil {
return nil, ErrNoEventsFound
}
var rawEvents []*postgresEventRecord
err = s.db.Select(&rawEvents,
`SELECT * FROM events WHERE aggregrateid=$1 ORDER BY timestamp ASC`, id)
if err != nil {
return nil, ErrNoEventsFound
}
events := make([]Event, len(rawEvents))
for i, rawEvent := range rawEvents {
// Get the registered factory function for creating events.
f, ok := s.factories[rawEvent.Type]
if !ok {
return nil, ErrEventNotRegistered
}
// Unmarshal JSON
event := f()
if err := json.Unmarshal(rawEvent.Data, event); err != nil {
return nil, ErrCouldNotUnmarshalEvent
}
if events[i], ok = event.(Event); !ok {
return nil, ErrInvalidEvent
}
rawEvent.Data = nil
}
if len(events) == 0 {
events = nil
}
return events, nil
}
// RegisterEventType registers an event factory for a event type. The factory is
// used to create concrete event types when loading from the database.
//
// An example would be:
// eventStore.RegisterEventType(&MyEvent{}, func() Event { return &MyEvent{} })
func (s *PostgresEventStore) RegisterEventType(event Event, factory func() Event) error {
if _, ok := s.factories[event.EventType()]; ok {
return ErrHandlerAlreadySet
}
s.factories[event.EventType()] = factory
return nil
}
// Clear clears the postgres storage.
func (s *PostgresEventStore) Clear() error {
if _, err := s.db.Exec(`DELETE FROM events`); err != nil {
return ErrCouldNotClearDB
}
if _, err := s.db.Exec(`DELETE FROM aggregrates`); err != nil {
return ErrCouldNotClearDB
}
return nil
}
// Close closes the postgres db connection.
func (s *PostgresEventStore) Close() error {
return s.db.Close()
}