eventium-memory-0.2.1: src/Eventium/Store/Memory.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module Eventium.Store.Memory
( tvarEventStoreReader,
tvarEventStoreWriter,
tvarEventStoreWriterTagged,
tvarGlobalEventStoreReader,
stateEventStoreReader,
stateEventStoreWriter,
stateEventStoreWriterTagged,
stateGlobalEventStoreReader,
embeddedStateEventStoreReader,
embeddedStateEventStoreWriter,
embeddedStateEventStoreWriterTagged,
embeddedStateGlobalEventStoreReader,
EventMap,
emptyEventMap,
eventMapTVar,
module Eventium.Store.Class,
)
where
import Control.Concurrent.STM
import Control.Monad.State.Class
import Data.Foldable (toList)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe)
import Data.Sequence (Seq, (><))
import qualified Data.Sequence as Seq
import Eventium.Store.Class
import Eventium.UUID
-- | Internal data structure used for the in-memory event stores.
data EventMap event
= EventMap
{ uuidMap :: Map UUID (Seq (VersionedStreamEvent event)),
globalEvents :: Seq (VersionedStreamEvent event)
}
deriving (Show)
-- | What it says on the tin, an initialized empty 'EventMap'
emptyEventMap :: EventMap event
emptyEventMap = EventMap Map.empty Seq.empty
-- | Initialize an 'EventMap' in a 'TVar'
eventMapTVar :: IO (TVar (EventMap event))
eventMapTVar = newTVarIO emptyEventMap
-- | An 'EventStoreReader' that stores events in a 'TVar' and runs in 'STM'.
-- This functions initializes the store by creating the 'TVar' and hooking up
-- the event store API to that 'TVar'.
tvarEventStoreReader :: TVar (EventMap event) -> VersionedEventStoreReader STM event
tvarEventStoreReader tvar = EventStoreReader $ \range -> lookupEventsInRange range <$> readTVar tvar
tvarEventStoreWriter :: TVar (EventMap event) -> VersionedEventStoreWriter STM event
tvarEventStoreWriter tvar = EventStoreWriter $ transactionalExpectedWriteHelper getLatestVersion storeEvents'
where
getLatestVersion uuid = flip latestEventVersion uuid <$> readTVar tvar
storeEvents' uuid events = do
store <- readTVar tvar
let (store', vers) = storeEventMap store uuid events
writeTVar tvar store'
return vers
-- | Like 'tvarEventStoreWriter' but accepts 'TaggedEvent's,
-- preserving the metadata attached to each event.
tvarEventStoreWriterTagged :: TVar (EventMap event) -> VersionedEventStoreWriter STM (TaggedEvent event)
tvarEventStoreWriterTagged tvar = EventStoreWriter $ transactionalExpectedWriteHelper getLatestVersion storeEvents'
where
getLatestVersion uuid = flip latestEventVersion uuid <$> readTVar tvar
storeEvents' uuid events = do
store <- readTVar tvar
let (store', vers) = storeEventMapTagged store uuid events
writeTVar tvar store'
return vers
-- | Analog of 'tvarEventStoreReader' for a 'GlobalEventStoreReader'
tvarGlobalEventStoreReader :: TVar (EventMap event) -> GlobalEventStoreReader STM event
tvarGlobalEventStoreReader tvar = EventStoreReader $ \range -> lookupGlobalEvents range <$> readTVar tvar
-- | Specialized version of 'embeddedStateEventStoreReader' that only contains an
-- 'EventMap' in the state.
stateEventStoreReader ::
(MonadState (EventMap event) m) =>
VersionedEventStoreReader m event
stateEventStoreReader = embeddedStateEventStoreReader id
stateGlobalEventStoreReader ::
(MonadState (EventMap event) m) =>
GlobalEventStoreReader m event
stateGlobalEventStoreReader = embeddedStateGlobalEventStoreReader id
-- | Specialized version of 'embeddedStateEventStoreWriter' that only contains an
-- 'EventMap' in the state.
stateEventStoreWriter ::
(MonadState (EventMap event) m) =>
VersionedEventStoreWriter m event
stateEventStoreWriter = embeddedStateEventStoreWriter id (const id)
-- | Like 'stateEventStoreWriter' but accepts 'TaggedEvent's.
stateEventStoreWriterTagged ::
(MonadState (EventMap event) m) =>
VersionedEventStoreWriter m (TaggedEvent event)
stateEventStoreWriterTagged = embeddedStateEventStoreWriterTagged id (const id)
-- | An 'EventStore' that runs on some 'MonadState' that contains an
-- 'EventMap'. This is useful if you want to include other state in your
-- 'MonadState'.
embeddedStateEventStoreReader ::
(MonadState s m) =>
(s -> EventMap event) ->
VersionedEventStoreReader m event
embeddedStateEventStoreReader getMap = EventStoreReader $ \range -> lookupEventsInRange range <$> gets getMap
embeddedStateEventStoreWriter ::
(MonadState s m) =>
(s -> EventMap event) ->
(s -> EventMap event -> s) ->
VersionedEventStoreWriter m event
embeddedStateEventStoreWriter getMap setMap = EventStoreWriter $ transactionalExpectedWriteHelper getLatestVersion storeEvents'
where
getLatestVersion uuid = flip latestEventVersion uuid <$> gets getMap
storeEvents' uuid events = do
state' <- get
let store = getMap state'
let (store', vers) = storeEventMap store uuid events
put $ setMap state' store'
return vers
-- | Analogous to 'embeddedStateEventStore' for a 'GlobalStreamEventStore'.
embeddedStateGlobalEventStoreReader ::
(MonadState s m) =>
(s -> EventMap event) ->
GlobalEventStoreReader m event
embeddedStateGlobalEventStoreReader getMap = EventStoreReader $ \range -> lookupGlobalEvents range <$> gets getMap
-- | Like 'embeddedStateEventStoreWriter' but accepts 'TaggedEvent's.
embeddedStateEventStoreWriterTagged ::
(MonadState s m) =>
(s -> EventMap event) ->
(s -> EventMap event -> s) ->
VersionedEventStoreWriter m (TaggedEvent event)
embeddedStateEventStoreWriterTagged getMap setMap = EventStoreWriter $ transactionalExpectedWriteHelper getLatestVersion storeEvents'
where
getLatestVersion uuid = flip latestEventVersion uuid <$> gets getMap
storeEvents' uuid events = do
state' <- get
let store = getMap state'
let (store', vers) = storeEventMapTagged store uuid events
put $ setMap state' store'
return vers
lookupEventMapRaw :: EventMap event -> UUID -> Seq (VersionedStreamEvent event)
lookupEventMapRaw (EventMap uuidMap _) uuid = fromMaybe Seq.empty $ Map.lookup uuid uuidMap
lookupEventsInRange :: QueryRange UUID EventVersion -> EventMap event -> [VersionedStreamEvent event]
lookupEventsInRange (QueryRange uuid start limit) store = toList $ filterEventsByRange start' limit' 0 rawEvents
where
start' = (\(EventVersion n) -> n) <$> start
limit' = (\(EventVersion n) -> n) <$> limit
rawEvents = lookupEventMapRaw store uuid
filterEventsByRange :: QueryStart Int -> QueryLimit Int -> Int -> Seq event -> Seq event
filterEventsByRange queryStart queryLimit defaultStart events =
let (start', events') =
case queryStart of
StartFromBeginning -> (defaultStart, events)
StartQueryAt start -> (start, Seq.drop (start - defaultStart) events)
events'' =
case queryLimit of
NoQueryLimit -> events'
MaxNumberOfEvents num -> Seq.take num events'
StopQueryAt stop -> Seq.take (stop - start' + 1) events'
in events''
latestEventVersion :: EventMap event -> UUID -> EventVersion
latestEventVersion store uuid = EventVersion $ Seq.length (lookupEventMapRaw store uuid) - 1
lookupGlobalEvents :: QueryRange () SequenceNumber -> EventMap event -> [GlobalStreamEvent event]
lookupGlobalEvents (QueryRange () start limit) (EventMap _ globalEvents) = events'
where
start' = (\(SequenceNumber n) -> n) <$> start
limit' = (\(SequenceNumber n) -> n) <$> limit
events = toList $ filterEventsByRange start' limit' 1 globalEvents
events' = zipWith (\s e -> StreamEvent () s e.metadata e) [startingSeqNum ..] events
startingSeqNum =
case start of
StartFromBeginning -> 1
(StartQueryAt startSeq) -> startSeq
storeEventMap ::
EventMap event -> UUID -> [event] -> (EventMap event, EventVersion)
storeEventMap store@(EventMap uuidMap globalEvents) uuid events =
let versStart = latestEventVersion store uuid
streamEvents = zipWith (\v e -> StreamEvent uuid v (emptyMetadata "") e) [versStart + 1 ..] events
newMap = Map.insertWith (flip (><)) uuid (Seq.fromList streamEvents) uuidMap
globalEvents' = globalEvents >< Seq.fromList streamEvents
in (EventMap newMap globalEvents', versStart + EventVersion (length events))
storeEventMapTagged ::
EventMap event -> UUID -> [TaggedEvent event] -> (EventMap event, EventVersion)
storeEventMapTagged store@(EventMap uuidMap globalEvents) uuid taggedEvents =
let versStart = latestEventVersion store uuid
streamEvents =
zipWith
(\v (TaggedEvent meta e) -> StreamEvent uuid v meta e)
[versStart + 1 ..]
taggedEvents
newMap = Map.insertWith (flip (><)) uuid (Seq.fromList streamEvents) uuidMap
globalEvents' = globalEvents >< Seq.fromList streamEvents
in (EventMap newMap globalEvents', versStart + EventVersion (length taggedEvents))