Skip to content
cccskills
FreePublished on GitHub

grain-query-handler

Implement query handlers that read from event-sourced read models

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md4.3 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Grain Query Handler Pattern

Required Imports

(ns my.service.core.queries
  (:require [ai.obney.grain.event-store-v3.interface :as es]
            [ai.obney.grain.query-processor.interface :refer [defquery]]
            [my.service.interface.read-models :as rm]
            [cognitect.anomalies :as anom]))

Query Template

(defquery :namespace query-name
  "Docstring describing what the query returns."
  [{{:keys [entity-id filter-param]} :query
    :keys [event-store other-dependency]}]

  (let [entity (rm/get-entity event-store entity-id)]
    (if entity
      {:query/result {:entity entity}}
      {::anom/category ::anom/not-found
       ::anom/message "Entity not found"})))

Return Patterns

Success

{:query/result {:entity data
                :related-data more-data}}

Error

{::anom/category ::anom/not-found
 ::anom/message "Human readable message"}

Fat Query Pattern (Screen Queries)

Load all data needed for a UI screen in one query:

(defquery :advisor meetings-screen
  "Fat query for meetings screen - returns all data needed."
  [{{:keys [student-id]} :query
    :keys [event-store]}]

  (let [;; Get primary entity
        student (rm/get-student event-store student-id)
        ;; Get related data
        meetings (rm/get-meetings-for-student event-store student-id)
        ;; Get metadata/options
        meeting-types (rm/get-meeting-types event-store)]

    (if student
      {:query/result {:student student
                      :meetings meetings
                      :meeting-types meeting-types}}
      {::anom/category ::anom/not-found
       ::anom/message "Student not found"})))

List Query with Pagination

(defquery :crm list-contacts
  "List contacts with optional filters and pagination."
  [{{:keys [type-slug status limit offset]} :query
    :keys [event-store]}]

  (let [;; Get all matching contacts
        all-contacts (cond->> (rm/get-contacts-all event-store)
                       type-slug (filter #(= type-slug (:type-slug %)))
                       status (filter #(= status (:status %))))
        total (count all-contacts)
        ;; Apply pagination
        paginated (cond->> all-contacts
                    offset (drop offset)
                    limit (take limit))]

    {:query/result {:contacts (vec paginated)
                    :total total}}))

Batch Loading Pattern

Pre-load related data to avoid N+1 queries:

(defquery :crm list-contacts-with-types
  [{{:keys [type-slug]} :query
    :keys [event-store]}]

  (let [;; Pre-load all contact types (1 query instead of N)
        types-by-id (rm/get-contact-types-by-id event-store)
        ;; Get contacts
        contacts (rm/get-contacts-by-type event-store type-slug)
        ;; Enrich with type data
        enriched (mapv #(assoc % :contact-type (get types-by-id (:type-id %)))
                       contacts)]

    {:query/result {:contacts enriched}}))

Composing Read Models

(defquery :crm get-contact
  "Get contact with attribution in single query."
  [{{:keys [contact-id]} :query
    :keys [event-store]}]

  (let [contact (rm/get-contact event-store contact-id)]
    (if contact
      (let [;; Get related data
            relationships (rm/get-relationships-for-contact event-store contact-id)
            attribution (rm/get-attribution event-store contact-id)]
        {:query/result {:contact (cond-> contact
                                   attribution (assoc :attribution attribution)
                                   (seq relationships) (assoc :relationships relationships))}})
      {::anom/category ::anom/not-found
       ::anom/message "Contact not found"})))

Conventions

  • Queries are read-only (never emit events)
  • Use read models to get current state
  • Return {:query/result {...}} for success
  • Return anomalies for not-found and errors
  • Use fat queries for screen data (reduces round trips)
  • Batch load related data to avoid N+1 queries
  • Include pagination metadata (:total) for list queries

Reference Files

  • components/crm-service/src/ai/obney/workshop/crm_service/core/queries.clj - CRM queries with batch loading

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

allium

Free

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

The English summary is being prepared. Showing the original description.

ObneyAI/orc22Updated Oct 8, 2026

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.

The English summary is being prepared. Showing the original description.

ObneyAI/orc22Updated Oct 8, 2026

elicit

Free

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.

The English summary is being prepared. Showing the original description.

ObneyAI/orc22Updated Oct 8, 2026

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

The English summary is being prepared. Showing the original description.

ObneyAI/orc22Updated Oct 8, 2026

Create scheduled background jobs that run on cron schedules

The English summary is being prepared. Showing the original description.

ObneyAI/orc22Updated Oct 8, 2026

Build read models by reducing events into queryable state

The English summary is being prepared. Showing the original description.

ObneyAI/orc22Updated Oct 8, 2026

See all skills by ObneyAI

Report a problem with this skill