@@ -18,7 +18,8 @@ type NotificationProjector struct {
1818
1919 storage driverSQL.ProjectionStorage
2020
21- decodeProjectionState driverSQL.ProjectionStateDecoder
21+ projectionStateInit driverSQL.ProjectionStateInitializer
22+ projectionStateDecode driverSQL.ProjectionStateDecoder
2223 handlers map [string ]goengine.MessageHandler
2324
2425 eventLoader driverSQL.EventStreamLoader
@@ -31,23 +32,26 @@ type NotificationProjector struct {
3132func NewNotificationProjector (
3233 db * sql.DB ,
3334 storage driverSQL.ProjectionStorage ,
34- acquireUnmarshalState driverSQL.ProjectionStateDecoder ,
35+ projectionStateInit driverSQL.ProjectionStateInitializer ,
36+ projectionStateDecode driverSQL.ProjectionStateDecoder ,
3537 eventHandlers map [string ]goengine.MessageHandler ,
3638 eventLoader driverSQL.EventStreamLoader ,
3739 resolver goengine.MessagePayloadResolver ,
3840 logger goengine.Logger ,
3941) (* NotificationProjector , error ) {
4042 switch {
4143 case db == nil :
42- return nil , errors . New ("db cannot be nil " )
44+ return nil , goengine . InvalidArgumentError ("db" )
4345 case storage == nil :
44- return nil , errors .New ("storage cannot be nil" )
46+ return nil , goengine .InvalidArgumentError ("storage" )
47+ case projectionStateInit == nil :
48+ return nil , goengine .InvalidArgumentError ("projectionStateInit" )
4549 case len (eventHandlers ) == 0 :
46- return nil , errors . New ("eventHandlers cannot be empty " )
50+ return nil , goengine . InvalidArgumentError ("eventHandlers" )
4751 case eventLoader == nil :
48- return nil , errors . New ("eventLoader cannot be nil " )
52+ return nil , goengine . InvalidArgumentError ("eventLoader" )
4953 case resolver == nil :
50- return nil , errors . New ("resolver cannot be nil " )
54+ return nil , goengine . InvalidArgumentError ("resolver" )
5155 }
5256
5357 if logger == nil {
@@ -57,7 +61,8 @@ func NewNotificationProjector(
5761 return & NotificationProjector {
5862 db : db ,
5963 storage : storage ,
60- decodeProjectionState : acquireUnmarshalState ,
64+ projectionStateInit : projectionStateInit ,
65+ projectionStateDecode : projectionStateDecode ,
6166 handlers : wrapProjectionHandlers (eventHandlers ),
6267 eventLoader : eventLoader ,
6368 resolver : resolver ,
@@ -114,21 +119,8 @@ func (s *NotificationProjector) project(
114119 }
115120 defer releaseLock ()
116121
117- // Unmarshal the projection state
118- var projectionState interface {}
119- if s .decodeProjectionState != nil {
120- projectionState , err = s .decodeProjectionState (rawState .ProjectionState )
121- if err != nil {
122- return err
123- }
124- }
125- state := driverSQL.ProjectionState {
126- Position : rawState .Position ,
127- ProjectionState : projectionState ,
128- }
129-
130122 // Load the event stream
131- eventStream , err := s .eventLoader (ctx , streamConn , notification , state )
123+ eventStream , err := s .eventLoader (ctx , streamConn , notification , rawState . Position )
132124 if err != nil {
133125 return err
134126 }
@@ -139,7 +131,7 @@ func (s *NotificationProjector) project(
139131 }()
140132
141133 // project event stream
142- if err := s .projectStream (ctx , conn , notification , state , eventStream ); err != nil {
134+ if err := s .projectStream (ctx , conn , notification , rawState , eventStream ); err != nil {
143135 return err
144136 }
145137
@@ -149,11 +141,15 @@ func (s *NotificationProjector) project(
149141// projectStream will project the events in the event stream and persist the state after the projection
150142func (s * NotificationProjector ) projectStream (
151143 ctx context.Context ,
152- conn * sql. Conn ,
144+ conn driverSQL. Execer ,
153145 notification * driverSQL.ProjectionNotification ,
154- state driverSQL.ProjectionState ,
146+ rawState * driverSQL.ProjectionRawState ,
155147 stream goengine.EventStream ,
156148) error {
149+ var (
150+ state driverSQL.ProjectionState
151+ stateAcquired bool
152+ )
157153 for stream .Next () {
158154 // Check if the context is expired
159155 select {
@@ -167,7 +163,6 @@ func (s *NotificationProjector) projectStream(
167163 if err != nil {
168164 return err
169165 }
170- state .Position = msgNumber
171166
172167 // Resolve the payload event name
173168 eventName , err := s .resolver .ResolveName (msg .Payload ())
@@ -184,7 +179,17 @@ func (s *NotificationProjector) projectStream(
184179 continue
185180 }
186181
182+ // Acquire the state if we have none
183+ if ! stateAcquired {
184+ state , err = s .acquireProjectState (ctx , rawState )
185+ if err != nil {
186+ return err
187+ }
188+ stateAcquired = true
189+ }
190+
187191 // Execute the handler
192+ state .Position = msgNumber
188193 state .ProjectionState , err = handler (ctx , state .ProjectionState , msg )
189194 if err != nil {
190195 return err
@@ -199,6 +204,24 @@ func (s *NotificationProjector) projectStream(
199204 return stream .Err ()
200205}
201206
207+ func (s * NotificationProjector ) acquireProjectState (ctx context.Context , rawState * driverSQL.ProjectionRawState ) (driverSQL.ProjectionState , error ) {
208+ state := driverSQL.ProjectionState {
209+ Position : rawState .Position ,
210+ }
211+
212+ // Decode or initialize projection state
213+ var err error
214+ if rawState .Position == 0 {
215+ // This is the fist time the projection runs so initialize the state
216+ state .ProjectionState , err = s .projectionStateInit (ctx )
217+ } else if s .projectionStateDecode != nil {
218+ // Unmarshal the projection state
219+ state .ProjectionState , err = s .projectionStateDecode (rawState .ProjectionState )
220+ }
221+
222+ return state , err
223+ }
224+
202225// wrapProjectionHandlers wraps the projection handlers so that any error or panic is caught and returned
203226func wrapProjectionHandlers (handlers map [string ]goengine.MessageHandler ) map [string ]goengine.MessageHandler {
204227 res := make (map [string ]goengine.MessageHandler , len (handlers ))
0 commit comments