Creating Cells

Contents

  1. Creating Cells
  2. Cell Registration
  3. Cell Spec Fields
  4. Handler Signature
  5. Output Schema Formats
  6. Rules
  7. Testing Cells in Isolation
  8. Common Patterns
  9. Resource-dependent cell
  10. Async cell (external API call)

Creating Cells

Cells are the atomic units of Mycelium, analogous to microservices. Each cell represents a single step in a workflow and can perform IO and side effects. Cells have explicit schema contracts, and the state a cell returns is used by the workflow engine to decide which cell to run next.

Cell Registration

Register cells via defmethod on the cell/cell-spec multimethod:

(ns my-app.cells.auth
  (:require [mycelium.cell :as cell]))

(defmethod cell/cell-spec :auth/parse-request [_]
  {:id       :auth/parse-request
   :handler  (fn [_resources data]
               (let [body (get-in data [:http-request :body])]
                 (if-let [token (get body "auth-token")]
                   (assoc data :auth-token token)
                   (assoc data :error-type :missing-token
                               :error-message "No auth token provided"))))
   :schema   {:input  [:map [:http-request [:map [:body map?]]]]
              :output {:success [:map [:auth-token :string]]
                       :failure [:map [:error-type :keyword]
                                      [:error-message :string]]}}
   :requires []})

Cell Spec Fields

FieldRequiredDescription
:idyesKeyword identifier, conventionally namespace/name (e.g. :auth/parse-request)
:handleryes(fn [resources data] -> data) for sync, or (fn [resources data callback error-callback]) for async
:schemayesMap with :input (Malli schema) and :output (single schema or per-transition map)
:requiresnoVector of resource keys the handler needs (e.g. [:db])
:async?noSet to true for async handlers
:docnoDocumentation string

Handler Signature

Sync (default):

(fn [resources data] -> data)

  • resources: map of injected dependencies (db, http-client, etc.)
  • data: accumulating data map — all keys from prior cells are present
  • Returns: the data map with new keys assoc'd

Async (:async? true):

(fn [resources data callback error-callback])

  • callback: call with (callback updated-data) on success
  • error-callback: call with (error-callback exception) on failure

Output Schema Formats

Single schema — all transitions must satisfy it:

:output [:map [:result :int]]

Per-transition schemas — each dispatch transition has its own contract:

:output {:found     [:map [:profile [:map [:name :string] [:email :string]]]]
         :not-found [:map [:error-type :keyword] [:error-message :string]]}

The transition keys in the output map must match the dispatch labels defined in the workflow's :dispatches for this cell.

Rules

  1. Cells must return the data map with computed values added (use assoc, not select-keys)
  2. Output must satisfy the declared :output schema for the matched transition
  3. Never import or call other cells — cells are isolated by design
  4. Use only resources passed via the first argument — never acquire external dependencies directly
  5. The :id used in defmethod must match the :id in the spec map

Testing Cells in Isolation

(require '[mycelium.dev :as dev])

;; Basic test
(dev/test-cell :auth/parse-request
  {:input     {:http-request {:body {"auth-token" "tok_abc"}}}
   :resources {}})
;; => {:pass? true, :output {...}, :errors [], :duration-ms 0.42}

;; With dispatch verification
(dev/test-cell :auth/parse-request
  {:input      {:http-request {:body {"auth-token" "tok_abc"}}}
   :resources  {}
   :dispatches [[:success (fn [d] (:auth-token d))]
                [:failure (fn [d] (:error-type d))]]
   :expected-dispatch :success})
;; => {:pass? true, :matched-dispatch :success, ...}

Common Patterns

Resource-dependent cell

(defmethod cell/cell-spec :user/fetch-profile [_]
  {:id       :user/fetch-profile
   :handler  (fn [{:keys [db]} data]
               (if-let [profile (db/get-user db (:user-id data))]
                 (assoc data :profile profile)
                 (assoc data :error-type :not-found
                             :error-message "User not found")))
   :schema   {:input  [:map [:user-id :string]]
              :output {:found     [:map [:profile [:map [:name :string] [:email :string]]]]
                       :not-found [:map [:error-type :keyword] [:error-message :string]]}}
   :requires [:db]})

Async cell (external API call)

(defmethod cell/cell-spec :api/fetch-data [_]
  {:id       :api/fetch-data
   :handler  (fn [_resources data callback error-callback]
               (future
                 (try
                   (let [resp (http/get (:url data))]
                     (callback (assoc data :response resp)))
                   (catch Exception e
                     (error-callback e)))))
   :schema   {:input  [:map [:url :string]]
              :output [:map [:response map?]]}
   :async?   true})