[finished up AcidStateCounter example. Jeremy Shaw **20120113204054 Ignore-this: 622b1eae4e1cab3a1f5db8afc4859d7 ] hunk ./AcidStateCounter.lhs 3 -

Our first example is a very simple page counter app.

+

Our first example is a very simple hit counter app.

hunk ./AcidStateCounter.lhs 9 -> {-# LANGUAGE DeriveDataTypeable, FlexibleContexts, GeneralizedNewtypeDeriving, -> MultiParamTypeClasses, TemplateHaskell, TypeFamilies #-} +> {-# LANGUAGE CPP, DeriveDataTypeable, FlexibleContexts, GeneralizedNewtypeDeriving, +> MultiParamTypeClasses, TemplateHaskell, TypeFamilies, RecordWildCards #-} hunk ./AcidStateCounter.lhs 22 -> import Data.Acid ( makeAcidic ) +> import Data.Acid ( AcidState, Query, Update, makeAcidic, openLocalState ) +> import Data.Acid.Advanced ( query', update' ) +> import Data.Acid.Local ( createCheckpointAndClose ) hunk ./AcidStateCounter.lhs 29 -

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

+

Next we define a type that we wish to store in our state. In this case we just create a simple record with a single field count:

hunk ./AcidStateCounter.lhs 33 -> newtype Counter = Counter { unCounter :: Integer } -> deriving (Eq, Num, Enum, Ord, Read, Show, Data, Typeable) +> data CounterState = CounterState { count :: Integer } +> deriving (Eq, Ord, Read, Show, Data, Typeable) hunk ./AcidStateCounter.lhs 36 -> $(deriveSafeCopy 0 'base ''Counter) +> $(deriveSafeCopy 0 'base ''CounterState) hunk ./AcidStateCounter.lhs 41 -

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.

+

deriveSafeCopy creates an instance of the SafeCopy class for CounterState. SafeCopy is class for versioned serialization, deserilization, and migration. Since this is the first version of the CounterState 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.

hunk ./AcidStateCounter.lhs 43 -

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

+

Next we will define an initial value that is suitable for initializing the CounterState state.

hunk ./AcidStateCounter.lhs 47 -> data AppState = AppState { -> count :: Counter -> } deriving (Eq, Ord, Read, Show, Data, Typeable) -> -> instance Version AppState -> $(deriveSerialize ''AppState) -> +> initialCounterState :: CounterState +> initialCounterState = CounterState 0 hunk ./AcidStateCounter.lhs 52 -

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.

+

Now that we have our types, we can define some update and query functions.

hunk ./AcidStateCounter.lhs 54 -

We then create an instance of Component for AppState. +

First let's define an update function which increments the count and returns the incremented value:

hunk ./AcidStateCounter.lhs 58 -> instance Component AppState where -> type Dependencies AppState = End -> initialValue = AppState { count = 0 } +> incCountBy :: Integer -> Update CounterState Integer +> incCountBy n = +> do c@CounterState{..} <- get +> let newCount = count + n +> put $ c { count = newCount } +> return newCount hunk ./AcidStateCounter.lhs 68 -

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:

+

In this line:

hunk ./AcidStateCounter.lhs 71 - -> addCounter :: Integer -> Update AppState Counter -> addCounter n = -> do appState <- get -> let newCount = (count appState) + (Counter n) -> put $ appState { count = newCount } -> return newCount -> - +#ifdef HsColour +> c@CounterState{..} <- get +#endif hunk ./AcidStateCounter.lhs 76 -

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:

+

we are using the RecordWildCards extension. The {..} binds all the fields of the record to symbols with the same name. That is why in the next line we can just write count instead of (count c). Using RecordWildCards here is completely optional, but tends to make the code less cluttered, and easier to read.

+ +

Also notice that we are using the get and put functions from MonadState to get and put the ACID state. The Update monad is basically an enchanced version of the State monad. For the moment it is perhaps easiest to just pretend that incCountBy has the type signature:

hunk ./AcidStateCounter.lhs 82 -> addCounter :: Integer -> State AppState Counter +> incCountBy :: Integer -> State CounterState Integer hunk ./AcidStateCounter.lhs 86 -

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

+

And then it becomes clearer that incCountBy is just a simple function in the State monad which updates CounterState and returns an Integer.

hunk ./AcidStateCounter.lhs 88 -

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.

+

When the incCountBy 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 CounterState between the get and the put. It will also be run atomically (the 'A' in ACID), meaning that either the whole function will run, it will not run at all. If the server is killed mid-transaction, the transaction will either be completely applied or not applied at all.

hunk ./AcidStateCounter.lhs 90 -

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.

+

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. In order to ensure Durability and to support replication, events need to be pure. That allows us to be confident that if the event log has to be replayed -- it will result in the same state we had before. hunk ./AcidStateCounter.lhs 92 -

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

+

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

hunk ./AcidStateCounter.lhs 96 -> peekCounter :: Query AppState Counter -> peekCounter = count <$> ask +> peekCount :: Query CounterState Integer +> peekCount = count <$> ask hunk ./AcidStateCounter.lhs 102 -

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

+

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

hunk ./AcidStateCounter.lhs 106 -> peekCounter :: Reader AppState Counter +> peekCount :: Reader CounterState Integer hunk ./AcidStateCounter.lhs 110 -

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.

+

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 not block other database transactions. It also lets the user calling the function know that the database will not be affected.

hunk ./AcidStateCounter.lhs 112 -

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

+

Next we have to turn the update and query functions into acid-state events. This is almost always done by using the template haskell function makeAcidic

hunk ./AcidStateCounter.lhs 116 -> $(mkMethods ''AppState ['addCounter, 'peekCounter]) +> $(makeAcidic ''CounterState ['incCountBy, 'peekCount]) hunk ./AcidStateCounter.lhs 121 +

The makeAcidic function creates a bunch of boilerplate types and type class instances. If you want to see what is happening under the hood, check out the examples here. The examples with names like, HelloWorldNoTH.hs show how to implement the boilerplate by hand. In practice, you will probably never want to or need to do this. But you may find it useful to have a basic understanding of what is happening. You could also use the -ddump-splices flag to ghc to see the auto-generated instances -- but the lack of formatting makes it difficult to read.

+ hunk ./AcidStateCounter.lhs 127 -> handlers :: ServerPart Response -> handlers = -> msum [ dir "peek" $ do c <- query PeekCounter -> ok $ toResponse $ "peeked at the count and saw: " ++ show (unCounter c) +> handlers :: AcidState CounterState -> ServerPart Response +> handlers acid = +> msum [ dir "peek" $ do c <- query' acid PeekCount +> ok $ toResponse $ "peeked at the count and saw: " ++ show c hunk ./AcidStateCounter.lhs 132 -> c <- update (AddCounter 1) -> ok $ toResponse $ "New count is: " ++ show (unCounter c) +> c <- update' acid (IncCountBy 1) +> ok $ toResponse $ "New count is: " ++ show c hunk ./AcidStateCounter.lhs 140 -

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

+

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

hunk ./AcidStateCounter.lhs 144 -> update :: (MonadIO m, UpdateEvent ev res) => ev -> m res -> query :: (MonadIO m, QueryEvent ev res) => ev -> m res +> update' :: (UpdateEvent event, MonadIO m) => +> AcidState (EventState event) -- ^ handle to acid-state +> -> event -- ^ update event to execute +> -> m (EventResult event) +> query' :: (QueryEvent event , MonadIO m) => +> AcidState (EventState event) -- ^ handle to acid-state +> -> event -- ^ query event to execute +> -> m (EventResult event) hunk ./AcidStateCounter.lhs 155 -

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

+

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

hunk ./AcidStateCounter.lhs 159 -> data PeekCounter = PeekCounter -> data AddCounter = AddCounter Integer +> data PeekCount = PeekCount +> data IncCountBy = IncCountBy Integer hunk ./AcidStateCounter.lhs 166 +

So now we can decipher the meaning of the type for the update' and query' functions. For example, in this code:

+ +
+#ifdef HsColour +> c <- update' acid (IncCountBy 1) +#endif +
+ +

The event is (IncCountBy 1) which has the type IncCountBy. Since there is an UpdateEvent IncCountBy instance, we can use this event with the update' function. That gives us:

+ +
+#ifdef HsColour +> update' :: (UpdateEvent IncCountBy, MonadIO m) => +> AcidState (EventState IncCountBy) +> -> IncCountBy +> -> m (EventResult IncCountBy) +#endif +
+ +

EventState is a type function. EventState IncCountBy results in the type CounterState. So that reduces to AcidState CounterState. So, we see that we can not accidently call the IncCountBy event against an acid state handle of the wrong type.

+ +

EventResult is also a type function. EventResult IncCountBy is Integer, as we would expect from the type signature for IncCountBy.

+ hunk ./AcidStateCounter.lhs 195 -> do bracket (startSystemState (Proxy :: Proxy AppState)) createCheckpointAndShutdown $ -> \_control -> -> simpleHTTP nullConf handlers -> where -> createCheckpointAndShutdown control = -> do createCheckpoint control -> shutdownSystem control +> do bracket (openLocalState initialCounterState) +> (createCheckpointAndClose) +> (\acid -> +> simpleHTTP nullConf (handlers acid)) hunk ./AcidStateCounter.lhs 202 -

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.

+

openLocalState starts up acid-state and returns an handle. If existing state is found on the disk, it will be automatically restored and used. If no pre-existing state is found, then initialCounterState will be used.

+ +

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 createCheckpointAndClose is not critical to data integrity. If the server crashes unexpectedly, it will replay all the logged transactions (Durability). However, it is a good idea to create a checkpoint on close. If you change an existing update event, and then tried to replay old versions of the event, things would probably end poorly. However, restoring from a checkpoint does not require the old events to be replayed. Hence, always creating a checkpoint on shutdown makes it easier to upgrade the server.

hunk ./AcidStateCounter.lhs 206 -

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.