Jack Wu
Jack Wu

Reputation: 457

clojure, how to reduce repeated code (potentially using macro)

TL;DR

how to reduce below repeated code, like create two job / trigger from job-inventory, instead of repeat twice and create terms


;; deps in project.clj
;; [clojurewerkz/quartzite "2.1.0"]


(ns hello.scheduler
  (:require [clojurewerkz.quartzite.scheduler :as qs]
            [clojurewerkz.quartzite.triggers :as t]
            [clojurewerkz.quartzite.jobs :as j]
            [clojurewerkz.quartzite.jobs :refer [defjob]]
            [clojurewerkz.quartzite.schedule.cron :as cron])
  (:use clojure.tools.logging)
  (:gen-class))

(def job-inventory
  [{:name "add" :task '(+ 1 1) :cron "0/5 * * ? * *"}
   {:name "multiply" :task '(* 4 5)  :cron "0/3 * * ? * *"}])

(defjob add [ctx] (info "add called, return" (+ 1 1)))
(defjob multiply [ctx] (info "multiply called, return" (* 2 3)))

(defn auto
  []
  (let [s   (-> (qs/initialize) qs/start)
        _ (qs/clear! s)
        job (j/build
             (j/of-type add)
             (j/with-identity (j/key "job.add")))
        trigger (t/build
                 (t/with-identity (t/key "trigger.add"))
                 (t/start-now)
                 (t/with-schedule (cron/schedule
                                   (cron/cron-schedule "0/5 * * ? * *"))))
        _ (qs/schedule s job trigger)
        
        job (j/build
             (j/of-type multiply)
             (j/with-identity (j/key "job.multiply")))
        trigger (t/build
                 (t/with-identity (t/key "trigger.multiply"))
                 (t/start-now)
                 (t/with-schedule (cron/schedule
                                   (cron/cron-schedule "0/3 * * ? * *"))))
        _ (qs/schedule s job trigger)
        ]
    ))


similar to what's described in http://clojurequartz.info/articles/getting_started.html , I have block of code to create jobs and hooks them for execution

quesetion is, when I get more and more of them, I wonder if I could have a better way of manage them, like create / spawn from that job-inventory, instead of actually creating varaibles like add or multiply

so, asking for one more layers of looping are there ways to utilize function programming, and avoid create new names ( in traditional language says python qt, if I had a sets of button, I could just smash into a giant dictionary, and loop over to create / disable, instead actually create each name as a top level varible )

I tried macro but it says unable to resolve class add, so guess I used it wrong

Upvotes: 1

Views: 170

Answers (1)

pete23
pete23

Reputation: 2280

The key thing to remember is that functions are data. Whilst you can't dynamically create types very easily (as opposed to instances that implement an interface, via reify), you can statically create a class which then proxies your functions.

First let's make the :task of the job-inventory a function.

(def job-inventory
  [{:name "add" :task (fn [] (println (+ 1 1))) :cron "0/5 * * ? * *"}
   {:name "multiply" :task (fn [] (println (* 4 5)))  :cron "0/3 * * ? * *"}])

Then we need the proxy job class. This executes a function it finds within the job data.

;; require clojurewerkz.quartzite.conversion :as qc
(defjob proxy-job [ctx]
  (let [ctx (qc/from-job-data ctx)]
    ((ctx "proxied-fn"))))

Then we create a schedule function which takes a map from the job-inventory and schedules it using the proxy-job to indirect.

(defn schedule [scheduler {:keys [:name :task :cron]}]
  (let [job (j/build
             (j/of-type proxy-job)
             (j/using-job-data {"proxied-fn" task})
             (j/with-identity (j/key (str "job." name))))
        trigger (t/build
                 (t/with-identity (t/key (str "trigger." name)))
                 (t/start-now)
                 (t/with-schedule (cron/schedule
                                   (cron/cron-schedule "0/3 * * ? * *"))))]
  (qs/schedule scheduler job trigger)
  scheduler)

(reduce scheduler schedule job-inventory)

It's possible this approach will fall apart if quartz decides to serialise/deserialise the job data. I leave fixing this with another layer of indirection as an easy exercise for the interested reader - to be honest my initial thoughts are that we create named functions which we then refer to by symbol, but it complicates the proxy and makes you wonder why not just use defjob. If you're prepared to defjob your functions, you can still refer to them in a job-inventory and have a data-based job builder function.

Upvotes: 1

Related Questions