cqrs 0.7.1 → 0.8.0
raw patch · 6 files changed
+59/−43 lines, 6 filesdep +conduitdep +safecopydep −enumeratorPVP ok
version bump matches the API change (PVP)
Dependencies added: conduit, safecopy
Dependencies removed: enumerator
API changes (from Hackage documentation)
- Data.CQRS.GUID: instance Serialize GUID
+ Data.CQRS.EventStore.Backend: RawSnapshot :: Int -> ByteString -> RawSnapshot
+ Data.CQRS.EventStore.Backend: data RawSnapshot
+ Data.CQRS.EventStore.Backend: rsSnapshotData :: RawSnapshot -> ByteString
+ Data.CQRS.EventStore.Backend: rsVersion :: RawSnapshot -> Int
+ Data.CQRS.GUID: instance SafeCopy GUID
+ Data.CQRS.Serialize: encode :: SafeCopy e => e -> ByteString
- Data.CQRS.EventStore: enumerateEventStore :: Serialize e => EventStore e -> Int -> Enumerator (PersistedEvent e) IO a
+ Data.CQRS.EventStore: enumerateEventStore :: SafeCopy e => EventStore e -> Int -> Source IO (PersistedEvent e)
- Data.CQRS.EventStore.Backend: EventStoreBackend :: (GUID -> Int -> [RawEvent] -> IO ()) -> (GUID -> Int -> IO [RawEvent]) -> (forall a. Int -> Enumerator RawEvent IO a) -> (GUID -> (Int, ByteString) -> IO ()) -> (GUID -> IO (Maybe (Int, ByteString))) -> IO Int -> (forall a. IO a -> IO a) -> IO () -> EventStoreBackend
+ Data.CQRS.EventStore.Backend: EventStoreBackend :: (GUID -> Int -> [RawEvent] -> IO ()) -> (GUID -> Int -> Source IO RawEvent) -> (Int -> Source IO RawEvent) -> (GUID -> RawSnapshot -> IO ()) -> (GUID -> IO (Maybe RawSnapshot)) -> IO Int -> (forall a. IO a -> IO a) -> IO () -> EventStoreBackend
- Data.CQRS.EventStore.Backend: esbEnumerateAllEvents :: EventStoreBackend -> forall a. Int -> Enumerator RawEvent IO a
+ Data.CQRS.EventStore.Backend: esbEnumerateAllEvents :: EventStoreBackend -> Int -> Source IO RawEvent
- Data.CQRS.EventStore.Backend: esbGetLatestSnapshot :: EventStoreBackend -> GUID -> IO (Maybe (Int, ByteString))
+ Data.CQRS.EventStore.Backend: esbGetLatestSnapshot :: EventStoreBackend -> GUID -> IO (Maybe RawSnapshot)
- Data.CQRS.EventStore.Backend: esbRetrieveEvents :: EventStoreBackend -> GUID -> Int -> IO [RawEvent]
+ Data.CQRS.EventStore.Backend: esbRetrieveEvents :: EventStoreBackend -> GUID -> Int -> Source IO RawEvent
- Data.CQRS.EventStore.Backend: esbWriteSnapshot :: EventStoreBackend -> GUID -> (Int, ByteString) -> IO ()
+ Data.CQRS.EventStore.Backend: esbWriteSnapshot :: EventStoreBackend -> GUID -> RawSnapshot -> IO ()
- Data.CQRS.Serialize: decode' :: Serialize e => ByteString -> e
+ Data.CQRS.Serialize: decode' :: SafeCopy e => ByteString -> e
- Data.CQRS.Transaction: getAggregateRoot :: (Default a, Serialize e, Typeable a, Typeable e, Aggregate a, Eventable a e) => GUID -> TransactionT e IO (AggregateRef a e, a)
+ Data.CQRS.Transaction: getAggregateRoot :: (Default a, SafeCopy e, Typeable a, Typeable e, Aggregate a, Eventable a e) => GUID -> TransactionT e IO (AggregateRef a e, a)
- Data.CQRS.Transaction: publishEvent :: (MonadIO m, Serialize e, Typeable a, Typeable e, Aggregate a, Eventable a e, Default a) => AggregateRef a e -> e -> TransactionT e m ()
+ Data.CQRS.Transaction: publishEvent :: (MonadIO m, SafeCopy e, Typeable a, Typeable e, Aggregate a, Eventable a e, Default a) => AggregateRef a e -> e -> TransactionT e m ()
- Data.CQRS.Transaction: runTransactionT :: (Typeable e, Serialize e) => EventStore e -> TransactionT e IO c -> IO c
+ Data.CQRS.Transaction: runTransactionT :: (Typeable e, SafeCopy e) => EventStore e -> TransactionT e IO c -> IO c
Files
- cqrs.cabal +3/−2
- src/Data/CQRS/EventStore/Backend.hs +12/−5
- src/Data/CQRS/GUID.hs +5/−5
- src/Data/CQRS/Internal/EventStore.hs +13/−14
- src/Data/CQRS/Serialize.hs +10/−4
- src/Data/CQRS/Transaction.hs +16/−13
cqrs.cabal view
@@ -1,5 +1,5 @@ Name: cqrs-Version: 0.7.1+Version: 0.8.0 Synopsis: Command-Query Responsibility Segregation Description: Haskell implementation of the CQRS architectural pattern. License: MIT@@ -15,10 +15,11 @@ , base16-bytestring >= 0.1.1.3 && < 0.2 , bytestring >= 0.9.0.1 , cereal >= 0.3.3 && < 0.4+ , conduit >= 0.1 && <0.2 , containers >= 0.4 , data-default >= 0.3 && < 0.4- , enumerator >= 0.4.15 && < 0.5 , random >= 1.0 && < 1.1+ , safecopy >= 0.6 && < 0.7 , transformers >= 0.2.2 && < 0.3 Extensions: DeriveDataTypeable ExistentialQuantification
src/Data/CQRS/EventStore/Backend.hs view
@@ -4,17 +4,24 @@ module Data.CQRS.EventStore.Backend ( EventStoreBackend(..) , RawEvent+ , RawSnapshot(..) ) where import Data.ByteString (ByteString)+import Data.Conduit (Source) import Data.CQRS.GUID import Data.CQRS.PersistedEvent (PersistedEvent)-import Data.Enumerator (Enumerator) -- | Raw event type. The data associated with an event is not -- translated in any way. type RawEvent = PersistedEvent ByteString +-- | Raw snapshot.+data RawSnapshot =+ RawSnapshot { rsVersion :: Int+ , rsSnapshotData :: ByteString+ }+ -- | Event stores are the backend used for reading and storing all the -- information about recorded events. data EventStoreBackend =@@ -28,19 +35,19 @@ -- identified by the given GUID. Only events at or after the given -- version number are retrieved. The events are returned in -- increasing order of version number.- esbRetrieveEvents :: GUID -> Int -> IO [RawEvent],+ esbRetrieveEvents :: GUID -> Int -> Source IO RawEvent, -- | Enumerate all events later than given logical timestamp.- esbEnumerateAllEvents :: forall a. Int -> Enumerator RawEvent IO a,+ esbEnumerateAllEvents :: Int -> Source IO RawEvent, -- | Write snapshot for aggregate identified by GUID and -- the given version number. The version number is NOT checked -- for validity. If the event store does not support snapshots -- this function may do nothing.- esbWriteSnapshot :: GUID -> (Int,ByteString) -> IO (),+ esbWriteSnapshot :: GUID -> RawSnapshot -> IO (), -- | Get latest snapshot of an aggregate identified by GUID. -- Returns the version number of the snapshot in addition to the -- data. An event store which does not support snapshots is -- permitted to return 'Nothing' in all cases.- esbGetLatestSnapshot :: GUID -> IO (Maybe (Int,ByteString)),+ esbGetLatestSnapshot :: GUID -> IO (Maybe RawSnapshot), -- | Get the latest version global number. esbGetLatestVersion :: IO Int, -- | Run transaction against the event store. The transaction is
src/Data/CQRS/GUID.hs view
@@ -8,13 +8,13 @@ , toByteString ) where -import Control.Monad (liftM)+import Control.Applicative ((<$>)) import Data.ByteString (ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Base16 as B16 import Data.ByteString.Char8 () import Data.Default (Default(..))-import Data.Serialize (Serialize(..))+import Data.SafeCopy (SafeCopy(..), contain, safePut, safeGet) import Data.Typeable (Typeable) import Data.Word (Word8) import System.Random (randomRIO)@@ -38,9 +38,9 @@ return $ GUID $ B.pack uuid -- | Serialize instance.-instance Serialize GUID where- put = put . toByteString- get = liftM fromByteString get+instance SafeCopy GUID where+ putCopy = contain . safePut . toByteString+ getCopy = contain $ fromByteString <$> safeGet -- | Hex encode a GUID. hexEncode :: GUID -> ByteString
src/Data/CQRS/Internal/EventStore.hs view
@@ -13,14 +13,13 @@ import Control.Exception (bracket) import Control.Monad (liftM)-import Data.ByteString (ByteString)-import Data.CQRS.EventStore.Backend (EventStoreBackend(..))+import Data.CQRS.EventStore.Backend (EventStoreBackend(..), RawSnapshot) import Data.CQRS.GUID import Data.CQRS.Internal.PersistedEvent (PersistedEvent(..), mapPersistedEvent)-import Data.CQRS.Serialize (decode')-import Data.Enumerator (Enumerator, ($=))-import qualified Data.Enumerator.List as EL-import Data.Serialize (Serialize, encode)+import Data.CQRS.Serialize (decode', encode)+import Data.Conduit (Source, ($=))+import qualified Data.Conduit.List as CL+import Data.SafeCopy (SafeCopy) -- Provide a type alias. data EventStore e = EventStore { esBackend :: EventStoreBackend@@ -32,23 +31,23 @@ -- | Enumerate all the events from an event store that occur at or later -- than a given logical timestamp.-enumerateEventStore :: forall a e . (Serialize e) => EventStore e -> Int -> Enumerator (PersistedEvent e) IO a+enumerateEventStore :: forall e . (SafeCopy e) => EventStore e -> Int -> Source IO (PersistedEvent e) enumerateEventStore es minVersion =- esbEnumerateAllEvents (esBackend es) minVersion $= EL.map (mapPersistedEvent decode')+ esbEnumerateAllEvents (esBackend es) minVersion $= CL.map (mapPersistedEvent decode') withTransaction :: EventStore e -> IO a -> IO a withTransaction (EventStore esb) = esbWithTransaction esb -storeEvents :: Serialize e => EventStore e -> GUID -> Int -> [PersistedEvent e] -> IO ()-storeEvents (EventStore esb) guid v0 evs = esbStoreEvents esb guid v0 $ (map $ mapPersistedEvent encode) evs+storeEvents :: SafeCopy e => EventStore e -> GUID -> Int -> [PersistedEvent e] -> IO ()+storeEvents (EventStore esb) guid v0 evs = esbStoreEvents esb guid v0 $ (map $ mapPersistedEvent $ encode) evs -retrieveEvents :: Serialize e => EventStore e -> GUID -> Int -> IO [PersistedEvent e]-retrieveEvents (EventStore esb) guid v0 = liftM (map $ mapPersistedEvent decode') $ esbRetrieveEvents esb guid v0+retrieveEvents :: SafeCopy e => EventStore e -> GUID -> Int -> Source IO (PersistedEvent e)+retrieveEvents (EventStore esb) guid v0 = fmap (mapPersistedEvent decode') $ esbRetrieveEvents esb guid v0 -writeSnapshot :: EventStore e -> GUID -> (Int, ByteString) -> IO ()+writeSnapshot :: EventStore e -> GUID -> RawSnapshot -> IO () writeSnapshot (EventStore esb) = esbWriteSnapshot esb -getLatestSnapshot :: EventStore e -> GUID -> IO (Maybe (Int, ByteString))+getLatestSnapshot :: EventStore e -> GUID -> IO (Maybe RawSnapshot) getLatestSnapshot (EventStore esb) = esbGetLatestSnapshot esb getLatestVersion :: EventStore e -> IO Int
src/Data/CQRS/Serialize.hs view
@@ -1,10 +1,16 @@ module Data.CQRS.Serialize ( decode'+ , encode ) where import Data.ByteString (ByteString)-import Data.Serialize (Serialize, decode)+import Data.SafeCopy (SafeCopy, safeGet, safePut)+import Data.Serialize (runGet, runPut) --- | Decode a serialized value "irrefutably".-decode' :: Serialize e => ByteString -> e-decode' = either error id . decode+-- | Decode a serialized value irrefutably.+decode' :: SafeCopy e => ByteString -> e+decode' = either error id . runGet safeGet++-- | Encode a value.+encode :: SafeCopy e => e -> ByteString+encode = runPut . safePut
src/Data/CQRS/Transaction.hs view
@@ -10,9 +10,12 @@ import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Trans.Class (MonadTrans(..), lift) import Control.Monad.Trans.State (StateT, get, gets, modify, runStateT)+import Data.Conduit (($$), runResourceT)+import qualified Data.Conduit.List as CL import Data.CQRS.Aggregate (Aggregate(..)) import Data.CQRS.Eventable (Eventable(..)) import Data.CQRS.EventStore (EventStore)+import Data.CQRS.EventStore.Backend (RawSnapshot(..)) import Data.CQRS.GUID (GUID) import Data.CQRS.Internal.AggregateRef (AggregateRef, mkAggregateRef) import qualified Data.CQRS.Internal.AggregateRef as AR@@ -20,7 +23,7 @@ import Data.CQRS.PersistedEvent (PersistedEvent(..)) import Data.Default (Default(..)) import Data.List (find)-import Data.Serialize (Serialize)+import Data.SafeCopy (SafeCopy) import Data.Typeable (Typeable, cast) -- | Transaction monad transformer.@@ -32,7 +35,7 @@ -- Existential wrapper for AggregateRef. data BoxedAggregateRef e =- forall a . (Typeable a, Typeable e, Serialize e, Default a, Aggregate a, Eventable a e) => BoxedAggregateRef (AggregateRef a e)+ forall a . (Typeable a, Typeable e, SafeCopy e, Default a, Aggregate a, Eventable a e) => BoxedAggregateRef (AggregateRef a e) -- | Transaction monad itself. type Transaction e = StateT (TransactionState e)@@ -44,7 +47,7 @@ } -- | Run transaction against an event store.-runTransactionT :: (Typeable e, Serialize e) => EventStore e -> TransactionT e IO c -> IO c+runTransactionT :: (Typeable e, SafeCopy e) => EventStore e -> TransactionT e IO c -> IO c runTransactionT eventStore_ (TransactionT transaction) = do ES.withTransaction eventStore_ $ do -- Get the latest global version number to use.@@ -61,13 +64,13 @@ v <- AR.getCurrentVersion a when (v - AR.arSnapshotVersion a > 10) $ do av <- AR.readValue a- ES.writeSnapshot eventStore_ (AR.arGUID a) (v, encodeAggregate av)+ ES.writeSnapshot eventStore_ (AR.arGUID a) $ RawSnapshot v (encodeAggregate av) -- Return the value. return r where s0 = TransactionState eventStore_ [ ] -- | Get an aggregate ref by GUID.-getById :: forall a e . (Typeable a, Typeable e, Serialize e, Default a, Aggregate a, Eventable a e) => GUID -> TransactionT e IO (AggregateRef a e)+getById :: forall a e . (Typeable a, Typeable e, SafeCopy e, Default a, Aggregate a, Eventable a e) => GUID -> TransactionT e IO (AggregateRef a e) getById guid = TransactionT $ do -- Check through list to see if we've given out a reference to the aggregate before. aggregateRefs <- fmap aggregateRefsToCommit get@@ -84,26 +87,26 @@ -- Get the latest snapshot from database, filling in a default -- if a) no snapshot exists, or b) snapshot state was not decodable.-getLatestSnapshot :: forall a e . (Typeable a, Typeable e, Serialize e, Default a, Aggregate a) => GUID -> Transaction e IO (Int,a)+getLatestSnapshot :: forall a e . (Typeable a, Typeable e, SafeCopy e, Default a, Aggregate a) => GUID -> Transaction e IO (Int,a) getLatestSnapshot guid = do es <- fmap eventStore get r <- liftIO $ ES.getLatestSnapshot es guid case r of- Just (v',a') -> do- case decodeAggregate a' :: Maybe a of- Just a'' -> return (v', a'')+ Just (RawSnapshot v a) -> do+ case decodeAggregate a :: Maybe a of+ Just a' -> return (v, a') Nothing -> return (0, def) Nothing -> do return (0, def) -- Retrieve aggregate from event store.-getByIdFromEventStore :: forall a e . (Typeable a, Typeable e, Serialize e, Default a, Aggregate a, Eventable a e) => GUID -> Transaction e IO (AggregateRef a e)+getByIdFromEventStore :: forall a e . (Typeable a, Typeable e, SafeCopy e, Default a, Aggregate a, Eventable a e) => GUID -> Transaction e IO (AggregateRef a e) getByIdFromEventStore guid = do es <- fmap eventStore get -- Get latest snapshot (if any). (v0,a0) <- getLatestSnapshot guid -- Get events.- events <- lift $ ES.retrieveEvents es guid v0+ events <- lift $ runResourceT $ ES.retrieveEvents es guid v0 $$ CL.consume let latestVersion = maximum $ (:) v0 (map peSequenceNumber events) -- Build the aggregate state from all the events. let a = foldr (applyEvent . peEvent) a0 events@@ -115,7 +118,7 @@ return a' -- | Publish event for an aggregate root.-publishEvent :: (MonadIO m, Serialize e, Typeable a, Typeable e, Aggregate a, Eventable a e, Default a) => AggregateRef a e -> e -> TransactionT e m ()+publishEvent :: (MonadIO m, SafeCopy e, Typeable a, Typeable e, Aggregate a, Eventable a e, Default a) => AggregateRef a e -> e -> TransactionT e m () publishEvent aggregateRef event = TransactionT $ do -- Publish event to aggregate itself. currentGlobalVersion <- gets tsCurrentVersion@@ -124,7 +127,7 @@ modify $ \s -> s { tsCurrentVersion = currentGlobalVersion + 1 } -- | Get aggregate root.-getAggregateRoot :: (Default a, Serialize e, Typeable a, Typeable e, Aggregate a, Eventable a e) => GUID -> TransactionT e IO (AggregateRef a e, a)+getAggregateRoot :: (Default a, SafeCopy e, Typeable a, Typeable e, Aggregate a, Eventable a e) => GUID -> TransactionT e IO (AggregateRef a e, a) getAggregateRoot guid = do aggregateRef <- getById guid aggregate <- lift $ AR.readValue aggregateRef