跳到正文
cccskills
免费在 GitHub 公开

grain-periodic-task

Create scheduled background jobs that run on cron schedules

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(1)

  • SKILL.md6.3 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Grain Periodic Task Pattern

Periodic tasks are scheduled background jobs that run on cron schedules. They use defperiodic to emit trigger events, and a corresponding defprocessor handles the actual work.

See Pattern Compendium section 1.4.1 for the authoritative reference.

Two-File Pattern

Periodic tasks are split into two parts:

  1. periodic_tasks.clj -- defperiodic emits a lightweight trigger event per tenant on schedule
  2. todo_processors.clj -- defprocessor subscribes to the trigger event and does the real work

The framework iterates over all tenants automatically. The defperiodic handler receives [tenant-id time] (NOT [context time]) and returns {:result/events [...]}. The framework appends the events per-tenant.

Periodic Task File (core/periodic_tasks.clj)

(ns ai.obney.orc.my-service.core.periodic-tasks
  "Scheduled trigger events for my-service."
  (:require [ai.obney.grain.event-store-v3.interface :refer [->event]]
            [ai.obney.grain.periodic-task.interface :refer [defperiodic]]))

(defperiodic :my-service send-reminders
  {:schedule {:cron "0 * * * *" :timezone "America/Chicago"}}
  "Emit reminder-check trigger for each tenant every hour."
  [_tenant-id time]
  {:result/events
   [(->event {:type :my-service/reminder-check-triggered
              :tags #{[:reminder-check (random-uuid)]}
              :body {:triggered-at (str time)}})]})

The generated var name is my-service-send-reminders.

Corresponding Processor (core/todo_processors.clj)

(ns ai.obney.orc.my-service.core.todo-processors
  (:require [ai.obney.orc.my-service.core.read-models :as rm]
            [ai.obney.grain.command-processor-v2.interface :as cp]
            [ai.obney.grain.time.interface :as time]
            [ai.obney.grain.todo-processor-v2.interface :refer [defprocessor]]
            [com.brunobonacci.mulog :as u]))

(defprocessor :my-service reminder-runner
  {:topics #{:my-service/reminder-check-triggered}}
  "Process pending reminders when trigger fires."
  [context]
  (let [items (rm/get-pending-items context)]
    (u/log ::reminder-runner :count (count items))
    (doseq [item items]
      (cp/process-command
       (assoc context :command {:command/id (random-uuid)
                                :command/timestamp (time/now)
                                :command/name :my-service/send-reminder
                                :item-id (:id item)})))
    {}))

Complete Example: Membership Billing

;; periodic_tasks.clj
(defperiodic :membership billing-check
  {:schedule {:cron "0 6 * * *" :timezone "America/Chicago"}}
  "Daily billing check trigger."
  [_tenant-id time]
  {:result/events [(->event {:type :membership/billing-check-triggered
                             :tags #{[:billing-check (random-uuid)]}
                             :body {:triggered-at (str time)}})]})

;; todo_processors.clj
(defprocessor :membership billing-runner
  {:topics #{:membership/billing-check-triggered}}
  "Process memberships due for billing."
  [context]
  (let [today (str (java.time.LocalDate/now))
        due (rm/get-memberships-due-for-billing context today)]
    (doseq [m due]
      (cp/process-command
       (assoc context :command {:command/id (random-uuid)
                                :command/timestamp (time/now)
                                :command/name :membership/initiate-billing
                                :membership-id (:membership-id m)})))
    {}))

Schedule Formats

FormatExampleDescription
Cron{:cron "0 * * * *" :timezone "America/Chicago"}Standard cron (minute hour day month weekday)

Common cron patterns:

  • "0 * * * *" -- every hour on the hour
  • "*/15 * * * *" -- every 15 minutes
  • "0 3 * * *" -- daily at 3 AM
  • "0 6 * * *" -- daily at 6 AM
  • "0 0 * * 1" -- weekly on Monday at midnight

CAS for Idempotency

When a trigger event must be processed at most once, use CAS inside the defprocessor:

(defprocessor :my-service digest-sender
  {:topics #{:my-service/digest-triggered}}
  "Send daily digest -- at most once per user per day."
  [context]
  (let [users (rm/get-users-needing-digest context)]
    (doseq [user users]
      (es/append (:event-store context)
                 {:tenant-id (:tenant-id context)
                  :events [(->event {:type :my-service/digest-sent
                                     :tags #{[:user (:user-id user)]}
                                     :body {:user-id (:user-id user)
                                            :date (str (java.time.LocalDate/now))}})]
                  :cas {:types #{:my-service/digest-sent}
                        :tags #{[:user (:user-id user)]}
                        :predicate-fn (fn [existing]
                                        (empty? (into [] existing)))}}))
    {}))

The CAS predicate checks that no :my-service/digest-sent event exists for this user with these tags. If another instance already emitted one, the append silently fails.

Auto-Registration and Wiring

Both defperiodic and defprocessor register in global registries automatically. No manual registry maps are needed.

Interface.clj only needs bare side-effect requires:

(ns ai.obney.orc.my-service.interface
  (:require [ai.obney.orc.my-service.core.commands]
            [ai.obney.orc.my-service.core.queries]
            [ai.obney.orc.my-service.core.todo-processors]   ;; bare require
            [ai.obney.orc.my-service.core.periodic-tasks]    ;; bare require
            [ai.obney.orc.my-service.core.read-models :as rm]))

;; No periodic-tasks or todo-processors re-export needed
(def get-item rm/get-item)

No manual wiring is needed. pt/start-periodic-triggers! discovers periodic tasks from the global registry. The control plane discovers todo processors the same way.

Reference Files

  • components/membership-service/src/.../core/periodic_tasks.clj -- Billing check trigger
  • components/scheduling-service/src/.../core/periodic_tasks.clj -- Scheduling triggers
  • components/user-service/src/.../core/periodic_tasks.clj -- User maintenance
  • docs/contributors/CONTRIBUTOR-GRAIN-PATTERNS.md section 1.4.1 -- Authoritative periodic task reference

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

allium

免费

Give your AI agents something more useful than a prompt. Velocity through clarity.

中文简介正在准备中。当前显示原文说明。

ObneyAI/orc222026年10月20日 更新

distill

免费

Extract an Allium specification from an existing codebase. Use when the user has existing code and wants to distil behaviour into a spec, reverse engineer a specification from implementation, generate a spec from code, turn implementation into a behavioural specification, or document what a codebase does in Allium terms.

中文简介正在准备中。当前显示原文说明。

ObneyAI/orc222026年10月20日 更新

elicit

免费

Run a structured discovery session to build an Allium specification through conversation. Use when the user wants to create a new spec from scratch, elicit or gather requirements, capture domain behaviour, specify a feature or system, define what a system should do, or is describing functionality and needs help shaping it into a specification.

中文简介正在准备中。当前显示原文说明。

ObneyAI/orc222026年10月20日 更新

Implement command handlers that validate state and emit events using the Grain framework

中文简介正在准备中。当前显示原文说明。

ObneyAI/orc222026年10月20日 更新

Implement query handlers that read from event-sourced read models

中文简介正在准备中。当前显示原文说明。

ObneyAI/orc222026年10月20日 更新

Build read models by reducing events into queryable state

中文简介正在准备中。当前显示原文说明。

ObneyAI/orc222026年10月20日 更新

查看 ObneyAI 的全部技能

报告此技能的问题