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
| Field | Required | Description |
|---|---|---|
:id | yes | Keyword identifier, conventionally namespace/name (e.g. :auth/parse-request) |
:handler | yes | (fn [resources data] -> data) for sync, or (fn [resources data callback error-callback]) for async |
:schema | yes | Map with :input (Malli schema) and :output (single schema or per-transition map) |
:requires | no | Vector of resource keys the handler needs (e.g. [:db]) |
:async? | no | Set to true for async handlers |
:doc | no | Documentation 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 successerror-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
- Cells must return the data map with computed values added (use
assoc, notselect-keys) - Output must satisfy the declared
:outputschema for the matched transition - Never import or call other cells — cells are isolated by design
- Use only resources passed via the first argument — never acquire external dependencies directly
- The
:idused indefmethodmust match the:idin 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})
Mycelium