packages feed

eventful-core 0.1.2 → 0.1.3

raw patch · 9 files changed

+372/−75 lines, 9 filesdep +contravariantPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: contravariant

API changes (from Hackage documentation)

- Eventful.Serializer: serializedEventStore :: (Monad m) => Serializer event serialized -> EventStore serialized m -> EventStore event m
- Eventful.Serializer: serializedGloballyOrderedEventStore :: (Monad m) => Serializer event serialized -> GloballyOrderedEventStore serialized m -> GloballyOrderedEventStore event m
+ Eventful.Projection: GloballyOrderedProjection :: !(Projection state (GloballyOrderedEvent serialized)) -> !SequenceNumber -> !state -> GloballyOrderedProjection state serialized
+ Eventful.Projection: StreamProjection :: Projection state event -> !UUID -> EventVersion -> !state -> StreamProjection state event
+ Eventful.Projection: [globallyOrderedProjectionProjection] :: GloballyOrderedProjection state serialized -> !(Projection state (GloballyOrderedEvent serialized))
+ Eventful.Projection: [globallyOrderedProjectionSequenceNumber] :: GloballyOrderedProjection state serialized -> !SequenceNumber
+ Eventful.Projection: [globallyOrderedProjectionState] :: GloballyOrderedProjection state serialized -> !state
+ Eventful.Projection: [streamProjectionProjection] :: StreamProjection state event -> Projection state event
+ Eventful.Projection: [streamProjectionState] :: StreamProjection state event -> !state
+ Eventful.Projection: [streamProjectionUuid] :: StreamProjection state event -> !UUID
+ Eventful.Projection: [streamProjectionVersion] :: StreamProjection state event -> EventVersion
+ Eventful.Projection: data GloballyOrderedProjection state serialized
+ Eventful.Projection: data StreamProjection state event
+ Eventful.Projection: globallyOrderedProjection :: Projection state (GloballyOrderedEvent serialized) -> GloballyOrderedProjection state serialized
+ Eventful.Projection: globallyOrderedProjectionEventHandler :: GloballyOrderedProjection state serialized -> GloballyOrderedEvent serialized -> GloballyOrderedProjection state serialized
+ Eventful.Projection: instance Data.Functor.Contravariant.Contravariant (Eventful.Projection.Projection state)
+ Eventful.Projection: projectionMapMaybe :: (eventB -> Maybe eventA) -> Projection state eventA -> Projection state eventB
+ Eventful.Projection: streamProjection :: Projection state event -> UUID -> StreamProjection state event
+ Eventful.ProjectionCache.Types: ProjectionCache :: (key -> orderKey -> serialized -> m ()) -> (key -> m (Maybe (orderKey, serialized))) -> ProjectionCache key orderKey serialized m
+ Eventful.ProjectionCache.Types: [loadProjectionSnapshot] :: ProjectionCache key orderKey serialized m -> key -> m (Maybe (orderKey, serialized))
+ Eventful.ProjectionCache.Types: [storeProjectionSnapshot] :: ProjectionCache key orderKey serialized m -> key -> orderKey -> serialized -> m ()
+ Eventful.ProjectionCache.Types: data ProjectionCache key orderKey serialized m
+ Eventful.ProjectionCache.Types: getLatestGlobalProjectionWithCache :: (Monad m) => GloballyOrderedEventStore event m -> GloballyOrderedProjectionCache key state m -> GloballyOrderedProjection state event -> key -> m (GloballyOrderedProjection state event)
+ Eventful.ProjectionCache.Types: getLatestProjectionWithCache :: (Monad m) => EventStore event m -> StreamProjectionCache state m -> StreamProjection state event -> m (StreamProjection state event)
+ Eventful.ProjectionCache.Types: runProjectionCacheUsing :: (Monad m, Monad mstore) => (forall a. mstore a -> m a) -> ProjectionCache key orderKey serialized mstore -> ProjectionCache key orderKey serialized m
+ Eventful.ProjectionCache.Types: serializedProjectionCache :: (Monad m) => Serializer state serialized -> ProjectionCache key orderKey serialized m -> ProjectionCache key orderKey state m
+ Eventful.ProjectionCache.Types: type GloballyOrderedProjectionCache key serialized m = ProjectionCache key SequenceNumber serialized m
+ Eventful.ProjectionCache.Types: type StreamProjectionCache serialized m = ProjectionCache UUID EventVersion serialized m
+ Eventful.ProjectionCache.Types: updateGlobalProjectionCache :: (Monad m) => GloballyOrderedEventStore event m -> GloballyOrderedProjectionCache key state m -> GloballyOrderedProjection state event -> key -> m ()
+ Eventful.ProjectionCache.Types: updateProjectionCache :: (Monad m) => EventStore event m -> StreamProjectionCache state m -> StreamProjection state event -> m ()
+ Eventful.Store.Class: runEventStoreUsing :: (Monad m, Monad mstore) => (forall a. mstore a -> m a) -> EventStore serialized mstore -> EventStore serialized m
+ Eventful.Store.Class: runGloballyOrderedEventStoreUsing :: (Monad m, Monad mstore) => (forall a. mstore a -> m a) -> GloballyOrderedEventStore serialized mstore -> GloballyOrderedEventStore serialized m
+ Eventful.Store.Class: serializedEventStore :: (Monad m) => Serializer event serialized -> EventStore serialized m -> EventStore event m
+ Eventful.Store.Class: serializedGloballyOrderedEventStore :: (Monad m) => Serializer event serialized -> GloballyOrderedEventStore serialized m -> GloballyOrderedEventStore event m
+ Eventful.Store.Queries: EventStoreQueryRange :: EventStoreQueryStart a -> EventStoreQueryLimit a -> EventStoreQueryRange a
+ Eventful.Store.Queries: MaxNumberOfEvents :: Int -> EventStoreQueryLimit a
+ Eventful.Store.Queries: NoQueryLimit :: EventStoreQueryLimit a
+ Eventful.Store.Queries: StartFromBeginning :: EventStoreQueryStart a
+ Eventful.Store.Queries: StartQueryAt :: a -> EventStoreQueryStart a
+ Eventful.Store.Queries: StopQueryAt :: a -> EventStoreQueryLimit a
+ Eventful.Store.Queries: [eventStoreQueryRangeLimit] :: EventStoreQueryRange a -> EventStoreQueryLimit a
+ Eventful.Store.Queries: [eventStoreQueryRangeStart] :: EventStoreQueryRange a -> EventStoreQueryStart a
+ Eventful.Store.Queries: allEvents :: EventStoreQueryRange a
+ Eventful.Store.Queries: data EventStoreQueryLimit a
+ Eventful.Store.Queries: data EventStoreQueryRange a
+ Eventful.Store.Queries: data EventStoreQueryStart a
+ Eventful.Store.Queries: eventsStartingAt :: a -> EventStoreQueryRange a
+ Eventful.Store.Queries: eventsStartingAtTakeLimit :: a -> Int -> EventStoreQueryRange a
+ Eventful.Store.Queries: eventsStartingAtUntil :: a -> a -> EventStoreQueryRange a
+ Eventful.Store.Queries: eventsUntil :: a -> EventStoreQueryRange a
+ Eventful.Store.Queries: instance GHC.Base.Functor Eventful.Store.Queries.EventStoreQueryLimit
+ Eventful.Store.Queries: instance GHC.Base.Functor Eventful.Store.Queries.EventStoreQueryRange
+ Eventful.Store.Queries: instance GHC.Base.Functor Eventful.Store.Queries.EventStoreQueryStart
+ Eventful.Store.Queries: instance GHC.Classes.Eq a => GHC.Classes.Eq (Eventful.Store.Queries.EventStoreQueryLimit a)
+ Eventful.Store.Queries: instance GHC.Classes.Eq a => GHC.Classes.Eq (Eventful.Store.Queries.EventStoreQueryStart a)
+ Eventful.Store.Queries: instance GHC.Show.Show a => GHC.Show.Show (Eventful.Store.Queries.EventStoreQueryLimit a)
+ Eventful.Store.Queries: instance GHC.Show.Show a => GHC.Show.Show (Eventful.Store.Queries.EventStoreQueryStart a)
- Eventful.Projection: getLatestGlobalProjection :: (Monad m) => GloballyOrderedEventStore serialized m -> Projection proj (ProjectionEvent serialized) -> Maybe (proj, SequenceNumber) -> m (proj, SequenceNumber)
+ Eventful.Projection: getLatestGlobalProjection :: (Monad m) => GloballyOrderedEventStore serialized m -> GloballyOrderedProjection state serialized -> m (GloballyOrderedProjection state serialized)
- Eventful.Projection: getLatestProjection :: (Monad m) => EventStore serialized m -> Projection proj serialized -> UUID -> m (proj, EventVersion)
+ Eventful.Projection: getLatestProjection :: (Monad m) => EventStore event m -> StreamProjection state event -> m (StreamProjection state event)
- Eventful.Store.Class: EventStore :: (UUID -> m EventVersion) -> (UUID -> Maybe EventVersion -> m [StoredEvent serialized]) -> (ExpectedVersion -> UUID -> [serialized] -> m (Maybe EventWriteError)) -> EventStore serialized m
+ Eventful.Store.Class: EventStore :: (UUID -> m EventVersion) -> (UUID -> EventStoreQueryRange EventVersion -> m [StoredEvent serialized]) -> (ExpectedVersion -> UUID -> [serialized] -> m (Maybe EventWriteError)) -> EventStore serialized m
- Eventful.Store.Class: GloballyOrderedEventStore :: (SequenceNumber -> m [GloballyOrderedEvent serialized]) -> GloballyOrderedEventStore serialized m
+ Eventful.Store.Class: GloballyOrderedEventStore :: (EventStoreQueryRange SequenceNumber -> m [GloballyOrderedEvent serialized]) -> GloballyOrderedEventStore serialized m
- Eventful.Store.Class: [getEvents] :: EventStore serialized m -> UUID -> Maybe EventVersion -> m [StoredEvent serialized]
+ Eventful.Store.Class: [getEvents] :: EventStore serialized m -> UUID -> EventStoreQueryRange EventVersion -> m [StoredEvent serialized]
- Eventful.Store.Class: [getSequencedEvents] :: GloballyOrderedEventStore serialized m -> SequenceNumber -> m [GloballyOrderedEvent serialized]
+ Eventful.Store.Class: [getSequencedEvents] :: GloballyOrderedEventStore serialized m -> EventStoreQueryRange SequenceNumber -> m [GloballyOrderedEvent serialized]

