packages feed

cqrs-example-0.7.0: src/CQRSExample/Aggregates.hs

module CQRSExample.Aggregates
       ( Project(..)
       , ProjectId
       , Site(..)
       , siteGUID
       , Task(..)
       , TaskId
       , User(..)
       , UserId
       , WorkUnit(..)
       , WorkUnitId
       ) where

import           Control.Monad (liftM)
import           Data.CQRS (Aggregate(..), GUID)
import           Data.CQRS.Serialize (decode')
import           Data.Default (Default(..))
import           Data.Map (Map)
import           Data.Set (Set)
import qualified Data.Set as S
import           Data.Serialize (Serialize(..), encode)
import           Data.Text (Text)
import           Data.Text.Encoding (decodeUtf8, encodeUtf8)
import           Data.Time.Calendar(Day(..), toGregorian, fromGregorian)
import           Data.Typeable (Typeable)
import           Data.Word (Word8)

-- Instances for Haskell built-ins.
instance Serialize Day where
  put = put . toGregorian
  get = do
    (a,b,c) <- get
    return $ fromGregorian a b c

-- Instance for Text. We'll just serialize all of it as UTF-8,
-- there's very little point in doing otherwise for our use case.
instance Serialize Text where
  put = put . encodeUtf8
  get = fmap decodeUtf8 get

-- Site aggregate. This keeps track of things which must be "per site"
-- (per installation), e.g. a list of used user names such that we
-- can be sure that there are no users with the same login name.
--
-- Note that only data required for command verification is necessary
-- here!
data Site = Site { sUserNames :: Set Text
                 } deriving (Typeable, Eq, Show)

instance Serialize Site where
  put (Site suns) =
    put suns
  get = do
    fmap Site get

instance Aggregate Site where
  encodeAggregate = encode
  decodeAggregate = decode'

instance Default Site where
  def = Site S.empty

-- GUID for the single Site aggregate.
siteGUID :: GUID
siteGUID = def

-- Projects.

type ProjectId = GUID

data Project = DefaultProject
             | ActiveProject { projectName :: Text
                             , projectCustomer :: Text
                             }
             deriving (Typeable, Eq, Show)

instance Serialize Project where
  put DefaultProject = do
    put (0 :: Word8)
  put (ActiveProject pn pc) = do
    put (1 :: Word8)
    put $ encodeUtf8 pn
    put $ encodeUtf8 pc
  get = do
    i :: Word8 <- get
    case i of
      0 -> return DefaultProject
      1 -> do
        pn <- liftM decodeUtf8 get
        pc <- liftM decodeUtf8 get
        return $ ActiveProject pn pc
      _ ->
        fail $ "Cannot decode project state" ++ show i

instance Aggregate Project where
  encodeAggregate = encode
  decodeAggregate = decode'

instance Default Project where
  def = DefaultProject

-- Tasks.

type TaskId = GUID

data Task = NewTask
          | ActiveTask { taskProjectId :: ProjectId
                       , taskShortDescription :: Text
                       , taskLongDescription :: Text
                       , taskWorkUnits :: Map Day WorkUnit
                       }
          deriving (Typeable, Eq, Show)

instance Serialize Task where
  put NewTask = do
    put (0 :: Word8)
  put (ActiveTask tpid tsd tld twu) = do
    put (1 :: Word8)
    put tpid
    put $ encodeUtf8 tsd
    put $ encodeUtf8 tld
    put twu
  get = do
    i :: Word8 <- get
    case i of
      0 -> return NewTask
      1 -> do
        tpid <- get
        tsd <- liftM decodeUtf8 get
        tld <- liftM decodeUtf8 get
        twu <- get
        return $ ActiveTask tpid tsd tld twu
      _ ->
        fail $ "Cannot decode task state: " ++ show i


instance Aggregate Task where
  encodeAggregate = encode
  decodeAggregate = decode'

instance Default Task where
  def = NewTask

-- Users.

type UserId = GUID

data User = UncreatedUser
          | ActiveUser { auUserName :: Text
                       , auPassword :: Text
                       , auLastName :: Text
                       , auFirstName :: Text
                       }
            deriving (Typeable, Eq, Show)

instance Serialize User where
  put UncreatedUser = do
    put $ (0 :: Word8)
  put (ActiveUser auun aupw auln aufn) = do
    put $ (1 :: Word8)
    put $ encodeUtf8 auun
    put $ encodeUtf8 aupw
    put $ encodeUtf8 auln
    put $ encodeUtf8 aufn
  get = do
    i :: Word8 <- get
    case i of
      0 -> return UncreatedUser
      1 -> do
        auun <- liftM decodeUtf8 get
        aupw <- liftM decodeUtf8 get
        auln <- liftM decodeUtf8 get
        aufn <- liftM decodeUtf8 get
        return $ ActiveUser auun aupw auln aufn
      _ ->
        fail $ "Cannot decode user state: " ++ show i

instance Aggregate User where
  encodeAggregate = encode
  decodeAggregate = decode'

instance Default User where
  def = UncreatedUser


-- Work Units (NOT aggregates!)
type WorkUnitId = GUID

data WorkUnit = WorkUnit { workUnitId :: GUID
                         , workUnitComment :: Text
                         , workUnitDuration :: Integer
                         , workUnitUser :: UserId
                         }
                deriving (Eq, Show)

instance Serialize WorkUnit where
  put (WorkUnit wuid wuc wud wuu) = do
    put $ wuid
    put $ encodeUtf8 wuc
    put wud
    put wuu
  get = do
    wuid <- get
    wuc <- liftM decodeUtf8 get
    wud <- get
    wuu <- get
    return $ WorkUnit wuid wuc wud wuu