A trivial web application created to test the cauldron dependency injection library.
After cloning the project repository, checkout the submodules:
git submodule init
git submodule update
Before running the server for the first time:
sqlite3 db.sqlite < schema.sql
Then:
cabal build all
cabal run comments
And navigate to http://localhost:8000/comments
.
ormolu --mode inplace $(git ls-files '*.hs')
-
There is not a central configuration record. Each component registers some configuration bean, which is parsed independently from the raw JSON configuration.
-
Beans often have an "interface" module which defines a record-of-functions type, and an "implemementation" module (potentially more than one) which provides a constructor for the record-of-functions.
This is not a hard rule; for other beans the record-of-functions and the constructor are defined in the same module.
-
Some bean constructors require the records-of-functions they return to be parameterized by
ReaderT env IO
, but others (like Comments.Repository.Sqlite.make) could be polymorphic over the monad. But then I would have to specify constraints likeMonadLog m
,MonadIO m
andMonadUnliftIO m
so I decided to stay concrete for now. -
The env in
ReaderT env IO
is only used for storing request-dependent info, particular to the request's servicing thread. Currently we store the SqliteConnection
allocated to each request, which is set by theRunner
.Unlike in other application architectures, the reader's env is not used for storing static dependencies (like, say, a logger function). Those are instead passed as regular parameters of the bean constructor functions.
-
Beans don't have to know about their own dependencies' dependencies. The
Runner
doesn't care that theCommentsServer
uses aCommentsRepository
for example. -
At the composition root of the application, all types are known. It's a good place to add extra logging (using a decorator) without touching the bean implementations.
Because the composition root sits near the top of the module dependency chain, recompilations after having modified it should (?) be fast.
-
Using sqlite3 in a shell script
One way to use sqlite3 in a shell script is to use "echo" or "cat" to generate a sequence of commands in a file, then invoke sqlite3 while redirecting input from the generated command file. This works fine and is appropriate in many circumstances. But as an added convenience, sqlite3 allows a single SQL command to be entered on the command line as a second argument after the database name.