Back to Table of Contents

Happstack State

This section will be expanded during the Happstack 7 development period.

This is a small app demonstrating the basics of using happstack-state. This app just has a simple counter which can be incremented or read.

First a bunch of LANGUAGE pragmas and imports:

> {-# LANGUAGE DeriveDataTypeable, FlexibleContexts, GeneralizedNewtypeDeriving, > MultiParamTypeClasses, TemplateHaskell, TypeFamilies #-} > > module Main where > > import Control.Applicative ( (<$>)) > import Control.Exception ( bracket) > import Control.Monad ( msum) > import Control.Monad.Reader ( ask) > import Control.Monad.State ( get, put) > import Data.Data ( Data, Typeable) > import Happstack.Server ( Response, ServerPart, dir, nullDir, nullConf, ok > , simpleHTTP, toResponse) > import Happstack.State ( Component(..), End, Proxy(..), Query, Update, Version > , createCheckpoint, deriveSerialize, mkMethods > , query, startSystemState, shutdownSystem, update ) >

Next we define a type that we wish to store in our state. In this case which just create a newtype wrapper around Integer:

> newtype Counter = Counter { unCounter :: Integer } > deriving (Eq, Num, Enum, Ord, Read, Show, Data, Typeable) > > instance Version Counter > $(deriveSerialize ''Counter) >

The Version instance is used for migration. If we later change the Counter type, we will want to be able to migrate old saved data to the new format. Migration will be covered in a later section.

deriveSerialize creates an instance of the Serialize class for Counter. The Serialize class specifies how to convert instances to binary representations and back. It also plays a role in the version migration process.

Next we will create a simple record that holds all the state for our application:

> data AppState = AppState { > count :: Counter > } deriving (Eq, Ord, Read, Show, Data, Typeable) > > instance Version AppState > $(deriveSerialize ''AppState) >

It is a bit overkill for this application since we only have one piece of state that we are tracking. But in larger apps, it is a common pattern.

We then create an instance of Component for AppState.

> instance Component AppState where > type Dependencies AppState = End > initialValue = AppState { count = 0 } >

A Component defines the boundries of transactions. An app can have multiple components, but each update or query can only see one component. Because the components operate independently, transactions in one component do not block transactions in another component. This can be used to provide better scaling in applications which perform many updates.

Dependencies is used when we have multiple components. Here we only have one, so we specify that the dependency list is empty.

initialValue specifies what the initial state of the database should be when the database is first created. Once the database has been created, changing that value has no effect.

Next we define an update function:

> addCounter :: Integer -> Update AppState Counter > addCounter n = > do appState <- get > let newCount = (count appState) + (Counter n) > put $ appState { count = newCount } > return newCount >

The Update monad is an enchanced version of the State monad. For the moment it is perhaps easiest to just pretend that addCounter has the type signature:

#ifdef HsColour > addCounter :: Integer -> State AppState Counter #endif

And then it becomes clearer that addCounter is just a simple function in the State monad which updates AppState.

When the addCounter function is invoked, it will be run in an isolated manner (the 'I' in ACID). That means that you do not need to worry about some other thread modifying the AppState in between the get and the put.

You may also note that Update (and State) are not instances of the MonadIO class. This means you can not perform IO inside the update. This is by design. The Update monad does provide a few special IO functions related to getting the current time or generating a random number. Those functions are covered elsewhere.

We can also define a query which only reads the state, and does not update it:

> peekCounter :: Query AppState Counter > peekCounter = count <$> ask >

The Query monad is an enhanced version of the Reader monad. So we can pretend that peekCounter has the type:

#ifdef HsColour > peekCounter :: Reader AppState Counter #endif

Although we could have just used get in the Update monad, it is better to use the Query monad if you are doing a read-only operation because it will have much better performance. It also lets the user calling the function know that the database will not be affected.

Next we have to register those update and query functions with the AppState component:

> $(mkMethods ''AppState ['addCounter, 'peekCounter]) >

Here we actually call our query and update functions:

> handlers :: ServerPart Response > handlers = > msum [ dir "peek" $ do c <- query PeekCounter > ok $ toResponse $ "peeked at the count and saw: " ++ show (unCounter c) > , do nullDir > c <- update (AddCounter 1) > ok $ toResponse $ "New count is: " ++ show (unCounter c) > > ] >

Note that we do not call the functions directly. Instead we invoke them using the update and query functions:

#ifdef HsColour > update :: (MonadIO m, UpdateEvent ev res) => ev -> m res > query :: (MonadIO m, QueryEvent ev res) => ev -> m res #endif

Thanks to mkMethods, the functions that we originally defined now have types with the same name, but starting with an uppercase letter:

#ifdef HsColour > data PeekCounter = PeekCounter > data AddCounter = AddCounter Integer #endif

The arguments to the constructors are the same as the arguments to the original function.

Finally, we have our main function:

> main :: IO () > main = > do bracket (startSystemState (Proxy :: Proxy AppState)) createCheckpointAndShutdown $ > \_control -> > simpleHTTP nullConf handlers > where > createCheckpointAndShutdown control = > do createCheckpoint control > shutdownSystem control

startSystemState (Proxy :: Proxy AppState) starts the transaction system. The Proxy argument tells the transaction system which Component we want to use as the top-level component.

The shutdown sequence creates a checkpoint when the server exits. This is good practice because it helps the server start faster, and makes migration go more smoothly. Calling createCheckpoint and shutdownSystem are not critical to data integrity. If the server crashes unexpectedly, it will replay all the logged transactions. But, running them during a normal shutdown sequence is a good idea.

[Source code for the app is here.]

Next: web-routes