{"id":15062806,"url":"https://github.com/nomnom-insights/nomnom.eternity","last_synced_at":"2025-10-29T16:24:10.177Z","repository":{"id":45542704,"uuid":"216621073","full_name":"nomnom-insights/nomnom.eternity","owner":"nomnom-insights","description":"⏲Function scheduling Component, running forever","archived":false,"fork":false,"pushed_at":"2021-12-09T10:16:00.000Z","size":22,"stargazers_count":11,"open_issues_count":0,"forks_count":0,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-05T20:45:08.700Z","etag":null,"topics":["clojure","component","scheduler","scheduler-cron"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nomnom-insights.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-21T17:02:05.000Z","updated_at":"2021-12-09T10:16:00.000Z","dependencies_parsed_at":"2022-07-17T04:19:34.595Z","dependency_job_id":null,"html_url":"https://github.com/nomnom-insights/nomnom.eternity","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nomnom-insights%2Fnomnom.eternity","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nomnom-insights%2Fnomnom.eternity/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nomnom-insights%2Fnomnom.eternity/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nomnom-insights%2Fnomnom.eternity/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nomnom-insights","download_url":"https://codeload.github.com/nomnom-insights/nomnom.eternity/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248198884,"owners_count":21063628,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","component","scheduler","scheduler-cron"],"created_at":"2024-09-24T23:46:47.777Z","updated_at":"2025-10-29T16:24:10.035Z","avatar_url":"https://github.com/nomnom-insights.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Eternity\n\n[![Clojars Project](https://img.shields.io/clojars/v/nomnom/eternity.svg)](https://clojars.org/nomnom/eternity)\n\n[![CircleCI](https://circleci.com/gh/nomnom-insights/nomnom.eternity.svg?style=svg)](https://circleci.com/gh/nomnom-insights/nomnom.eternity)\n\n\u003cimg src=\"https://i.annihil.us/u/prod/marvel//universe3zx/images/c/c5/Eternitystand.jpg\" align=right heigth=\"250px\" \u003e\n\n\nA [Component](https://github.com/stuartsierra/component) for scheduling jobs which is suitable to be used in component systems. Wrapper around [at-at](https://github.com/overtone/at-at) which is a wrapper around `java.util.concurrent.ScheduledThreadPoolExecutor`\n\n## Description\n\nThere are two components that you will need to setup in your system:\n\n- `scheduler-pool` the `j.u.c.ScheduledThreadPoolExecutor` instance, can (should!) be shared among multiple scheduled jobs\n- your scheduled job component(s)\n\nThe scheduled job component:\n  - requires the scheduler pool as a dependency, by default it uses the `:scheduler-pool` key\n  - supports the following config options:\n     - `name`: just to keep track of things\n     - `frequency`: how often should scheduler run the function, can be specified in ms (100) or as date string \"1m\"/\"2h\"\n     - `delay` (optional): used for fine-grained control of when to run the *first time*, can either be specifed in ms (1000) or as time string specifying when scheduler should start\n       (\"07:00\" - at 7am, \"30\" - at next half hour). Use this option along with `frequency` setting to have schedulers run a function every 4 hours, exactly 15 past the hour\n  - passed-in scheduled function: will receive 1 argument: the map of components it depends on\n\n## How to use it\n\nAdd component to your system map.\n\n```clojure\n(require '[eternity.scheduler :as scheduler]\n         '[eternity.pool :as pool])\n\n(def system-map\n  {:scheduler-pool (pool/create)\n   :every-4h-stuff (component/using\n                    (scheduler/create\n                     ;; the config: will run every 4hrs, at the beginning of the hour\n                     {:name \"scheduler-1\" :delay \"00\" :frequency \"4h\"}\n                     ;; scheduled function\n                     (fn [components]\n                       (do-stuff (:db components)))\n                     [:scheduler-pool :db]))\n   :every-15s (component/using\n                    (scheduler/create\n                     ;; the config: will run every 15s, right from the start\n                     {:name \"scheduler-2\" :frequency \"15s\"}\n                     ;; scheduled function\n                     (fn [components]\n                       (do-very-frequesnt-stuff components))\n                     [:scheduler-pool :redis :publisher]))})\n```\n\nFollowing setup will run scheduled job every 4 hours starting at the beginning of the hour.\n On each tick function `do-stuff` will be evaluated where `db` will be passed as arg.\n\nSecond scheduled function will run every 15s, as soon as the system starts, the `:redis` and `:publisher` components will be passed in as the dependency map.\n\n### Middlewares\n\nEternity ships with middlewares, which are just functions wrapping execution of the scheduled function. They're inspired by Ring and you can add yours as long as they do the following:\n\n- accept the scheduled function as the argument\n- return a function which accepts  `components` map as an argument\n- call the scheduled functions with the components as the argument\n\nThis way you can compose many middlewares by nesting function calls. Just like Ring.\n\nHere's a sample/simple logging middleware:\n\n```clojure\n(defn with-logging [scheduled-fn]\n  (fn [component]\n    (log/info \"about to do work\")\n    (scheduled-fn component)\n    (log/info \"done!\")))\n```\n\nYou can use it in your Component system like so:\n\n```clojure\n{ :scheduled-fn (component/using\n                  {:name \"a sched\" :frequency \"15s\" }\n                  (with-logging (fn [component]  (db/do-query (:db-conn component)))))\n                [:db-conn :scheduler-pool]}\n\n```\n\n#### Included Middlewares\n\n#### `with-lock` (`eternity.middleware.with-lock`)\n\nDepends on [Lockjaw](https://github.com/nomnom-insights/nomnom.lockjaw) and Postgres, guarantees that only the lock holder will do the job. Requires a `:lock` component as a dependency. You can use [nomnom/utility-belt.sql](https://github.com/nomnom-insights/nomnom.utility-belt.sql) if you need a JDBC connection pool component.\nExample:\n\n```clojure\n\n{:scheduler-pool (eternity.pool/create)\n  :db-conn (db-pool/create pg-config)\n  :lock (component/using\n         (lockjaw.create {:name \"a-service-name\"})\n         [:db-conn])\n :scheduled-thing (component/using\n                   (eternity.scheduler\n                    {:name \"scheduled-thing\" :frequency \"8h\"}\n                    (eternity.middleware.with-lock/handler\n                     ;; will execute only if the lock is acquired!\n                     (fn [component]\n                       (do-stuff component))))\n                   [:lock :scheduler-pool])}\n\n\n```\n\n#### `with-exception-tracking` (`eternity.middleware.with-exception-tracking`)\n\nDepends on [Caliban](https://github.com/nomnom-insights/nomnom.caliban). Requires `:exception-tracker` component as a dependency.\n\n\n```clojure\n\n{:scheduler-pool (eternity.pool/create)\n :exception-tracker (caliban.tracker/create config)\n :scheduled-thing (component/using\n                   (eternity.scheduler\n                    {:name \"scheduled-thing\" :frequency \"8h\"}\n                    (eternity.middleware.with-exception-tracking/handler\n                     ;; if do -stuff throws an exception it will be logged and reported\n                     (fn [component]\n                       (do-stuff component))))\n                   [:exception-tracker :scheduler-pool])}\n\n\n```\n\n\n### Combining middlewares\n\nJust like in Ring, middlewares can be combined:\n\n```clojure\n\n{:scheduler-pool (eternity.pool/create)\n :exception-trakcer (caliban.tracker/create config)\n :lock (component/using (lockjaw.create {:name \"service\"}) [:db-conn])\n :scheduled-thing (component/using\n                   (eternity.scheduler\n                    {:name \"scheduled-thing\" :frequency \"8h\"}\n                    ;; Stack middlewares:\n                    (eternity.middleware.with-exception-tracking/handler\n                     (eternity.middleware.with-lock/handler\n                      (fn [component]\n                        (do-stuff component)))))\n                   [:exception-tracker :lock :scheduler-pool])}\n\n\n```\n\n\n## Roadmap\n\n\n- [ ] *Maybe* remove direct dependency on `at-at` and use the Scheduled ThreadPool Executor directly\n- [ ] More middlewares\n\n# Authors\n\n\u003csup\u003eIn alphabetical order\u003c/sup\u003e\n\n- [Afonso Tsukamoto](https://github.com/AfonsoTsukamoto)\n- [Łukasz Korecki](https://github.com/lukaszkorecki)\n- [Marketa Adamova](https://github.com/MarketaAdamova)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnomnom-insights%2Fnomnom.eternity","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnomnom-insights%2Fnomnom.eternity","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnomnom-insights%2Fnomnom.eternity/lists"}