diff --git a/eventful-core.cabal b/eventful-core.cabal
--- a/eventful-core.cabal
+++ b/eventful-core.cabal
@@ -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
diff --git a/src/Eventful.hs b/src/Eventful.hs
--- a/src/Eventful.hs
+++ b/src/Eventful.hs
@@ -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
diff --git a/src/Eventful/Aggregate.hs b/src/Eventful/Aggregate.hs
--- a/src/Eventful/Aggregate.hs
+++ b/src/Eventful/Aggregate.hs
@@ -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
diff --git a/src/Eventful/Projection.hs b/src/Eventful/Projection.hs
--- a/src/Eventful/Projection.hs
+++ b/src/Eventful/Projection.hs
@@ -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
diff --git a/src/Eventful/ProjectionCache/Types.hs b/src/Eventful/ProjectionCache/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Eventful/ProjectionCache/Types.hs
@@ -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
diff --git a/src/Eventful/ReadModel/Class.hs b/src/Eventful/ReadModel/Class.hs
--- a/src/Eventful/ReadModel/Class.hs
+++ b/src/Eventful/ReadModel/Class.hs
@@ -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
diff --git a/src/Eventful/Serializer.hs b/src/Eventful/Serializer.hs
--- a/src/Eventful/Serializer.hs
+++ b/src/Eventful/Serializer.hs
@@ -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
diff --git a/src/Eventful/Store/Class.hs b/src/Eventful/Store/Class.hs
--- a/src/Eventful/Store/Class.hs
+++ b/src/Eventful/Store/Class.hs
@@ -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'.
diff --git a/src/Eventful/Store/Queries.hs b/src/Eventful/Store/Queries.hs
new file mode 100644
--- /dev/null
+++ b/src/Eventful/Store/Queries.hs
@@ -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)
