packages feed

eventful-memory 0.1.2 → 0.1.3

raw patch · 6 files changed

+295/−58 lines, 6 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Eventful.Store.Memory: memoryEventStore :: IO (EventStore serialized STM, GloballyOrderedEventStore serialized STM)
+ Eventful.ProjectionCache.Memory: embeddedStateProjectionCache :: (MonadState s m, Ord key) => (s -> ProjectionMap key orderKey serialized) -> (s -> ProjectionMap key orderKey serialized -> s) -> ProjectionCache key orderKey serialized m
+ Eventful.ProjectionCache.Memory: emptyProjectionMap :: ProjectionMap key orderKey serialized
+ Eventful.ProjectionCache.Memory: projectionMapTVar :: IO (TVar (ProjectionMap key orderKey serialized))
+ Eventful.ProjectionCache.Memory: tvarProjectionCache :: (Ord key) => TVar (ProjectionMap key orderKey serialized) -> ProjectionCache key orderKey serialized STM
+ Eventful.ProjectionCache.Memory: type ProjectionMap key orderKey serialized = Map key (orderKey, serialized)
+ Eventful.Store.Memory: embeddedStateEventStore :: (MonadState s m) => (s -> EventMap serialized) -> (s -> EventMap serialized -> s) -> EventStore serialized m
+ Eventful.Store.Memory: embeddedStateGloballyOrderedEventStore :: (MonadState s m) => (s -> EventMap serialized) -> GloballyOrderedEventStore serialized m
+ Eventful.Store.Memory: eventMapTVar :: IO (TVar (EventMap serialized))
+ Eventful.Store.Memory: tvarEventStore :: TVar (EventMap serialized) -> EventStore serialized STM
+ Eventful.Store.Memory: tvarGloballyOrderedEventStore :: TVar (EventMap serialized) -> GloballyOrderedEventStore serialized STM

Files

