Initializing Data in the Foundation Data Type - Examples - Developing Web Apps with Haskell and Yesod, Second Edition (2015)

Developing Web Apps with Haskell and Yesod, Second Edition (2015)

Part III. Examples

Chapter 21. Initializing Data in the Foundation Data Type

This chapter demonstrates a relatively simple concept: performing some initialization of data to be kept in the foundation data type. There are various reasons to do this, though the two most important are:

Efficiency

By initializing data once, at process startup, you can avoid having to recompute the same value in each request.

Persistence

We want to store some information in a mutable location that will be persisted between individual requests. This is frequently done via an external database, but it can also be done via an in-memory mutable variable.

NOTE

Mutable variables can be a convenient storage mechanism, but remember that they have some downsides. If your process dies, you lose your data. Also, if you scale horizontally to more than one process, you’ll need some way to synchronize the data between processes. We’ll punt on both of those issues here, but the problems are real. This is one of the reasons Yesod puts such a strong emphasis on using an external database for persistence.

To demonstrate, we’ll implement a very simple website. It will contain a single route and will serve content stored in a Markdown file. In addition to serving that content, we’ll also display an old-school visitor counter indicating how many visitors have been to the site.

Step 1: Define Your Foundation

We’ve identified two pieces of information to be initialized: the Markdown content to be display on the homepage, and a mutable variable holding the visitor count. Remember that our goal is to perform as much of the work in the initialization phase as possible and thereby avoid performing the same work in the handlers themselves. Therefore, we want to preprocess the Markdown content into HTML. As for the visitor count, a simple IORef should be sufficient. So, our foundation data type is:

dataApp=App

{ homepageContent ::Html

, visitorCount ::IORefInt

}

Step 2: Use the Foundation

For this trivial example, we only have one route: the homepage. All we need to do is:

1. Increment the visitor count.

2. Get the new visitor count.

3. Display the Markdown content together with the visitor count.

One trick we’ll use to make the code a bit shorter is to utilize record wildcard syntax: App {..}. This is convenient when we want to deal with a number of different fields in a data type:

getHomeR ::HandlerHtml

getHomeR =do

App {..} <-getYesod

currentCount <-liftIO $ atomicModifyIORef visitorCount

$ \i -> (i + 1, i + 1)

defaultLayout $ do

setTitle "Homepage"

[whamlet|

<article>#{homepageContent}

<p>You are visitor number: #{currentCount}.

|]

Step 3: Create the Foundation Value

When we initialize our application, we’ll now need to provide values for the two fields we described previously. This is normal IO code and can perform any arbitrary actions needed. In our case, we need to:

1. Read the Markdown from the file.

2. Convert that Markdown to HTML.

3. Create the visitor counter variable.

The code ends up being just as simple as those steps imply:

go ::IO ()

go =do

rawMarkdown <-TLIO.readFile "homepage.md"

countRef <-newIORef 0

warp 3000 App

{ homepageContent =markdown def rawMarkdown

, visitorCount =countRef

}

Summary

There’s no rocket science involved in this example—just very straightforward programming. The purpose of this chapter is to demonstrate the commonly used best practice, for achieving these often-needed objectives. In your own applications, the initialization steps will likely be much more complicated: setting up database connection pools, starting background jobs to batch-process large data, or anything else. After reading this chapter, you should now have a good idea of where to place your application-specific initialization code.

Here is the full source code for the example:

{-# LANGUAGE OverloadedStrings #-}

{-# LANGUAGE QuasiQuotes #-}

{-# LANGUAGE RecordWildCards #-}

{-# LANGUAGE TemplateHaskell #-}

{-# LANGUAGE TypeFamilies #-}

import Data.IORef

importqualifiedData.Text.Lazy.IOas TLIO

import Text.Markdown

import Yesod

dataApp=App

{ homepageContent ::Html

, visitorCount ::IORefInt

}

mkYesod "App" [parseRoutes|

/ HomeRGET

|]

instanceYesodApp

getHomeR ::HandlerHtml

getHomeR =do

App {..} <-getYesod

currentCount <-liftIO $ atomicModifyIORef visitorCount

$ \i -> (i + 1, i + 1)

defaultLayout $ do

setTitle "Homepage"

[whamlet|

<article>#{homepageContent}

<p>You are visitor number: #{currentCount}.

|]

main ::IO ()

main =do

rawMarkdown <-TLIO.readFile "homepage.md"

countRef <-newIORef 0

warp 3000 App

{ homepageContent =markdown def rawMarkdown

, visitorCount =countRef

}