Files

eventful-core.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack  name:           eventful-core-version:        0.1.2+version:        0.1.3 synopsis:       Core module for eventful description:    Core module for eventful category:       Database,Eventsourcing@@ -32,6 +32,7 @@       base >= 4.9 && < 5     , aeson     , containers+    , contravariant     , http-api-data     , path-pieces     , sum-type-boilerplate@@ -45,9 +46,11 @@       Eventful.EventBus       Eventful.ProcessManager       Eventful.Projection+      Eventful.ProjectionCache.Types       Eventful.ReadModel.Class       Eventful.Serializer       Eventful.Store.Class+      Eventful.Store.Queries       Eventful.TH       Eventful.TH.Projection       Eventful.TH.SumTypeSerializer@@ -65,6 +68,7 @@       base >= 4.9 && < 5     , aeson     , containers+    , contravariant     , http-api-data     , path-pieces     , sum-type-boilerplate@@ -82,9 +86,11 @@       Eventful.EventBus       Eventful.ProcessManager       Eventful.Projection+      Eventful.ProjectionCache.Types       Eventful.ReadModel.Class       Eventful.Serializer       Eventful.Store.Class+      Eventful.Store.Queries       Eventful.TH       Eventful.TH.Projection       Eventful.TH.SumTypeSerializer@@ -101,6 +107,7 @@       base >= 4.9 && < 5     , aeson     , containers+    , contravariant     , http-api-data     , path-pieces     , sum-type-boilerplate
src/Eventful.hs view
@@ -5,6 +5,7 @@ import Eventful.Aggregate as X import Eventful.EventBus as X import Eventful.Projection as X+import Eventful.ProjectionCache.Types as X import Eventful.ProcessManager as X import Eventful.ReadModel.Class as X import Eventful.Serializer as X
src/Eventful/Aggregate.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE RecordWildCards #-}+ -- | Defines an Aggregate type-class from DDD parlance.  module Eventful.Aggregate@@ -48,9 +50,9 @@   -> command   -> m [serialized] commandStoredAggregate store (Aggregate handler proj) uuid command = do-  (latest, vers) <- getLatestProjection store proj uuid-  let events = handler latest command-  mError <- storeEvents store (ExactVersion vers) uuid events+  StreamProjection{..} <- getLatestProjection store (streamProjection proj uuid)+  let events = handler streamProjectionState command+  mError <- storeEvents store (ExactVersion streamProjectionVersion) uuid events   case mError of     (Just err) -> error $ "TODO: Create aggregate restart logic. " ++ show err     Nothing -> return events
src/Eventful/Projection.hs view
@@ -4,15 +4,21 @@   ( 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 Data.Maybe (fromMaybe)  import Eventful.Serializer import Eventful.Store.Class@@ -33,6 +39,11 @@     -- new state.   } +instance Contravariant (Projection state) where+  contramap f (Projection seed handler) = Projection seed handler'+    where+      handler' state event = handler state (f event)+ -- | Computes the latest state of a 'Projection' from some events. latestProjection :: (Foldable t) => Projection state event -> t event -> state latestProjection (Projection seed handler) = foldl' handler seed@@ -44,46 +55,92 @@ allProjections :: Projection state event -> [event] -> [state] allProjections (Projection seed handler) = scanl' handler seed +-- | A 'StreamProjection' is a 'Projection' that has been constructed from+-- events from a particular event stream. This is mostly useful so we can+-- associate an 'EventVersion' with some state.+data StreamProjection state event+  = StreamProjection+  { streamProjectionProjection :: Projection state event+  , streamProjectionUuid :: !UUID+  , streamProjectionVersion :: EventVersion+  , streamProjectionState :: !state+  }++-- | Initialize a 'StreamProjection' with a 'Projection'.+streamProjection+  :: Projection state event+  -> UUID+  -> StreamProjection state event+streamProjection projection@Projection{..} uuid =+  StreamProjection projection uuid (-1) projectionSeed+ -- | Gets the latest projection from a store by using 'getEvents' and then -- applying the events using the Projection's event handler. getLatestProjection   :: (Monad m)-  => EventStore serialized m-  -> Projection proj serialized-  -> UUID-  -> m (proj, EventVersion)-getLatestProjection store proj uuid = do-  events <- getEvents store uuid Nothing+  => EventStore event m+  -> StreamProjection state event+  -> m (StreamProjection state event)+getLatestProjection store projection@StreamProjection{..} = do+  events <- getEvents store streamProjectionUuid (eventsStartingAt $ streamProjectionVersion + 1)   let-    latestVersion = maxEventVersion events-    latestProj = latestProjection proj $ storedEventEvent <$> events-  return (latestProj, latestVersion)+    latestVersion = newEventVersion events+    latestState = foldl' (projectionEventHandler streamProjectionProjection) streamProjectionState $ storedEventEvent <$> events+  return $+    projection+    { streamProjectionVersion = latestVersion+    , streamProjectionState = latestState+    }   where-    maxEventVersion [] = -1-    maxEventVersion es = maximum $ storedEventVersion <$> es+    newEventVersion [] = streamProjectionVersion+    newEventVersion es = maximum $ storedEventVersion <$> es +-- | This is a combination of a 'Projection' and the latest projection state+-- with respect to some 'SequenceNumber'. This is useful for in-memory read+-- models, and for querying the latest state starting from some previous state+-- at a lower 'SequenceNumber'.+data GloballyOrderedProjection state serialized+  = GloballyOrderedProjection+  { globallyOrderedProjectionProjection :: !(Projection state (GloballyOrderedEvent serialized))+  , globallyOrderedProjectionSequenceNumber :: !SequenceNumber+  , globallyOrderedProjectionState :: !state+  }++-- | Initialize a 'GloballyOrderedProjection' at 'SequenceNumber' 0 and with+-- the projection's seed value.+globallyOrderedProjection+  :: Projection state (GloballyOrderedEvent serialized)+  -> GloballyOrderedProjection state serialized+globallyOrderedProjection projection@Projection{..} =+  GloballyOrderedProjection projection 0 projectionSeed++-- | This applies an event to a 'GloballyOrderedProjection'. NOTE: There is no+-- guarantee that the 'SequenceNumber' for the event is the previous+-- 'SequenceNumber' plus one (in fact, that isn't even a guarantee that some+-- stores can provide). This function will update the+-- 'GloballyOrderedProjetion' to use the sequence number of the event.+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'+ -- | Gets globally ordered events from the event store and builds a -- 'Projection' based on 'ProjectionEvent'. Optionally accepts the current -- projection state as an argument. getLatestGlobalProjection   :: (Monad m)   => GloballyOrderedEventStore serialized m-  -> Projection proj (ProjectionEvent serialized)-  -> Maybe (proj, SequenceNumber)-  -> m (proj, SequenceNumber)-getLatestGlobalProjection store proj mCurrentState = do-  let-    currentState = fromMaybe (projectionSeed proj) $ fst <$> mCurrentState-    startingSequenceNumber = maybe 0 (+1) $ snd <$> mCurrentState-  events <- getSequencedEvents store startingSequenceNumber-  let-    projectionEvents = globallyOrderedEventToProjectionEvent <$> events-    latestState = foldl' (projectionEventHandler proj) currentState projectionEvents-    latestSeq =-      case events of-        [] -> startingSequenceNumber-        _ -> globallyOrderedEventSequenceNumber $ last events-  return (latestState, latestSeq)+  -> GloballyOrderedProjection state serialized+  -> m (GloballyOrderedProjection state serialized)+getLatestGlobalProjection store globalProjection@GloballyOrderedProjection{..} = do+  events <- getSequencedEvents store (eventsStartingAt $ globallyOrderedProjectionSequenceNumber + 1)+  return $ foldl' globallyOrderedProjectionEventHandler globalProjection events  -- | Use a 'Serializer' to wrap a 'Projection' with event type @event@ so it -- uses the @serialized@ type.@@ -91,9 +148,14 @@   :: Projection state event   -> Serializer event serialized   -> Projection state serialized-serializedProjection (Projection seed eventHandler) Serializer{..} =-  Projection seed serializedHandler+serializedProjection proj Serializer{..} = projectionMapMaybe deserialize proj++-- | Transform a 'Projection' when you only have a partial relationship between+-- the source event type and the target event type.+projectionMapMaybe+  :: (eventB -> Maybe eventA)+  -> Projection state eventA+  -> Projection state eventB+projectionMapMaybe f (Projection seed handler) = Projection seed handler'   where-    -- Try to deserialize the event and apply the handler. If we can't-    -- deserialize, then just return the state.-    serializedHandler state = maybe state (eventHandler state) . deserialize+    handler' state = maybe state (handler state) . f
+ src/Eventful/ProjectionCache/Types.hs view
@@ -0,0 +1,147 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}++module Eventful.ProjectionCache.Types+  ( ProjectionCache (..)+  , StreamProjectionCache+  , GloballyOrderedProjectionCache+  , runProjectionCacheUsing+  , serializedProjectionCache+  , getLatestProjectionWithCache+  , getLatestGlobalProjectionWithCache+  , updateProjectionCache+  , updateGlobalProjectionCache+  ) where++import Eventful.Projection+import Eventful.Serializer+import Eventful.Store.Class+import Eventful.UUID++-- | A 'ProjectionCache' caches snapshots of 'Projection's in event streams.+-- This is useful if your event streams are very large. This cache operates on+-- some 'Monad' @m@ and stores the 'Projection' state of type @serialized@.+--+-- At its core, this is essentially just a key-value store with knowledge of+-- the stream 'UUID' and 'EventVersion'. It is recommended to use the other+-- helper functions in this module to interpret the stored values using a+-- 'Projection'.+--+-- The @key@ and @orderKey@ type parameters are polymorphic so we can abstract+-- over a cache for individual event streams, and a cache for globally ordered+-- streams.+data ProjectionCache key orderKey serialized m+  = ProjectionCache+  { storeProjectionSnapshot :: key -> orderKey -> serialized -> m ()+    -- ^ Stores the state for a projection at a given @key@ and @orderKey@.+    -- This is pretty unsafe, because there is no guarantee what is stored is+    -- actually derived from the events in the stream. Consider using+    -- 'updateProjectionCache'.+  , loadProjectionSnapshot :: key -> m (Maybe (orderKey, serialized))+    -- ^ Loads the latest projection state from the cache.+  }++-- | Type synonym for a 'ProjectionCache' used on individual event streams.+type StreamProjectionCache serialized m = ProjectionCache UUID EventVersion serialized m++-- | Type synonym for a 'ProjectionCache' that is used in conjunction with a+-- 'GloballyOrderedEventStore'.+type GloballyOrderedProjectionCache key serialized m = ProjectionCache key SequenceNumber serialized m++-- | Changes the monad a 'ProjectionCache' runs in. This is useful to run the+-- cache in another 'Monad' while forgetting the original 'Monad'.+runProjectionCacheUsing+  :: (Monad m, Monad mstore)+  => (forall a. mstore a -> m a)+  -> ProjectionCache key orderKey serialized mstore+  -> ProjectionCache key orderKey serialized m+runProjectionCacheUsing runCache ProjectionCache{..} =+  ProjectionCache+  { storeProjectionSnapshot = \uuid version state -> runCache $ storeProjectionSnapshot uuid version state+  , loadProjectionSnapshot = runCache . loadProjectionSnapshot+  }++-- | Wraps a 'ProjectionCache' and transparently serializes/deserializes events for+-- you. Note that in this implementation deserialization errors when using+-- 'getEvents' are simply ignored (the event is not returned).+serializedProjectionCache+  :: (Monad m)+  => Serializer state serialized+  -> ProjectionCache key orderKey serialized m+  -> ProjectionCache key orderKey state m+serializedProjectionCache Serializer{..} ProjectionCache{..} =+  ProjectionCache storeProjectionSnapshot' loadProjectionSnapshot'+  where+    storeProjectionSnapshot' uuid version = storeProjectionSnapshot uuid version . serialize+    loadProjectionSnapshot' uuid = do+      mState <- loadProjectionSnapshot uuid+      return $ mState >>= traverse deserialize++-- | Like 'getLatestProjection', but uses a 'ProjectionCache' if it contains+-- more recent state.+getLatestProjectionWithCache+  :: (Monad m)+  => EventStore event m+  -> StreamProjectionCache state m+  -> StreamProjection state event+  -> m (StreamProjection state event)+getLatestProjectionWithCache store cache originalProj = do+  mLatestState <- loadProjectionSnapshot cache (streamProjectionUuid originalProj)+  let+    mkProjection' (version, state) =+      if version > streamProjectionVersion originalProj+      then+        originalProj+        { streamProjectionVersion = version+        , streamProjectionState = state+        }+      else originalProj+    projection' = maybe originalProj mkProjection' mLatestState+  getLatestProjection store projection'++-- | Like 'getLatestGlobalProjection', but uses a 'ProjectionCache' if it+-- contains more recent state.+getLatestGlobalProjectionWithCache+  :: (Monad m)+  => GloballyOrderedEventStore event m+  -> GloballyOrderedProjectionCache key state m+  -> GloballyOrderedProjection state event+  -> key+  -> m (GloballyOrderedProjection state event)+getLatestGlobalProjectionWithCache store cache originalProj key = do+  mLatestState <- loadProjectionSnapshot cache key+  let+    mkProjection' (seqNum, state) =+      if seqNum > globallyOrderedProjectionSequenceNumber originalProj+      then+        originalProj+        { globallyOrderedProjectionSequenceNumber = seqNum+        , globallyOrderedProjectionState = state+        }+      else originalProj+    projection' = maybe originalProj mkProjection' mLatestState+  getLatestGlobalProjection store projection'++-- | Loads the latest projection state from the cache/store and stores this+-- value back into the projection cache.+updateProjectionCache+  :: (Monad m)+  => EventStore event m+  -> StreamProjectionCache state m+  -> StreamProjection state event+  -> m ()+updateProjectionCache store cache projection = do+  StreamProjection{..} <- getLatestProjectionWithCache store cache projection+  storeProjectionSnapshot cache streamProjectionUuid streamProjectionVersion streamProjectionState++-- | Analog of 'updateProjectionCache' for a 'GloballyOrderedProjectionCache'.+updateGlobalProjectionCache+  :: (Monad m)+  => GloballyOrderedEventStore event m+  -> GloballyOrderedProjectionCache key state m+  -> GloballyOrderedProjection state event+  -> key+  -> m ()+updateGlobalProjectionCache store cache projection key = do+  GloballyOrderedProjection{..} <- getLatestGlobalProjectionWithCache store cache projection key+  storeProjectionSnapshot cache key globallyOrderedProjectionSequenceNumber globallyOrderedProjectionState
src/Eventful/ReadModel/Class.hs view
@@ -31,7 +31,7 @@ runPollingReadModel ReadModel{..} getGloballyOrderedEvents runStore waitSeconds = forever $ do   -- Get new events starting from latest applied sequence number   latestSeq <- readModelLatestAppliedSequence readModelModel-  newEvents <- runStore $ getSequencedEvents getGloballyOrderedEvents (latestSeq + 1)+  newEvents <- runStore $ getSequencedEvents getGloballyOrderedEvents (eventsStartingAt $ latestSeq + 1)    -- Handle the new events   readModelHandleEvents readModelModel newEvents
src/Eventful/Serializer.hs view
@@ -14,9 +14,6 @@   , jsonSerializer   , jsonTextSerializer   , dynamicSerializer-    -- * Serialized event store-  , serializedEventStore-  , serializedGloballyOrderedEventStore     -- * Sum types   , EventSumType (..)   , eventSumTypeSerializer@@ -25,13 +22,11 @@ import Control.Applicative ((<|>)) import Data.Aeson import Data.Dynamic-import Data.Maybe (fromMaybe, mapMaybe)+import Data.Maybe (fromMaybe) import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Encoding as TLE import GHC.Generics -import Eventful.Store.Class- -- | A 'Serializer' describes the injective conversion between types @a@ and -- @b@. In plain English, this means that you can go from @a@ to @b@, and you -- can 'Maybe' go from @b@ back to @a@. This is often used to serialize events@@ -112,35 +107,6 @@       (error $ "Failure in eventSumTypeSerializer. Can't serialize " ++ show (typeOf event))       (eventFromDyn $ eventToDyn event)     deserialize' = eventFromDyn . eventToDyn---- | Wraps an 'EventStore' and transparently serializes/deserializes events for--- you. Note that in this implementation deserialization errors when using--- 'getEvents' are simply ignored (the event is not returned).-serializedEventStore-  :: (Monad m)-  => Serializer event serialized-  -> EventStore serialized m-  -> EventStore event m-serializedEventStore Serializer{..} store =-  EventStore-  (getLatestVersion store)-  getEvents'-  storeEvents'-  where-    getEvents' uuid mVersion = mapMaybe (traverse deserialize) <$> getEvents store uuid mVersion-    storeEvents' expectedVersion uuid events = storeEvents store expectedVersion uuid (serialize <$> events)---- | Like 'serializedEventStore' except for 'GloballyOrderedEventStore'.-serializedGloballyOrderedEventStore-  :: (Monad m)-  => Serializer event serialized-  -> GloballyOrderedEventStore serialized m-  -> GloballyOrderedEventStore event m-serializedGloballyOrderedEventStore Serializer{..} store =-  GloballyOrderedEventStore getSequencedEvents'-  where-    getSequencedEvents' sequenceNumber =-      mapMaybe (traverse deserialize) <$> getSequencedEvents store sequenceNumber  -- | This is a type class for serializing sum types of events to 'Dynamic' -- without the associated constructor. This is useful when transforming between
src/Eventful/Store/Class.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-}  module Eventful.Store.Class@@ -10,6 +11,12 @@   , GloballyOrderedEventStore (..)   , ExpectedVersion (..)   , EventWriteError (..)+  , runEventStoreUsing+  , runGloballyOrderedEventStoreUsing+  , module Eventful.Store.Queries+    -- * Serialization+  , serializedEventStore+  , serializedGloballyOrderedEventStore     -- * Utility types   , ProjectionEvent (..)   , StoredEvent (..)@@ -25,9 +32,12 @@   ) where  import Data.Aeson+import Data.Maybe (mapMaybe) import Web.HttpApiData import Web.PathPieces +import Eventful.Serializer+import Eventful.Store.Queries import Eventful.UUID  -- | The 'EventStore' is the core type of eventful. A store operates in some@@ -36,7 +46,7 @@   = EventStore   { getLatestVersion :: UUID -> m EventVersion     -- ^ Gets the latest 'EventVersion' for a given 'Projection'.-  , getEvents :: UUID -> Maybe EventVersion -> m [StoredEvent serialized]+  , getEvents :: UUID -> EventStoreQueryRange EventVersion -> m [StoredEvent serialized]     -- ^ Retrieves all the events for a given 'Projection' using that     -- projection's UUID. If an event version is provided then all events with     -- a version greater than or equal to that version are returned.@@ -50,7 +60,7 @@ -- store. newtype GloballyOrderedEventStore serialized m =   GloballyOrderedEventStore-  { getSequencedEvents :: SequenceNumber -> m [GloballyOrderedEvent serialized]+  { getSequencedEvents :: EventStoreQueryRange SequenceNumber -> m [GloballyOrderedEvent serialized]   }  -- | ExpectedVersion is used to assert the event stream is at a certain version@@ -100,6 +110,60 @@   if f latestVersion   then storeEvents' uuid events >> return Nothing   else return $ Just $ EventStreamNotAtExpectedVersion latestVersion++-- | Changes the monad an 'EventStore' runs in. This is useful to run event+-- stores in another 'Monad' while forgetting the original 'Monad'.+runEventStoreUsing+  :: (Monad m, Monad mstore)+  => (forall a. mstore a -> m a)+  -> EventStore serialized mstore+  -> EventStore serialized m+runEventStoreUsing runStore EventStore{..} =+  EventStore+  { getLatestVersion = runStore . getLatestVersion+  , getEvents = \uuid range -> runStore $ getEvents uuid range+  , storeEvents = \vers uuid events -> runStore $ storeEvents vers uuid events+  }++-- | Analog of 'runEventStoreUsing' for a 'GloballyOrderedEventStore'.+runGloballyOrderedEventStoreUsing+  :: (Monad m, Monad mstore)+  => (forall a. mstore a -> m a)+  -> GloballyOrderedEventStore serialized mstore+  -> GloballyOrderedEventStore serialized m+runGloballyOrderedEventStoreUsing runStore GloballyOrderedEventStore{..} =+  GloballyOrderedEventStore+  { getSequencedEvents = runStore . getSequencedEvents+  }++-- | Wraps an 'EventStore' and transparently serializes/deserializes events for+-- you. Note that in this implementation deserialization errors when using+-- 'getEvents' are simply ignored (the event is not returned).+serializedEventStore+  :: (Monad m)+  => Serializer event serialized+  -> EventStore serialized m+  -> EventStore event m+serializedEventStore Serializer{..} store =+  EventStore+  (getLatestVersion store)+  getEvents'+  storeEvents'+  where+    getEvents' uuid mVersion = mapMaybe (traverse deserialize) <$> getEvents store uuid mVersion+    storeEvents' expectedVersion uuid events = storeEvents store expectedVersion uuid (serialize <$> events)++-- | Like 'serializedEventStore' except for 'GloballyOrderedEventStore'.+serializedGloballyOrderedEventStore+  :: (Monad m)+  => Serializer event serialized+  -> GloballyOrderedEventStore serialized m+  -> GloballyOrderedEventStore event m+serializedGloballyOrderedEventStore Serializer{..} store =+  GloballyOrderedEventStore getSequencedEvents'+  where+    getSequencedEvents' sequenceNumber =+      mapMaybe (traverse deserialize) <$> getSequencedEvents store sequenceNumber  -- | A 'ProjectionEvent' is an event that is associated with a 'Projection' via -- the projection's 'UUID'.
+ src/Eventful/Store/Queries.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE DeriveFunctor #-}++module Eventful.Store.Queries+  ( EventStoreQueryRange (..)+  , EventStoreQueryStart (..)+  , EventStoreQueryLimit (..)+  , allEvents+  , eventsUntil+  , eventsStartingAt+  , eventsStartingAtUntil+  , eventsStartingAtTakeLimit+  ) where++-- | This type defines how to query an event stream. It defines both where to+-- start and where to stop in the stream.+data EventStoreQueryRange a+  = EventStoreQueryRange+  { eventStoreQueryRangeStart :: EventStoreQueryStart a+  , eventStoreQueryRangeLimit :: EventStoreQueryLimit a+  } deriving (Functor)++-- | This type defines where an event store query starts.+data EventStoreQueryStart a+  = StartFromBeginning+  | StartQueryAt a+  deriving (Show, Eq, Functor)++-- | This type is used to limit the results of a query from an event store.+data EventStoreQueryLimit a+  = NoQueryLimit+  | MaxNumberOfEvents Int+  | StopQueryAt a+  deriving (Show, Eq, Functor)++allEvents :: EventStoreQueryRange a+allEvents = EventStoreQueryRange StartFromBeginning NoQueryLimit++eventsUntil :: a -> EventStoreQueryRange a+eventsUntil end = EventStoreQueryRange StartFromBeginning (StopQueryAt end)++eventsStartingAt :: a -> EventStoreQueryRange a+eventsStartingAt start = EventStoreQueryRange (StartQueryAt start) NoQueryLimit++eventsStartingAtUntil :: a -> a -> EventStoreQueryRange a+eventsStartingAtUntil start end = EventStoreQueryRange (StartQueryAt start) (StopQueryAt end)++eventsStartingAtTakeLimit :: a -> Int -> EventStoreQueryRange a+eventsStartingAtTakeLimit start maxNum = EventStoreQueryRange (StartQueryAt start) (MaxNumberOfEvents maxNum)