componentm-0.0.0.0: src/Control/Monad/Component.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-|
Module : Control.Monad.Component
Description : Sane resource allocation library for complex applications
Copyright : (c) Roman Gonzalez, 2017-2018
License : MIT
Maintainer : open-source@roman-gonzalez.info
Stability : experimental
= Why use 'ComponentM'?
'ComponentM' values wraps vanilla 'IO' sub-routines whose responsibility is to
allocate resources that your application may need (e.g. database connections,
tcp sockets, etc). Your program will execute these 'ComponentM' sub-routines at
the beginning of it's lifecyle, building an environment that your main
application needs in order to work as intended.
By using 'ComponentM' sub-routines your program will automatically:
* Compose the cleanup sub-routines of all your allocated resources
* Keep track of initialization time for each resource needed in your application
* Keep track of teardown time for each resources needed in your application.
* Isolate the teardown of each resource in your application, ensuring no thrown
exception will affect the cleanup of resources.
* Initialize resources concurrently when using 'Applicative' notation
* Build a dependency graph of your application resources when using
'Applicative' or 'Monad' notation; and then guarantees the execution of
cleanup operations in a topological sorted order
* Make sure that previously allocated resources are cleaned up when a resource
throws an exception on initialization
* Report all exceptions thrown on each resource teardown
* Document (through types) what is the purpose of some of the 'IO' sub-routines
in your program
These properties are crucial when applications need to run for long periods of
time and they are reloaded (without a process restart). It also ensures that
resources are cleaned tightly when doing REPL driven development through GHCi.
-}
module Control.Monad.Component
(
-- * How to build 'ComponentM' values
-- $howto_componentm_values
buildComponent
, buildComponent_
-- * Making 'ComponentM' values useful
-- $monad_and_runners
, ComponentM
, runComponentM
, runComponentM1
-- * Error Records
-- $errors
, ComponentError (..)
, ComponentBuildError (..)
-- * 'ComponentM' tracing accessors
, ComponentEvent (..)
, Build
, buildElapsedTime
, buildFailure
, BuildResult
, toBuildList
-- * Re-exports
, TeardownResult (..)
, throwM
) where
import Control.Monad.Catch (throwM)
import Control.Monad.Component.Internal.Core (buildComponent,
buildComponent_,
runComponentM,
runComponentM1)
import Control.Monad.Component.Internal.Types (Build (..),
BuildResult (..),
ComponentBuildError (..),
ComponentError (..),
ComponentEvent (..),
ComponentM)
import Control.Teardown (TeardownResult (..))
{- $howto_componentm_values
'ComponentM' values are built from vanilla 'IO' sub-routines that allocate resources, the
two functions provided are:
['buildComponent_']: Used when a component in your application does not allocate
a resource
['buildComponent']: Used when a component in your application allocates a
resource and requires a cleanup on teardown
Following is an example on how to
@
{-# LANGUAGE PackageImports #-}
import "sqlite-simple" qualified Database.SQLite.Simple as SQLite
import "componentm" Control.Monad.Component (ComponentM, buildComponent, buildComponent_)
-- | App environment record
data AppEnv = AppEnv { appDb :: !SQLite.Connection }
-- | Configuration record
data Config = Config { dbPath :: !String }
readConfig :: IO Config
readConfig =
-- NOTE: Here we would have a more sophisticated algorithm for fetching
-- configuration values for our app
return (Config ":memory:")
configComponent :: ComponentM AppConfig
configComponent = buildComponent_ "Config" $ do
readConfigFile "./resources/config.yml"
dbComponent :: FilePath -> ComponentM SQLite.Connection
dbComponent dbPath =
buildComponent "Database" (SQLite.open dbPath) SQLite.close
buildAppEnv :: ComponentM AppEnv
buildAppEnv = do
config <- configComponent
AppEnv <$> dbComponent (dbPath config)
@
In the previous example, we use both 'buildComponent_' and 'buildComponent' to
create different components that our application needs.
-}
{- $monad_and_runers
To execute our 'ComponentM' sub-routines, we can use two different functions
'runComponentM' or 'runComponentM1'; following is an example:
@
appMain :: AppEnv -> IO ()
appMain = error "pending"
main :: IO ()
main =
runComponentM1
-- Our logging function
print
-- The name of our application
"my-fancy-application"
-- The 'ComponentM' sub-routine that builds the 'AppEnv'
buildAppEnv
-- Our main application runs here
appMain
@
-}
{- $errors
There are two possible failures that the 'runComponentM' functions can thrown
['ComponentBuildFailed']: This error happens when allocation of some component's
resource fails
['ComponentRuntimeFailed']: This error happens when there is an exception thrown
from our main application callback
-}