This project was created as a way to showcase Haskell's efficacy at building concise, usuable, real-world applications. The intention was to demonstrate the complete end-to-end development of a blog in Haskell, make the tutorial in the format of the same blog to show it's real-world applicability, and along the way, explain or clarify various Haskell concepts and techniques.
I still strongly believe that we need (especially with Haskell in particular) more real-world, live, open demonstrations of software development. It's likely that I will continue this same idea in a slightly different form when time permits.
View the original README below
This tutorial will guide you, step by step, in creating a blog -- just like the one you're reading now -- using Haskell, a pure, strongly-typed functional programming language.
Does this tutorial look too long? Want your blog now? Clone this repository and follow the instructions in the I Want My Blog Now! section. In less than fifteen minutes, you'll be able to publish your first post.
The tutorial comes with a number of useful resources to help you learn:
- A set of exercises of varying difficulty for learning or fine-tuning Haskell programming skills. The exercises are a way to learn while making useful improvements to the blog.
- Immediately deployable Docker images you can use to run your own blog before you even look at the source code. This is a good way to jump in the deep end with Haskell.
- Deployment scripts that can easily be re-used or extended for use in other Haskell projects
Learning Haskell is difficult. First, you have to understand a lot of new concepts like algebraic data-types, function currying, typeclasses, and monads. Then, you have to learn how to use algebraic data-types, currying, typeclasses, and monads to build non-trivial applications that are both efficient and maintainable. The goal of this tutorial isn't to help you understand these Haskell concepts individually, but to help you use them generally, in the process of building real-world applications.
Much of this tutorial is language-agnostic. Great software will have similar characteristics regardless of the implementation language (or even despite it). Even so, each language has its own vision of how to develop software efficiently, and the best langauges are the ones that support a consistent methodology. This tutorial showcases a little bit of Haskell's vision and methodology for software development. We'll discuss some common problems in software and show how Haskell's type system and expressiveness lead to some fascinating solutions.
If you're new to Haskell, I hope that this project will help you to appreciate Haskell's vision for software development and to understand why Haskell programmers are so passionate about things like immutablity, types, and abstract mathematics. If you already know a little Haskell (or maybe a lot), then perhaps this project will be useful as a starting point for your next application. Whatever the case, there should be something here for everyone.
Haskell pushes the limits of software development. It challenges you
to try new ways of doing something you might already know how to do in
another language. But it's not just a different syntax, it's a different way of
thinking. You'll see old problems in a new way, and the tried-and-true
solutions that you've relied upon before may start to appear a little
rusty. It's happened to all of us -- there we are, programming in another
language, as comfortable and confident as we've always been, when we
find ourselves thinking about the Haskell approach and wishing we could
write a simple where
clause, curry a function, or do just a little bit
of type-level programming.
- Introduction
- The Application
- Managing the Application
- Code Review: Program Organization
- Code Review: Domain Specific Languages
- Developing New Features
- Deploying Your Blog
- I Want My Blog Now!
- Exercises
- FAQ
The code and this tutorial are written with a set of goals in mind. The primary goal is to instruct. We want to demonstrate what it takes to build a simple web application using Haskell. As we go through the code and the tasks involved, there will be a recurring focus on understanding software engineering through the particular trade-offs that Haskell makes as a pure, strongly-typed functional programming language. We are not just interested in how to build a web application, but why we should do it with these particular tools and processes. Too often programming tutorials focus on the construction and not the engineering. We will place equal emphasis on both.
There are many, many ways to build a blog with Haskell. We've done so in a way that facilitates teaching and discussion. For example, some of the source code could be written more concisely, but that won't help explain Haskell's coding style, and playing code golf won't help promote a language with a reputation for being hard to understand. Likewise, we could have used less complex libraries, but we wanted to showcase what Haskell can do differently and better than other languages, as well as generate some useful discussion and insight on software engineering in general.
Is pure functional programming better? When is it better? What are its advantages when building real world applications? In this tutorial, we'll discuss the trade-offs associated with engineering applications in Haskell.
In particular, we'll pursue answers to the following questions:
- How do you write code that is easy to understand?
- How do you write code that is easy to extend?
- How do you write code quickly? How do write code that works the first or second time its run?
- How do you write code with minimum errors?
- How do you create a web application that is easy to deploy?
- How do you manage a software project's common tasks and metadata?
- How do you structure the code in a Haskell web application?
This example application is a very basic blog. We could have created something more interesting, but the code would be longer and more complex. The brevity and relative simplicity of the blog serves the project's goals in two ways:
- Generality The purpose of this project is to teach about building Haskell applications in general, but not how to build a specific application. Too many details would distract from the more general lessons.
- Extensibility Because there are few assumptions in the application logic, the project can easily be used as a base for a more complicated web application.
Haskell is awesome, but often misunderstood. If successful, this project will showcase Haskell's strengths and serve as an entrypoint to building useful Haskell applications.
TODO
While the specification is simple, creating a robust implementation will require more than a few libraries and tools.
- Servant (Web Framework)
- Opalaye (PostgreSQL ORM)
- Aeson (JSON / YAML parsing)
- BlazeHtml (HTML combinators)
- Optparse-Applicative (Command-line parsing)
A software project, even a humble blog, might have a myriad of components. Each compenent will have a set of actions that can be performed on it such as compiling, running tests, generating documentation, deploying, etc...
Onboarding a new developer to a project, its components, and all the actions that the developer will need to do with those components should be simple. It shouldn't take a full day of another developer's time to walk someone though running and testing a project. That time should be spent on walking through the code and discussing any design decisions, goals, or active issues.
Futheremore, onboarding yourself after a few weeks away from the project should be just as easy. If you can't remember what you ate for dinner two weeks ago, it's perfectly fine that you also forgot which exact command compiles the source code, runs the test suite, and spams your team Slack channel with baby elephant gifs. This acceptable amnesia is remedied by a clear, concise interface, preferably one available on the command line, clearly documented, and easily extensible.
Our Haskell blog app comes with a command-line utility that
satifies these goals perfectly. It's called dev.hs
. After you clone
the repository and follow the environment setup tutorial, you can run
./dev.hs --help
:
Blog Development Commands
Usage: dev.hs COMMAND
Run ./dev.hs --help for a list of commands.
Available options:
-h,--help Show this help text
Available commands:
build Build the [executable, docker images, ...]
run Run the [docker container, executable, etc...].
sh Run sh/bash in a running docker container.
psql run Psql in a running docker container.
init Initialize the database data.
deploy Compile, build images, and run containers.
Often, command-line utilities like dev.hs
are
implemented in a shell language like Bash or an interpreted langauge like
Python. Both are great choices, but actually, we can just use Haskell!
Stack allows us to write Haskell scripts that can be
run on the command line as executables. We get the best of both worlds
-- simple, self-contained scripts and the full power of Haskell.
Futhermore, because our script is written in Haskell, it has full
access to the types and functions in our application. We'll see later
how this is really convenient.
go from zero to running project
We can compile the web server using stack:
stack build
or with dev.hs
:
./dev.hs build --exe
If you want to play around with the executable, you can use stack. For
example, stack exec
will run the web server locally. See the
stack documentation for more information and commands.
TODO
TODO
TODO
TODO
TODO
custom prelude explicit imports for learning
TODO
TODO
TODO
A blog isn't really a blog if no one can read it.
Writing good code is something to be proud of.
What do we need to do to take care of our web application?
- Create a process for updating the application.
- Restart it if it dies (or replicate it so it doesn't).
- Keep backups of the data.
- Be vigilant of security vulnerabilities.
- Upgrade the hardware if it needs more memory or processing power.
- Optimize the code if the hardware gets expensive.
- Give it a treat if it does something good. (OK, maybe this is just for puppies)
This section is less opinionated that other sections. That's because how you deploy your application will vary much more than how you write it. There are dozens of products and platforms available that you might want to use. You may want to pay for Docker Cloud, switch to particular cloud host, pay for a managed database, or even use a container orchestration service if your application begins to scale. Some of these products or free, some are cheap, and some are really expensive.
There are varying degrees to which you can automate deployment from completely manual (but well-documented) processes to a completely automated continuous development cycle.
This is the graduation. You're on your own now. You will have to evaluate your needs and find the best tools for the job, spending as much or as little money was you want.
These deployment instructions are meant to be as simple and as extensible as possible. Most steps are manual. It will be up to you to automate them in a way that's useful for you.
- Create a droplet that runs Docker. A droplet is Digital Ocean's name for a virtual server on its platform.
Docker Cloud is Docker's premium service for managing Docker images and containers. In this tutorial we only use it as a means to share our images between our computer and the server. Generally, it's useful to have the images persisted somewhere where they may be easily accessed. You may want to use old images that were lost, access them from a different computer, or allow other team members to use them.
Docker Cloud organizes images into repositories It's free to use for one private repository as well as unlimited public repositories. If you don't want to use Docker Cloud, and aren't concerned about any of the benefits we just listed, feel free to copy the images manually:
docker save <image> | bzip2 | pv | ssh user@host 'bunzip2 | docker load'
It's good to remember that you almost never need to use an official service to acheive a goal in programming. It's also good to remember that your time is often more valuable than you think, and a few dollars can go a long way.
To push our images to the server, you'll need a Docker Cloud account and a repository. If you already have both, you can continue on to Step 1. Otherwise, it will only take you a few steps to create an account and repository:
- If you don't already have a Docker Cloud account, create one here.
- Once you are logged in to Docker Cloud, you can follow the instructions for creating a repository.
Now you should be ready to deploy your blog. Let's get started.
Before we do anything official with our images, we should tag them with a version number. This will prevent us from confusing images that belong to different versions of our application.
Just as with version control, it's not very common to go back to a previous revision, but when you need to, you're glad that it's there. Likewise, there are benefits to archiving previous versions of our application. For example, if something were to go wrong with our web server, we could always stop the running container and deploy a container for the image of the version that was last functioning correctly. We don't have to checkout a previous commit in version control, recompile anything, or even copy any files. It can all be done with Docker.
-
Use the
docker tag
command to tag your images. Of course, make sure that you tag the images you want to deploy and update the version numbers accordingly. For example:docker tag b0b6fdfe0115 haskell-blog-tutorial/db:1.0
docker tag f77652c4b03e haskell-blog-tutorial/web:1.0
Following the instructions to push images to Docker Hub. If it tells you to tag the image, you can skip that step, since we just did that.
Verify that your images have been uploaded, and then continue to the next step, where we'll log on to the server.
-
Logon to your droplet. If you're not sure how, follow these instructions.
-
Pull the images from your respository. You will need the namespace (your account name), the name of the repository, and the tag. Just check your local images or the repository if you've forgotten.
docker pull jeffscottwise/haskell-blog-tutorial:db-1.0
docker pull jeffscottwise/haskell-blog-tutorial:web-1.0
Once again, we'll take a more manual approach to running the application so you can make your own adjustments and automations according to your specific needs.
We'll run the containers for the database and webserver using the
docker run
command. Since port 80 should be open by default on your
droplet, you'll be able to access your application through a web
browser as soon as the containers are active.
-
Create a Network. In order for the web server container to connect to the database container, it will need to resolve the database container's address base on the name that is hard-coded into the server. Docker Compose does this automatically, but it's very simple to do manually.
If you already have a network for the containers, then you can skip this step. If you're not sure, run
docker network ls
to check. TheDRIVER
needs to bebridge
.If you need to create a network, do so now:
docker network create blog
-
Run the Application Containers. Start the database container, and then start the web server. As before, you will need to substitute your own image names, unless you are using the official haskell-blog-tutorial images.
docker run -d --name blog-db --net=blog jeffscottwise/haskell-blog-tutorial:db-0.7.0
docker run -d --name blog-web --net=blog -p 80:80 jeffscottwise/haskell-blog-tutorial:web-0.7.0
-
Make sure it works. Open a web browser and enter the IP address of the docker droplet. You should see the blog homepage.
Configuring your domain name to point to the Digital Ocean droplet is straightfoward.
- Follow the steps in this tutorial.
TODO
TODO
list of some good options. won't be update-to-date
list of options