본문으로 건너뛰기
cccskills
무료GitHub에 공개

grain-service

Create a complete Grain domain service with schemas, read models, commands, queries, and tests

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md7.3 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Create a Grain Domain Service (ORC Library)

This skill walks through creating a domain service component in the ORC library. No UI, no web layer — services expose data through commands, queries, and read models.

Naming conventions:

  • Service name: widgets (kebab-case for ns, used as event/command prefix)
  • Entity name: widget (singular)
  • Component dir: components/widget-service/

Step 1: Create Component Structure

components/widget-service/
  deps.edn
  src/ai/obney/orc/widget_service/
    interface.clj
    interface/schemas.clj
    core/
      read_models.clj
      commands.clj
      queries.clj
      todo_processors.clj
  test/ai/obney/orc/widget_service/
    test_helpers.clj
    commands_test.clj

deps.edn:

{:paths ["src" "resources"]
 :deps {}
 :aliases {:test {:extra-paths ["test"]
                  :extra-deps {}}}}

Step 2: Define Schemas (interface/schemas.clj)

(ns ai.obney.orc.widget-service.interface.schemas
  (:require [ai.obney.grain.schema-util.interface :refer [defschemas]]))

(defschemas events
  {:widgets/widget-created [:map [:widget-id :uuid] [:name :string] [:description [:maybe :string]]]
   :widgets/widget-updated [:map [:widget-id :uuid] [:name :string] [:description [:maybe :string]]]
   :widgets/widget-deleted [:map [:widget-id :uuid]]})

(defschemas commands
  {:widgets/create-widget [:map
                           [:name [:string {:min 1 :error/message "Name is required"}]]
                           [:description {:optional true} [:maybe :string]]]
   :widgets/update-widget [:map
                           [:widget-id :uuid]
                           [:name [:string {:min 1 :error/message "Name is required"}]]
                           [:description {:optional true} [:maybe :string]]]
   :widgets/delete-widget [:map
                           [:widget-id :uuid]]})

(defschemas queries
  {:widgets/get-all [:map]
   :widgets/get-by-id [:map [:widget-id :uuid]]})

Step 3: Build Read Model (core/read_models.clj)

(ns ai.obney.orc.widget-service.core.read-models
  (:require [ai.obney.grain.read-model-processor-v2.interface :as rmp :refer [defreadmodel]]))

(def widget-event-types
  #{:widgets/widget-created
    :widgets/widget-updated
    :widgets/widget-deleted})

(defmulti widgets* (fn [_state event] (:event/type event)))

(defmethod widgets* :widgets/widget-created
  [state {:keys [widget-id name description]}]
  (assoc state widget-id {:widget-id widget-id :name name :description description :active true}))

(defmethod widgets* :widgets/widget-updated
  [state {:keys [widget-id name description]}]
  (update state widget-id merge {:name name :description description}))

(defmethod widgets* :widgets/widget-deleted
  [state {:keys [widget-id]}]
  (dissoc state widget-id))

(defmethod widgets* :default [state _] state)

(defreadmodel :widgets widgets
  {:events widget-event-types :version 1}
  [state event]
  (widgets* state event))

