[start work on acid-state documentation Jeremy Shaw **20120106194101 Ignore-this: 34773d1108fbbdbff14ccdb5aa3c1a5e ] addfile ./AcidState.lhs hunk ./AcidState.lhs 1 + + + + Crash Course in Happstack + + + + + +

Back to Table of Contents

+

acid-state

+ +

acid-state is a NoSQL, RAM-cloud persistent data store. One of its attractive features is that it is designed to store arbitrary Haskell datatypes and queries are written using plain old Haskell code. This means you do not have to learn a special query language, or figure out how to turn your beautiful Haskell datastructures into some limited set of ints and strings.

+ +

acid-state and safecopy are the successors to the old happstack-state and happstack-data libraries. You can learn more at the acid-state homepage. acid-state is now completely independent from Happstack and can be used with any web framework. However, Happstack is still committed to the improvement and promotion of acid-state.

+ +

Apps written using happstack-state can be migrated to use acid-state relatively easily. Details on the process or documented here. + +#include "AcidStateCounter.lhs" + +

[Source code for the app is here.]

+

Next: web-routes

+ + + addfile ./AcidStateCounter.lhs hunk ./AcidStateCounter.lhs 1 +

acid-state counter

+ +

Our first example is a very simple page counter app.

+ +

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 Data.Acid ( makeAcidic ) +> import Data.SafeCopy ( base, deriveSafeCopy ) + +
+ +

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

+ +
+ +> newtype Counter = Counter { unCounter :: Integer } +> deriving (Eq, Num, Enum, Ord, Read, Show, Data, Typeable) +> +> $(deriveSafeCopy 0 'base ''Counter) +> + +
+ +

deriveSafeCopy creates an instance of the SafeCopy class for Counter. SafeCopy is class for versioned serialization, deserilization, and migration. Since this is the first version of the Counter type, we give it version number 0 and declare it to be the base type. Later if we change the type, we can change the version number and provide code to migrate the old instances. The migration will happen automatically when the old state is read. For more information on SafeCopy and migration see the haddock docs.

+ +

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.