packages feed

cqrs-example-0.9.0: src/CQRSExample/Query.hs

module CQRSExample.Query
       ( QueryM
       , QueryState
       , QTaskState(..)
       , newQueryState
       , runQuery
       , qCompletedTaskIdList
       , qTaskList
       , reactToEvent
       ) where

import           Control.Concurrent.STM (atomically)
import           Control.Concurrent.STM.TVar (TVar, readTVar, writeTVar)
import           Control.DeepSeq (NFData(..), ($!!))
import           Control.Monad.Trans.Class (lift)
import           Control.Monad.Trans.Reader (ReaderT, ask, runReaderT)
import           Data.CQRS.Query (GUID, PersistedEvent(..))
import           Data.Data (Data)
import           Data.DeriveTH (derive, makeNFData)
import qualified Data.List as L
import           Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import           Data.Text (Text)
import           Data.Typeable (Typeable)

import           CQRSExample.Aggregates ()
import           CQRSExample.Events (Event(..), TaskEvent(..))

-- Tasks.
data QTask =
    QTask { qTaskId :: GUID
          , qTaskTitle :: Text
          , qTaskState :: QTaskState }
    deriving (Data, Typeable, Ord, Eq, Show)

data QTaskState = QTaskOpen
                | QTaskCompleted
                | QTaskArchived
                  deriving (Data, Typeable, Ord, Eq, Show)

-- Queryable state.
data QueryState = QueryState
    { qTasks :: Map GUID QTask
    } deriving (Show)

-- Need NFData instances for deep evaluation.
$(derive makeNFData ''QTaskState)
$(derive makeNFData ''QTask)
$(derive makeNFData ''QueryState)

-- Query monad.
type QueryM a = ReaderT (TVar QueryState) IO a

-- Create query state.
newQueryState :: QueryState
newQueryState = QueryState { qTasks = M.empty }

-- runQuery
runQuery :: TVar QueryState -> QueryM a -> IO a
runQuery qs q = runReaderT q qs

-- Run query against the current state and return the result.
qRunQ :: QueryM QueryState
qRunQ = do
  qsr <- ask
  lift $ atomically $ readTVar qsr

-- Query the task list
qTaskList :: QueryM [(GUID, Text, QTaskState)]
qTaskList = do
  tasks <- fmap qTasks qRunQ
  return $ map f $ filter p $ L.sortBy sf $ M.elems tasks
  where
    f t = (qTaskId t, qTaskTitle t, qTaskState t)
    sf (QTask _ title1 _) (QTask _ title2 _) = compare title1 title2
    p (QTask _ _ QTaskOpen) = True
    p (QTask _ _ QTaskCompleted) = True
    p (QTask _ _ QTaskArchived) = False

qCompletedTaskIdList :: QueryM [GUID]
qCompletedTaskIdList = do
  tasks <- fmap qTasks qRunQ
  return $ map f $ filter p $ M.elems tasks
    where
      f (QTask i _ _) = i
      p (QTask _ _ QTaskCompleted) = True
      p (QTask _ _ QTaskOpen) = False
      p (QTask _ _ QTaskArchived) = False

reactToEvent :: PersistedEvent Event -> QueryM ()
reactToEvent ev = do
  let e = peEvent ev
  lift $ putStrLn $ "guid: " ++ show g ++ " event: " ++ show e
  qsr <- ask
  -- Update the query state
  lift $ atomically $ do
    qs0 <- readTVar qsr
    writeTVar qsr $!! react e $!! qs0

  where
    g = peAggregateGUID ev

    reactTask (TaskAdded title) =
        M.insert g (QTask g title QTaskOpen)
    reactTask TaskCompleted =
        M.adjust (\task -> task { qTaskState = QTaskCompleted }) g
    reactTask TaskReopened =
        M.adjust (\task -> task { qTaskState = QTaskOpen }) g
    reactTask TaskArchived =
        M.adjust (\task -> task { qTaskState = QTaskArchived }) g

    react (TaskEvent e) qs =
        qs { qTasks = reactTask e $ qTasks qs }