跳到正文
cccskills
免费在 GitHub 公开

grain-todo-processor

Implement event-driven side effects that react to domain events

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

查看安装方法

包含的文件(1)

  • SKILL.md5.9 KB

SKILL.md(原文)

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

Grain Todo Processor Pattern

Todo processors are policies that react to domain events and emit follow-up events or perform side effects.

Required Imports

(ns ai.obney.orc.my-service.core.todo-processors
  (:require [ai.obney.orc.my-service.core.read-models :as rm]
            [ai.obney.grain.event-store-v3.interface :refer [->event]]
            [ai.obney.grain.todo-processor-v2.interface :refer [defprocessor]]
            [ai.obney.grain.time.interface :as time]))

Processor Template

(defprocessor :ns processor-name
  {:topics #{:ns/triggering-event}}
  "Docstring explaining what this processor does and when."
  [context]
  (let [event (:event context)
        entity-id (:entity-id event)
        ;; Check current state using read models
        entity (rm/get-entity context entity-id)]
    (if (should-take-action? entity)
      ;; Emit follow-up events
      {:result/events
       [(->event
         {:type :ns/follow-up-action
          :tags #{[:entity entity-id]}
          :body {:entity-id entity-id
                 :triggered-by (:event/type event)
                 :processed-at (str (time/now))}})]}
      ;; No action needed
      {})))

The generated var name is ns-processor-name (e.g., (defprocessor :crm ensure-attribution ...) creates var crm-ensure-attribution).

Return Patterns

Emit Events (pure -- batch checkpointed)

{:result/events [event1 event2]}

Side Effect with Checkpoint Control

{:result/effect (fn [] (send-email! ...))
 :result/checkpoint :after}  ;; or :before

No Action

{}

Auto-Registration

defprocessor registers the processor in a global registry automatically. There is no manual registry map (def todo-processors) needed. The control plane discovers processors from the registry and starts them when a tenant is assigned to the node.

Interface.clj only needs a bare side-effect require to load the namespace:

(ns ai.obney.orc.my-service.interface
  (:require ;; Side-effect requires
            [ai.obney.orc.my-service.core.commands]
            [ai.obney.orc.my-service.core.queries]
            [ai.obney.orc.my-service.core.todo-processors]  ;; bare require, no alias
            ...))

;; No todo-processors re-export needed

No manual wiring is needed -- the control plane discovers processors from the global registry.

Common Use Cases

Ensure Default Value

(defprocessor :crm ensure-attribution
  {:topics #{:crm/contact-created}}
  "Record default attribution if none exists."
  [context]
  (let [{:keys [contact-id]} (:event context)
        existing (rm/get-contact context contact-id)]
    (if (:attribution existing)
      {}  ;; Already has attribution
      {:result/events
       [(->event
         {:type :crm/attribution-recorded
          :tags #{[:contact contact-id]}
          :body {:contact-id contact-id
                 :attribution {:source :manual_entry
                               :recorded-at (str (time/now))}}})]})))

Duplicate Detection

(defprocessor :crm check-for-duplicates
  {:topics #{:crm/contact-created}}
  "Check for duplicates when contact is created."
  [context]
  (let [{:keys [contact-id]} (:event context)
        email (normalize-email (get-in (:event context) [:field-values :email]))
        existing (rm/get-contacts-by-email context email)]
    (if (seq (remove #(= contact-id (:id %)) existing))
      {:result/events
       [(->event
         {:type :crm/duplicate-detected
          :tags #{[:contact contact-id]}
          :body {:contact-id contact-id
                 :match-type :email
                 :match-value email}})]}
      {})))

Cascade Operations

(defprocessor :crm end-relationships-on-archive
  {:topics #{:crm/contact-archived}}
  "End all relationships when contact is archived."
  [context]
  (let [{:keys [contact-id]} (:event context)
        rels (rm/get-relationships-for-contact context contact-id)
        active-rels (filter #(nil? (:end-date %)) rels)]
    (if (seq active-rels)
      {:result/events
       (mapv (fn [rel]
               (->event
                {:type :crm/relationship-ended
                 :tags #{[:relationship (:id rel)]}
                 :body {:relationship-id (:id rel)
                        :end-date (str (java.time.LocalDate/now))
                        :reason "Contact archived"}}))
             active-rels)}
      {})))

Transform Source Data

(defprocessor :crm record-lead-attribution
  {:topics #{:crm/lead-captured}}
  "Map lead form type to attribution source."
  [context]
  (let [{:keys [contact-id form-type form-id]} (:event context)
        source (case form-type
                 :application :application_form
                 :intake :intake_form
                 :interest :interest_form
                 :manual_entry)]
    {:result/events
     [(->event
       {:type :crm/attribution-recorded
        :tags #{[:contact contact-id]}
        :body {:contact-id contact-id
               :attribution {:source source
                             :form-id form-id}}})]}))

Conventions

  • Processors receive a context map with :event, :event-store, :tenant-id
  • Use read models to check current state before acting
  • Return {:result/events [...]} or {} (empty map)
  • One processor can listen to multiple topics via the :topics set
  • Multiple processors can listen to the same topic
  • Normalize data before comparison (email lowercase, phone digits only)
  • Include reason/context in generated events for audit trail

Reference Files

  • components/user-service/src/ai/obney/workshop/user_service/core/todo_processors.clj - Email notifications
  • components/crm-service/src/ai/obney/workshop/crm_service/core/todo_processors.clj - Attribution & duplicates

评价

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

评论

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

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

提交后会立即公开。请勿填写邮箱、电话等个人信息。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日 更新

Create scheduled background jobs that run on cron schedules

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

ObneyAI/orc222026年10月20日 更新

Implement query handlers that read from event-sourced read models

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

ObneyAI/orc222026年10月20日 更新

查看 ObneyAI 的全部技能

报告此技能的问题