=== 1. Create a table to store jobs
In this example, our jobs table will be called `jobs_test`
```
ghci> import Datasbe.PostgreSQL.Simple (connectPostgreSQL)
ghci> import OddJobs.Migrations
ghci> conn <- connectPostgreSQL "dbname=jobs_test user=jobs_test password=jobs_test host=localhost"
ghci> createJobTable conn "jobs_test"
```
=== 2. Create a module for your job-runner
Ideally, this module should be compiled into a separate executable and should depend on your application's library module. If you do not wish to deploy odd-jobs as an independent executable, you may embed it within your main application's executable as well. This is described in [deployment](#deployment).
\begin{code}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
module OddJobsCliExample where
import OddJobs.Job (Job(..), ConcurrencyControl(..), Config(..), throwParsePayload)
import OddJobs.ConfigBuilder (mkConfig, withConnectionPool, defaultTimedLogger, defaultLogStr, defaultJobType)
import OddJobs.Cli (defaultMain)
-- Note: It is not necessary to use fast-logger. You can use any logging library
-- that can give you a logging function in the IO monad.
import System.Log.FastLogger(withTimedFastLogger, LogType'(..), defaultBufSize)
import System.Log.FastLogger.Date (newTimeCache, simpleTimeFormat)
import Data.Text (Text)
import Data.Aeson as Aeson
import GHC.Generics
-- This example is using these functions to introduce an artificial delay of a
-- few seconds in one of the jobs. Otherwise it is not really needed.
import OddJobs.Types (delaySeconds, Seconds(..))
\end{code}
=== 3. Set-up a Haskell type to represent your job-payload
- Ideally, this data-type should be defined _inside_ your application's code and the module containing this type-definition should be part of the `exposed-modules` stanza.
- To work with all the default settings provided by 'OddJobs.ConfigBuilder' this data-type should have a **"tagged" JSON serialisation,** i.e.:
```json
{"tag": "SendWelcomEmail", "contents": 10}
```
In case your JSON payload does not conform to this structure, please look at [customising the job-payload's structure](#custom-payload-structure).
- In this example, we are _blindly_ deriving `ToJSON` and `FromJSON` instances because the default behaviour of Aeson is to generate a tagged JSON as-per the example given above.
\begin{code}
data MyJob
= SendWelcomeEmail Int
| SendPasswordResetEmail Text
| SetupSampleData Int
deriving (Eq, Show, Generic, ToJSON, FromJSON)
\end{code}
=== 4. Write the core job-runner function
In this example, the core job-runner function is in the `IO` monad. In all probability, you application's code will be in a custom monad, and not IO. Pleae refer to TODO, on how to work with custom monads.
\begin{code}
myJobRunner :: Job -> IO ()
myJobRunner job = do
(throwParsePayload job) >>= \case
SendWelcomeEmail userId -> do
putStrLn $ "This should call the function that actually sends the welcome email. " <>
"\nWe are purposely waiting 60 seconds before completing this job so that graceful shutdown can be demonstrated."
delaySeconds (Seconds 60)
putStrLn "60 second wait is now over..."
SendPasswordResetEmail tkn ->
putStrLn "This should call the function that actually sends the password-reset email"
SetupSampleData userId -> do
Prelude.error "User onboarding is incomplete"
putStrLn "This should call the function that actually sets up sample data in a newly registered user's account"
\end{code}
=== 5. Write the main function using `OddJobs.Cli`
\begin{code}
main :: IO ()
main = do
defaultMain startJobMonitor
where
-- A callback-within-callback function. If the commands-line args contain a
-- `start` command, this function will be called. Once this function has
-- constructed the 'Config' (which requires setting up a logging function,
-- and a DB pool) it needs to execute the `callback` function that is passed
-- to it.
startJobMonitor callback =
-- a utility function provided by `OddJobs.ConfigBuilder` which ensures
-- that the DB pool is gracefully destroyed upon shutdown.
withConnectionPool (Left "dbname=jobs_test user=jobs_test password=jobs_test host=localhost")$ \dbPool -> do
-- Boilerplate code to setup a TimedFastLogger (from the fast-logger library)
tcache <- newTimeCache simpleTimeFormat
withTimedFastLogger tcache (LogFileNoRotate "oddjobs.log" defaultBufSize) $ \logger -> do
-- Using the default string-based logging provided by
-- `OddJobs.ConfigBuilder`. If you want to actually use
-- structured-logging you'll need to define your own logging function.
let jobLogger = defaultTimedLogger logger (defaultLogStr defaultJobType)
cfg = mkConfig jobLogger "jobs" dbPool (MaxConcurrentJobs 50) myJobRunner Prelude.id
-- Finally, executing the callback function that was passed to me...
callback cfg
\end{code}
=== 6. Compile and start the Odd Jobs runner
```
$ stack install :exe:odd-jobs-cli
$ odd-jobs-cli start --daemonize --web-ui-basic-auth=oddjobs --web-ui-basic-password=awesome
```
=== 7. Enqueue some jobs from within your application's code
```
ghci> import OddJobs.Job (createJob)
ghci> import Database.PostgreSQL.Simple
ghci> conn <- connectPostgreSQL "dbname=jobs_test user=jobs_test password=jobs_test host=localhost"
ghci> createJob conn $ SendWelcomeEmail 10
ghci> createJob conn $ SetupSampleData 10
```
=== 8. Check-out the awesome web UI
Visit [http://localhost:7777](http://localhost:7777) (`username=oddjobs` / `password=awesome` as configured earlier).
=== 9. Check-out the log file to see what Odd Jobs is doing
```
$ tail -f oddjobs.log
```
=== 10. Finally, shutdown Odd Jobs _gracefully_
Please read [graceful shutdown](#graceful-shutdown) to know more.
```
$ odd-jobs-cli stop --timeout 65
```