{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} -------------------------------------------------------------------------------- -- | -- Module : EventSource.Aggregate.Simple -- Copyright : (C) 2017 Yorick Laupa -- License : (see the file LICENSE) -- Maintainer: Yorick Laupa -- Stability : experimental -- Portability: non-portable -- -------------------------------------------------------------------------------- module EventSource.Aggregate.Simple ( AggIO , AggregateIO(..) , ValidateIO(..) , Simple , newAgg , loadAgg , loadOrCreateAgg , submitCmd , submitEvt , closeAgg , snapshot , route ) where -------------------------------------------------------------------------------- import Control.Exception (SomeException) -------------------------------------------------------------------------------- import qualified EventSource.Aggregate as Self import EventSource -------------------------------------------------------------------------------- -- | Simple aggregate abstraction. newtype Simple id command event state = Simple state -------------------------------------------------------------------------------- -- | A stream aggregate. An aggregate updates its internal based on the event -- it receives. You can read its current state by using 'snapshot'. If it -- supports validation, through 'Validated' typeclass, it can receive -- command and emits an event if the command was successful. Otherwise, it will -- yield an error. When receiving valid command, an aggregate will persist the -- resulting event. An aggregate is only responsible of its own stream. type AggIO id command event state = Self.Agg (Simple id command event state) -------------------------------------------------------------------------------- -- | Represents a stream aggregate. An aggregate can rebuild its internal state -- by replaying all the stream's events that aggregate is responsible for. class AggregateIO event state | event -> state where -- | Given current aggregate state, updates it according to the event the -- aggregate receives. applyIO :: state -> event -> IO state -------------------------------------------------------------------------------- -- | Represents an aggregate that support validation. An aggregate that supports -- validation can receive command and decide if it was valid or not. When the -- validation is successful, The aggregate emits an event that will be -- persisted and pass to 'apply' function. class AggregateIO event state => ValidateIO command event state | command -> state, command -> event where -- | Validates a command. If the command validation succeeds, it will emits -- an event. Otherwise, it will returns an error. validateIO :: state -> command -> IO (Either SomeException event) -------------------------------------------------------------------------------- instance AggregateIO event state => Self.Aggregate (Simple id command event state) where type Id (Simple id command event state) = id type Evt (Simple id command event state) = event type M (Simple id command event state) = IO apply (Simple a) e = fmap Simple (applyIO a e) -------------------------------------------------------------------------------- instance ValidateIO command event state => Self.Validate (Simple id command event state) where type Cmd (Simple id command event state) = command type Err (Simple id command event state) = SomeException validate (Simple a) cmd = validateIO a cmd -------------------------------------------------------------------------------- -- | Creates a new aggregate given an eventstore handle, an id and an initial -- state. newAgg :: AggregateIO event state => SomeStore -> id -> state -> IO (AggIO id command event state) newAgg store aId seed = Self.newAgg store aId (Simple seed) -------------------------------------------------------------------------------- -- | Creates an aggregate and replays its entire stream to rebuild its -- internal state. loadAgg :: (AggregateIO event state, Self.StreamId id, DecodeEvent event) => SomeStore -> id -> state -> IO (Either ForEventFailure (AggIO id command event state)) loadAgg store aId seed = Self.loadAgg store aId (Simple seed) -------------------------------------------------------------------------------- -- | Like 'loadAgg' but call 'loadAgg' in case of 'ForEventFailure' error. loadOrCreateAgg :: (AggregateIO event state, Self.StreamId id, DecodeEvent event) => SomeStore -> id -> state -> IO (AggIO id command event state) loadOrCreateAgg store aId seed = Self.loadOrCreateAgg store aId (Simple seed) -------------------------------------------------------------------------------- -- | Submits a command to the aggregate. If the command was valid, it returns -- an event otherwise an error. In case of a valid command, the aggregate -- persist the resulting event to the eventstore. The aggregate will also -- update its internal state accordingly. submitCmd :: (ValidateIO command event state, Self.StreamId id, EncodeEvent event) => AggIO id command event state -> command -> IO (Either SomeException event) submitCmd agg cmd = Self.submitCmd agg cmd -------------------------------------------------------------------------------- -- | Submits an event. The aggregate will update its internal state accondingly. submitEvt :: AggregateIO event state => AggIO id command event state -> event -> IO () submitEvt agg event = Self.submitEvt agg event -------------------------------------------------------------------------------- -- | Returns current aggregate state. snapshot :: AggIO id command event state -> IO state snapshot agg = do Simple a <- Self.snapshot agg pure a -------------------------------------------------------------------------------- -- | Closes internal aggregate state. closeAgg :: AggIO id command event state -> IO () closeAgg agg = Self.closeAgg agg -------------------------------------------------------------------------------- -- | Uses usually by Root aggregates which usually have unusual workflow and -- make great use of a CPS-ed computation. -- http://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-1 route :: AggIO id command event state -> (SomeStore -> state -> (state -> r -> IO ()) -> IO ()) -> IO r route agg k = Self.route agg $ \store (Simple state) resp -> k store state (\state' -> resp (Simple state'))