module Eventful.Projection
( Projection (..)
, latestProjection
, allProjections
, StreamProjection (..)
, streamProjection
, getLatestProjection
, GloballyOrderedProjection (..)
, globallyOrderedProjection
, globallyOrderedProjectionEventHandler
, getLatestGlobalProjection
, serializedProjection
, projectionMapMaybe
)
where
import Data.Foldable (foldl')
import Data.Functor.Contravariant
import Data.List (scanl')
import Eventful.Serializer
import Eventful.Store.Class
import Eventful.UUID
data Projection state event
= Projection
{ projectionSeed :: state
, projectionEventHandler :: state -> event -> state
}
instance Contravariant (Projection state) where
contramap f (Projection seed handler) = Projection seed handler'
where
handler' state event = handler state (f event)
latestProjection :: (Foldable t) => Projection state event -> t event -> state
latestProjection (Projection seed handler) = foldl' handler seed
allProjections :: Projection state event -> [event] -> [state]
allProjections (Projection seed handler) = scanl' handler seed
data StreamProjection state event
= StreamProjection
{ streamProjectionProjection :: Projection state event
, streamProjectionUuid :: !UUID
, streamProjectionVersion :: EventVersion
, streamProjectionState :: !state
}
streamProjection
:: Projection state event
-> UUID
-> StreamProjection state event
streamProjection projection@Projection{..} uuid =
StreamProjection projection uuid (1) projectionSeed
getLatestProjection
:: (Monad m)
=> EventStore event m
-> StreamProjection state event
-> m (StreamProjection state event)
getLatestProjection store projection@StreamProjection{..} = do
events <- getEvents store streamProjectionUuid (eventsStartingAt $ streamProjectionVersion + 1)
let
latestVersion = newEventVersion events
latestState = foldl' (projectionEventHandler streamProjectionProjection) streamProjectionState $ storedEventEvent <$> events
return $
projection
{ streamProjectionVersion = latestVersion
, streamProjectionState = latestState
}
where
newEventVersion [] = streamProjectionVersion
newEventVersion es = maximum $ storedEventVersion <$> es
data GloballyOrderedProjection state serialized
= GloballyOrderedProjection
{ globallyOrderedProjectionProjection :: !(Projection state (GloballyOrderedEvent serialized))
, globallyOrderedProjectionSequenceNumber :: !SequenceNumber
, globallyOrderedProjectionState :: !state
}
globallyOrderedProjection
:: Projection state (GloballyOrderedEvent serialized)
-> GloballyOrderedProjection state serialized
globallyOrderedProjection projection@Projection{..} =
GloballyOrderedProjection projection 0 projectionSeed
globallyOrderedProjectionEventHandler
:: GloballyOrderedProjection state serialized
-> GloballyOrderedEvent serialized
-> GloballyOrderedProjection state serialized
globallyOrderedProjectionEventHandler GloballyOrderedProjection{..} event@GloballyOrderedEvent{..} =
let
Projection{..} = globallyOrderedProjectionProjection
seqNum = globallyOrderedEventSequenceNumber
state' = projectionEventHandler globallyOrderedProjectionState event
in GloballyOrderedProjection globallyOrderedProjectionProjection seqNum state'
getLatestGlobalProjection
:: (Monad m)
=> GloballyOrderedEventStore serialized m
-> GloballyOrderedProjection state serialized
-> m (GloballyOrderedProjection state serialized)
getLatestGlobalProjection store globalProjection@GloballyOrderedProjection{..} = do
events <- getSequencedEvents store (eventsStartingAt $ globallyOrderedProjectionSequenceNumber + 1)
return $ foldl' globallyOrderedProjectionEventHandler globalProjection events
serializedProjection
:: Projection state event
-> Serializer event serialized
-> Projection state serialized
serializedProjection proj Serializer{..} = projectionMapMaybe deserialize proj
projectionMapMaybe
:: (eventB -> Maybe eventA)
-> Projection state eventA
-> Projection state eventB
projectionMapMaybe f (Projection seed handler) = Projection seed handler'
where
handler' state = maybe state (handler state) . f