eventful-memory.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack  name:           eventful-memory-version:        0.1.2+version:        0.1.3 synopsis:       In-memory implementations for eventful description:    In-memory implementations for eventful category:       Database,Eventsourcing@@ -36,6 +36,7 @@     , safe     , stm   exposed-modules:+      Eventful.ProjectionCache.Memory       Eventful.ReadModel.Memory       Eventful.Store.Memory   default-language: Haskell2010@@ -58,8 +59,11 @@     , HUnit     , eventful-test-helpers   other-modules:+      Eventful.ProjectionCache.MemorySpec       Eventful.Store.MemorySpec       HLint+      MemoryTestImport+      Eventful.ProjectionCache.Memory       Eventful.ReadModel.Memory       Eventful.Store.Memory   default-language: Haskell2010@@ -79,6 +83,8 @@     , stm     , hlint   other-modules:+      Eventful.ProjectionCache.MemorySpec       Eventful.Store.MemorySpec+      MemoryTestImport       Spec   default-language: Haskell2010
+ src/Eventful/ProjectionCache/Memory.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RecordWildCards #-}++module Eventful.ProjectionCache.Memory+  ( ProjectionMap+  , emptyProjectionMap+  , projectionMapTVar+  , tvarProjectionCache+  , embeddedStateProjectionCache+  , module Eventful.ProjectionCache.Types+  ) where++import Control.Concurrent.STM+import Control.Monad.State.Class hiding (state)+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map++import Eventful.ProjectionCache.Types++-- | A 'ProjectionMap' just stores the latest snapshot for each UUID.+type ProjectionMap key orderKey serialized = Map key (orderKey, serialized)++emptyProjectionMap :: ProjectionMap key orderKey serialized+emptyProjectionMap = Map.empty++projectionMapTVar :: IO (TVar (ProjectionMap key orderKey serialized))+projectionMapTVar = newTVarIO emptyProjectionMap++storeProjectionInMap+  :: (Ord key)+  => key+  -> orderKey+  -> serialized+  -> ProjectionMap key orderKey serialized+  -> ProjectionMap key orderKey serialized+storeProjectionInMap uuid version state = Map.insert uuid (version, state)++-- | A 'ProjectionCache' that uses a 'TVar' and runs in 'STM'.+tvarProjectionCache+  :: (Ord key)+  => TVar (ProjectionMap key orderKey serialized)+  -> ProjectionCache key orderKey serialized STM+tvarProjectionCache tvar =+  let+    storeProjectionSnapshot uuid version projState = modifyTVar' tvar (storeProjectionInMap uuid version projState)+    loadProjectionSnapshot uuid = Map.lookup uuid <$> readTVar tvar+  in ProjectionCache{..}++-- | A 'ProjectionCache' for some 'MonadState' that contains a 'ProjectionMap'.+embeddedStateProjectionCache+  :: (MonadState s m, Ord key)+  => (s -> ProjectionMap key orderKey serialized)+  -> (s -> ProjectionMap key orderKey serialized -> s)+  -> ProjectionCache key orderKey serialized m+embeddedStateProjectionCache getMap setMap =+  let+    storeProjectionSnapshot uuid version projState = modify' (storeProjectionSnapshot' uuid version projState)+    loadProjectionSnapshot uuid = Map.lookup uuid <$> gets getMap+  in ProjectionCache{..}+  where+    storeProjectionSnapshot' uuid version projState state =+      setMap state $ storeProjectionInMap uuid version projState $ getMap state
src/Eventful/Store/Memory.hs view
@@ -2,11 +2,15 @@ {-# LANGUAGE RecordWildCards #-}  module Eventful.Store.Memory-  ( memoryEventStore+  ( tvarEventStore+  , tvarGloballyOrderedEventStore   , stateEventStore   , stateGloballyOrderedEventStore+  , embeddedStateEventStore+  , embeddedStateGloballyOrderedEventStore   , EventMap   , emptyEventMap+  , eventMapTVar   , module Eventful.Store.Class   ) where @@ -36,58 +40,109 @@ emptyEventMap :: EventMap serialized emptyEventMap = EventMap Map.empty 0 +-- | Initialize an 'EventMap' in a 'TVar'+eventMapTVar :: IO (TVar (EventMap serialized))+eventMapTVar = newTVarIO emptyEventMap+ -- | An 'EventStore' 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'.-memoryEventStore :: IO (EventStore serialized STM, GloballyOrderedEventStore serialized STM)-memoryEventStore = do-  tvar <- newTVarIO emptyEventMap+tvarEventStore :: TVar (EventMap serialized) -> EventStore serialized STM+tvarEventStore tvar =   let     getLatestVersion uuid = flip latestEventVersion uuid <$> readTVar tvar-    getEvents uuid vers = toList . (\s -> lookupEventsFromVersion s uuid vers) <$> readTVar tvar+    getEvents uuid range = (\s -> lookupEventsInRange s uuid range) <$> readTVar tvar     storeEvents' uuid events = modifyTVar' tvar (\store -> storeEventMap store uuid events)     storeEvents = transactionalExpectedWriteHelper getLatestVersion storeEvents'-    getSequencedEvents seqNum = flip lookupEventMapSeq seqNum <$> readTVar tvar-  return (EventStore{..}, GloballyOrderedEventStore{..})+  in EventStore{..} --- | An 'EventStore' that runs in a 'MonadState' monad.+-- | Analog of 'tvarEventStore' for a 'GloballyOrderedEventStore'+tvarGloballyOrderedEventStore :: TVar (EventMap serialized) -> GloballyOrderedEventStore serialized STM+tvarGloballyOrderedEventStore tvar =+  let+    getSequencedEvents range = flip lookupEventMapRange range <$> readTVar tvar+  in GloballyOrderedEventStore{..}++-- | Specialized version of 'embeddedStateEventStore' that only contains an+-- 'EventMap' in the state. stateEventStore   :: (MonadState (EventMap serialized) m)   => EventStore serialized m-stateEventStore =+stateEventStore = embeddedStateEventStore id (flip const)++-- | An 'EventStore' that runs on some 'MonadState' that contains an+-- 'EventMap'. This is useful if you want to include other state in your+-- 'MonadState'.+embeddedStateEventStore+  :: (MonadState s m)+  => (s -> EventMap serialized)+  -> (s -> EventMap serialized -> s)+  -> EventStore serialized m+embeddedStateEventStore getMap setMap =   let-    getLatestVersion uuid = flip latestEventVersion uuid <$> get-    getEvents uuid vers = toList . (\s -> lookupEventsFromVersion s uuid vers) <$> get-    storeEvents' uuid events = modify (\store -> storeEventMap store uuid events)+    getLatestVersion uuid = flip latestEventVersion uuid <$> gets getMap+    getEvents uuid range = (\s -> lookupEventsInRange s uuid range) <$> gets getMap+    storeEvents' uuid events = modify' (modifyStore uuid events)     storeEvents = transactionalExpectedWriteHelper getLatestVersion storeEvents'   in EventStore{..}+  where+    modifyStore uuid events state' =+      let+        store = getMap state'+        store' = storeEventMap store uuid events+      in setMap state' store' --- | A 'GloballyOrderedEventStore' that runs in a 'MonadState' monad.+-- | Analogous to 'stateEventStore' for a 'GloballyOrderedEventStore'. stateGloballyOrderedEventStore   :: (MonadState (EventMap serialized) m)   => GloballyOrderedEventStore serialized m-stateGloballyOrderedEventStore =+stateGloballyOrderedEventStore = embeddedStateGloballyOrderedEventStore id++-- | Analogous to 'embeddedStateEventStore' for a 'GloballyOrderedEventStore'.+embeddedStateGloballyOrderedEventStore+  :: (MonadState s m)+  => (s -> EventMap serialized)+  -> GloballyOrderedEventStore serialized m+embeddedStateGloballyOrderedEventStore getMap =   let-    getSequencedEvents seqNum = flip lookupEventMapSeq seqNum <$> get+    getSequencedEvents range = flip lookupEventMapRange range <$> gets getMap   in GloballyOrderedEventStore{..}  lookupEventMapRaw :: EventMap serialized -> UUID -> Seq (StoredEvent serialized) lookupEventMapRaw (EventMap uuidMap _) uuid =   fmap globallyOrderedEventToStoredEvent $ fromMaybe Seq.empty $ Map.lookup uuid uuidMap -lookupEventsFromVersion :: EventMap serialized -> UUID -> Maybe EventVersion -> Seq (StoredEvent serialized)-lookupEventsFromVersion store uuid Nothing = lookupEventMapRaw store uuid-lookupEventsFromVersion store uuid (Just (EventVersion vers)) = Seq.drop vers $ lookupEventMapRaw store uuid+lookupEventsInRange :: EventMap serialized -> UUID -> EventStoreQueryRange EventVersion -> [StoredEvent serialized]+lookupEventsInRange store uuid range = filterEventsByRange range' 0 rawEvents+  where+    range' = unEventVersion <$> range+    rawEvents = toList $ lookupEventMapRaw store uuid +filterEventsByRange :: EventStoreQueryRange Int -> Int -> [event] -> [event]+filterEventsByRange EventStoreQueryRange{..} defaultStart events =+  let+    (start', events') =+      case eventStoreQueryRangeStart of+        StartFromBeginning -> (defaultStart, events)+        StartQueryAt start -> (start, drop (start - defaultStart) events)+    events'' =+      case eventStoreQueryRangeLimit of+        NoQueryLimit -> events'+        MaxNumberOfEvents num -> take num events'+        StopQueryAt stop -> take (stop - start' + 1) events'+  in events''+ latestEventVersion :: EventMap serialized -> UUID -> EventVersion latestEventVersion store uuid = EventVersion $ Seq.length (lookupEventMapRaw store uuid) - 1 -lookupEventMapSeq :: EventMap serialized -> SequenceNumber -> [GloballyOrderedEvent serialized]-lookupEventMapSeq (EventMap uuidMap _) seqNum =-  sortOn globallyOrderedEventSequenceNumber $-  filter ((> seqNum) . globallyOrderedEventSequenceNumber) $-  concat $-  toList <$> toList uuidMap+lookupEventMapRange :: EventMap serialized -> EventStoreQueryRange SequenceNumber -> [GloballyOrderedEvent serialized]+lookupEventMapRange (EventMap uuidMap _) range = filterEventsByRange range' 1 rawEvents+  where+    range' = unSequenceNumber <$> range+    rawEvents =+      sortOn globallyOrderedEventSequenceNumber $+      concat $+      toList <$> toList uuidMap  storeEventMap   :: EventMap serialized -> UUID -> [serialized] -> EventMap serialized
+ tests/Eventful/ProjectionCache/MemorySpec.hs view
@@ -0,0 +1,54 @@+module Eventful.ProjectionCache.MemorySpec (spec) where++import Control.Concurrent.STM+import Control.Monad.State.Strict+import Test.Hspec++import Eventful.ProjectionCache.Memory+import Eventful.Store.Memory+import Eventful.TestHelpers++import MemoryTestImport++spec :: Spec+spec = do+  describe "TVar projection cache" $ do+    streamProjectionCacheSpec tvarStreamProjectionCacheRunner+    globallyOrderedProjectionCacheSpec tvarGloballyOrderedProjectionCacheRunner++  describe "MonadState embedded memory projection cache" $ do+    streamProjectionCacheSpec stateStreamProjectionCacheRunner+    globallyOrderedProjectionCacheSpec stateGloballyOrderedProjectionCacheRunner++tvarStreamProjectionCacheRunner :: StreamProjectionCacheRunner STM+tvarStreamProjectionCacheRunner = StreamProjectionCacheRunner $ \action -> do+  eventTVar <- eventMapTVar+  projTVar <- projectionMapTVar+  let+    store = tvarEventStore eventTVar+    cache = tvarProjectionCache projTVar+  atomically $ action store cache++tvarGloballyOrderedProjectionCacheRunner :: GloballyOrderedProjectionCacheRunner STM+tvarGloballyOrderedProjectionCacheRunner = GloballyOrderedProjectionCacheRunner $ \action -> do+  eventTVar <- eventMapTVar+  projTVar <- projectionMapTVar+  let+    store = tvarEventStore eventTVar+    globalStore = tvarGloballyOrderedEventStore eventTVar+    cache = tvarProjectionCache projTVar+  atomically $ action store globalStore cache++stateStreamProjectionCacheRunner :: StreamProjectionCacheRunner (StateT (StreamEmbeddedState Counter CounterEvent) IO)+stateStreamProjectionCacheRunner = StreamProjectionCacheRunner $ \action -> evalStateT (action store cache) emptyEmbeddedState+  where+    store = embeddedStateEventStore embeddedEventMap setEventMap+    cache = embeddedStateProjectionCache embeddedProjectionMap setProjectionMap++stateGloballyOrderedProjectionCacheRunner :: GloballyOrderedProjectionCacheRunner (StateT (GloballyOrderedEmbeddedState Counter CounterEvent Text) IO)+stateGloballyOrderedProjectionCacheRunner =+  GloballyOrderedProjectionCacheRunner $ \action -> evalStateT (action store globalStore cache) emptyEmbeddedState+  where+    store = embeddedStateEventStore embeddedEventMap setEventMap+    globalStore = embeddedStateGloballyOrderedEventStore embeddedEventMap+    cache = embeddedStateProjectionCache embeddedProjectionMap setProjectionMap
tests/Eventful/Store/MemorySpec.hs view
@@ -4,50 +4,61 @@ import Control.Monad.State.Strict import Test.Hspec -import Eventful.Serializer import Eventful.Store.Memory import Eventful.TestHelpers +import MemoryTestImport+ spec :: Spec spec = do-  describe "TVar memory event store with Dynamic serialized type" $ do-    eventStoreSpec makeTVarDynamicStore (const atomically)-    sequencedEventStoreSpec makeTVarDynamicGlobalStore (const atomically)-   describe "TVar memory event store with actual event type" $ do-    eventStoreSpec makeTVarStore (const atomically)-    sequencedEventStoreSpec makeTVarGlobalStore (const atomically)+    eventStoreSpec tvarRunner+    sequencedEventStoreSpec tvarGlobalRunner -  describe "MonadState memory event store with actual event type" $ do-    eventStoreSpec makeStateStore (const (flip evalStateT emptyEventMap))-    sequencedEventStoreSpec makeStateGlobalStore (const (flip evalStateT emptyEventMap))+  describe "TVar memory event store with Dynamic serialized type" $ do+    eventStoreSpec tvarDynamicRunner+    sequencedEventStoreSpec tvarDynamicGlobalRunner -makeTVarStore :: IO (EventStore serialized STM, ())-makeTVarStore = do-  (store, _, ()) <- makeTVarGlobalStore-  return (store, ())+  describe "MonadState memory event store with actual event type" $ do+    eventStoreSpec stateStoreRunner+    sequencedEventStoreSpec stateStoreGlobalRunner -makeTVarGlobalStore :: IO (EventStore serialized STM, GloballyOrderedEventStore serialized STM, ())-makeTVarGlobalStore = do-  (store, globalStore) <- memoryEventStore-  return (store, globalStore, ())+  describe "MonadState embedded memory event store with actual event type" $ do+    eventStoreSpec embeddedStateStoreRunner+    sequencedEventStoreSpec embeddedStateStoreGlobalRunner -makeTVarDynamicStore :: IO (EventStore CounterEvent STM, ())-makeTVarDynamicStore = do-  (store, _, ()) <- makeTVarDynamicGlobalStore-  return (store, ())+tvarRunner :: EventStoreRunner STM+tvarRunner = EventStoreRunner $ \action -> (tvarEventStore <$> eventMapTVar) >>= atomically . action -makeTVarDynamicGlobalStore :: IO (EventStore CounterEvent STM, GloballyOrderedEventStore CounterEvent STM, ())-makeTVarDynamicGlobalStore = do-  (store, globalStore) <- memoryEventStore+tvarGlobalRunner :: GloballyOrderedEventStoreRunner STM+tvarGlobalRunner = GloballyOrderedEventStoreRunner $ \action -> do+  tvar <- eventMapTVar   let-    store' = serializedEventStore dynamicSerializer store-    globalStore' = serializedGloballyOrderedEventStore dynamicSerializer globalStore-  return (store', globalStore', ())+    store = tvarEventStore tvar+    globalStore = tvarGloballyOrderedEventStore tvar+  atomically $ action store globalStore -makeStateStore :: IO (EventStore serialized (StateT (EventMap serialized) IO), ())-makeStateStore = return (stateEventStore, ())+tvarDynamicRunner :: EventStoreRunner STM+tvarDynamicRunner = EventStoreRunner $ \action -> (fst <$> makeDynamicTVarStore) >>= atomically . action -makeStateGlobalStore-  :: IO (EventStore serialized (StateT (EventMap serialized) IO), GloballyOrderedEventStore serialized (StateT (EventMap serialized) IO), ())-makeStateGlobalStore = return (stateEventStore, stateGloballyOrderedEventStore, ())+tvarDynamicGlobalRunner :: GloballyOrderedEventStoreRunner STM+tvarDynamicGlobalRunner = GloballyOrderedEventStoreRunner $ \action -> makeDynamicTVarStore >>= atomically . uncurry action++stateStoreRunner :: EventStoreRunner (StateT (EventMap CounterEvent) IO)+stateStoreRunner = EventStoreRunner $ \action -> evalStateT (action stateEventStore) emptyEventMap++stateStoreGlobalRunner :: GloballyOrderedEventStoreRunner (StateT (EventMap CounterEvent) IO)+stateStoreGlobalRunner = GloballyOrderedEventStoreRunner $+  \action -> evalStateT (action stateEventStore stateGloballyOrderedEventStore) emptyEventMap++embeddedStateStoreRunner :: EventStoreRunner (StateT (StreamEmbeddedState Counter CounterEvent) IO)+embeddedStateStoreRunner = EventStoreRunner $ \action -> evalStateT (action store) emptyEmbeddedState+  where+    store = embeddedStateEventStore embeddedEventMap setEventMap++embeddedStateStoreGlobalRunner :: GloballyOrderedEventStoreRunner (StateT (StreamEmbeddedState Counter CounterEvent) IO)+embeddedStateStoreGlobalRunner = GloballyOrderedEventStoreRunner $+  \action -> evalStateT (action store globalStore) emptyEmbeddedState+  where+    store = embeddedStateEventStore embeddedEventMap setEventMap+    globalStore = embeddedStateGloballyOrderedEventStore embeddedEventMap
+ tests/MemoryTestImport.hs view
@@ -0,0 +1,49 @@+module MemoryTestImport+  ( makeDynamicTVarStore+  , EmbeddedState (..)+  , StreamEmbeddedState+  , GloballyOrderedEmbeddedState+  , emptyEmbeddedState+  , setEventMap+  , setProjectionMap+  ) where++import Control.Concurrent.STM++import Eventful.ProjectionCache.Memory+import Eventful.Serializer+import Eventful.Store.Memory+import Eventful.TestHelpers+import Eventful.UUID++makeDynamicTVarStore :: IO (EventStore CounterEvent STM, GloballyOrderedEventStore CounterEvent STM)+makeDynamicTVarStore = do+  tvar <- eventMapTVar+  let+    store = tvarEventStore tvar+    globalStore = tvarGloballyOrderedEventStore tvar+    store' = serializedEventStore dynamicSerializer store+    globalStore' = serializedGloballyOrderedEventStore dynamicSerializer globalStore+  return (store', globalStore')++data EmbeddedState state event key orderKey+  = EmbeddedState+  { _embeddedDummyArgument :: Int+  , embeddedEventMap :: EventMap event+  , embeddedProjectionMap :: ProjectionMap key orderKey state+  }++type StreamEmbeddedState state event = EmbeddedState state event UUID EventVersion+type GloballyOrderedEmbeddedState state event key = EmbeddedState state event key SequenceNumber++emptyEmbeddedState :: EmbeddedState state event key orderKey+emptyEmbeddedState = EmbeddedState 100 emptyEventMap emptyProjectionMap++setEventMap :: EmbeddedState state event key orderKey -> EventMap event -> EmbeddedState state event key orderKey+setEventMap state' eventMap = state' { embeddedEventMap = eventMap }++setProjectionMap+  :: EmbeddedState state event key orderKey+  -> ProjectionMap key orderKey state+  -> EmbeddedState state event key orderKey+setProjectionMap state' projectionMap = state' { embeddedProjectionMap = projectionMap }