Back to Table of Contents
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:
Next we define a type that we wish to store in our state. In this case which just create a
newtype wrapper around
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
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:
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 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:
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:
And then it becomes clearer that
addCounter is just a simple function in the
State monad which updates
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
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:
Query monad is an enhanced version of the
Reader monad. So we can pretend that
peekCounter has the type:
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
Here we actually call our query and update functions:
Note that we do not call the functions directly. Instead we invoke them using the
mkMethods, the functions that we originally defined now have types with the same name, but starting with an uppercase letter:
The arguments to the constructors are the same as the arguments to the original function.
Finally, we have our main function:
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
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.]