;; Query helpers
(defn get-widget [ctx widget-id]
  (get (rmp/project ctx :widgets/widgets {:tags #{[:widget widget-id]}})
       widget-id))

(defn get-all-widgets [ctx]
  (->> (vals (rmp/project ctx :widgets/widgets))
       (filter :active)
       (sort-by :name)))

Step 4: Implement Commands (core/commands.clj)

(ns ai.obney.orc.widget-service.core.commands
  (:require [ai.obney.orc.widget-service.core.read-models :as rm]
            [ai.obney.grain.event-store-v3.interface :refer [->event]]
            [ai.obney.grain.command-processor-v2.interface :refer [defcommand]]
            [cognitect.anomalies :as anom]))

(defcommand :widgets create-widget
  "Create a new widget."
  [{{:keys [name description]} :command :as ctx}]
  (let [widget-id (random-uuid)]
    {:command-result/events
     [(->event {:type :widgets/widget-created
                :tags #{[:widget widget-id]}
                :body {:widget-id widget-id
                       :name name
                       :description description}})]}))

(defcommand :widgets delete-widget
  "Delete a widget."
  [{{:keys [widget-id]} :command :as ctx}]
  (if-let [widget (rm/get-widget ctx widget-id)]
    {:command-result/events
     [(->event {:type :widgets/widget-deleted
                :tags #{[:widget widget-id]}
                :body {:widget-id widget-id}})]}
    {::anom/category ::anom/not-found
     ::anom/message "Widget not found."}))

Step 5: Implement Queries (core/queries.clj)

Queries return data — no UI rendering in a library.

(ns ai.obney.orc.widget-service.core.queries
  (:require [ai.obney.orc.widget-service.core.read-models :as rm]
            [ai.obney.grain.query-processor.interface :refer [defquery]]))

(defquery :widgets get-all
  "Get all active widgets."
  [ctx]
  {:query/result {:widgets (rm/get-all-widgets ctx)}})

(defquery :widgets get-by-id
  "Get a widget by ID."
  [{{:keys [widget-id]} :query :as ctx}]
  (let [widget (rm/get-widget ctx (parse-uuid widget-id))]
    (if widget
      {:query/result {:widget widget}}
      {:query/result {:widget nil}})))

Step 6: Create Interface (interface.clj)

(ns ai.obney.orc.widget-service.interface
  (:require ;; Side-effect requires — registers commands/queries/processors
            [ai.obney.orc.widget-service.core.commands]
            [ai.obney.orc.widget-service.core.queries]
            [ai.obney.orc.widget-service.core.todo-processors]
            ;; Re-export read model helpers
            [ai.obney.orc.widget-service.core.read-models :as rm]))

(def get-widget rm/get-widget)
(def get-all-widgets rm/get-all-widgets)

Step 7: Todo Processors (core/todo_processors.clj)

(ns ai.obney.orc.widget-service.core.todo-processors
  (:require [ai.obney.grain.todo-processor-v2.interface :refer [defprocessor]]))

;; Add defprocessor forms here when event-driven side effects are needed

Step 8: Write Tests

(ns ai.obney.orc.widget-service.commands-test
  (:require [clojure.test :refer [deftest testing is]]
            [ai.obney.orc.grain-test-utils.interface :as tu]
            [ai.obney.orc.widget-service.core.commands]
            [ai.obney.orc.widget-service.interface.schemas]
            [cognitect.anomalies :as anom]))

(deftest create-widget-test
  (testing "creates a widget"
    (tu/with-test-context [ctx]
      (let [result (tu/process-command! ctx
                     {:command/name :widgets/create-widget
                      :name "Test Widget"
                      :description "A test"})]
        (is (tu/event-of-type? result :widgets/widget-created))
        (let [event (tu/find-event result :widgets/widget-created)]
          (is (= "Test Widget" (:name event))))))))

Step 9: Register in deps.edn

Root deps.edn

Add to :aliases > :dev > :extra-deps:

orc/widget-service {:local/root "components/widget-service"}

projects/orc/deps.edn

Add to :deps:

orc/widget-service {:local/root "../../components/widget-service"}

Reference Files

  • components/orc-service/ — Core ORC service (comprehensive example)
  • components/colbert/ — ColBERT service (commands, read models, queries)
  • components/ontology/ — Ontology service (event sourcing patterns)

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

allium

무료

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

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ObneyAI/orc222026년 10월 8일 업데이트

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월 8일 업데이트

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월 8일 업데이트

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

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ObneyAI/orc222026년 10월 8일 업데이트

Create scheduled background jobs that run on cron schedules

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ObneyAI/orc222026년 10월 8일 업데이트

Implement query handlers that read from event-sourced read models

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ObneyAI/orc222026년 10월 8일 업데이트

ObneyAI의 스킬 모두 보기

이 스킬의 문제 신고