1

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

pete23
  • 2,204
  • 23
  • 28
Jack Wu
  • 447
  • 4
  • 14
  • 1
    A related lib you may wish to consider: https://github.com/overtone/at-at – Alan Thompson May 24 '21 at 14:57
  • @AlanThompson thanks for your attention, there are only three tasks, but in such quartz terms, I had to build a job-a, of type job-a, and a trigger-a, and then hook up to schedule, may consider to take a look at their tutorial, there are no strict connection or dependencies between these jobs. – Jack Wu May 24 '21 at 17:32
  • @AlanThompson thanks for the clue and would definitely take a look, here I got a project that's already kind of cron format schedule configured, so would be more appealing if that's compatible, appreciate your time : ) – Jack Wu May 24 '21 at 17:36
  • @AlanThompson here the question may goes, how could I call `defjob job-a defjob job-b defjob job-c` in a loop ( and later reference them ), kind of code generation trick. – Jack Wu May 24 '21 at 17:38
  • 1
    @AlanThompson Hi Alan, I've reduce the description and just show the code piece, hope it's slighly more clear – Jack Wu May 27 '21 at 14:14

1 Answers1

1

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.

pete23
  • 2,204
  • 23
  • 28