The Plumbing Problem
Every microservice is mostly plumbing: HTTP server, connection pools, marshalling, retries, health checks, wiring. You rewrite the same dozen things with different nouns. Mycel is a declarative runtime that lets you declare that plumbing instead of writing it.
The Prototyping Trap
The author's previous post got labeled "prototyping toy" because "zero code" triggers a mental drawer: no-code/low-code tools like Bubble or Zapier. But Mycel is not a visual builder. It's text config in version control, diffable like Terraform or nginx.conf. It's not a code generator—no emitted Go sitting in your repo. The runtime reads config and does the thing.
File #4: Production Concerns as Config
The real test is what happens when you add validation, retries, health checks, auth, migrations. Here's how they stay declarative:
Validation beyond types:
type "signup" {
email = string({ format = "email" })
age = number({ min = 18, max = 120 })
password = string({ min_length = 12, pattern = "..." })
plan = string({ enum = ["free", "pro"] })
tax_id = string({ validator = "valid_vat" }) # custom CEL/WASM rule
}
Retry with real backoff:
error_handling {
retry {
attempts = 5
delay = "1s"
max_delay = "30s"
backoff = "exponential" # or linear / constant
}
}
Plus circuit breaker and per-error-class dispositions (ack/retry/requeue/reject).
Health that actually probes dependencies: /health/ready pings every connector (DB PingContext, Redis PING) and reports per-component status. mycel check runs the same probes before accepting traffic.
Env across services: env() function, per-environment overlays (environments/prod.mycel), .env support, environment-aware defaults (debug logs + hot reload in dev; JSON logs + locked-down errors in prod), and connector profiles to swap backends per environment.
Multi-statement DB transactions: Clear previous rows, insert a parent, capture autoincrement id, loop over children referencing it—all atomic in one DB transaction, declared in config.
The Honest Tradeoff
Complexity doesn't evaporate. Auth is inherently complex, so an auth config is more involved than a three-line flow. But the tradeoff is not "clean demo → hidden code." It's "clean demo → more config." You trade writing how for learning the vocabulary of what. Your nginx.conf grows as you add TLS, caching, rate limiting—but never turns into C. Same shape here.
The Escape Hatch
When something doesn't fit declaratively, write a WASM plugin in Rust or Go, compile it, and the runtime calls it. The declarative layer handles the 90% plumbing; the escape hatch covers the 10% that's truly yours.
What the Drawer Gets Wrong
"Good for prototypes, bad for production" treats maturity as the axis. The real axis is plumbing vs. logic that's genuinely yours. Mycel removes the plumbing—same tool, same job, whether prototyping or on Kubernetes serving real traffic. The three-file demo was the smallest honest look; file #4 is where it gets interesting.
Try It
- Repo: https://github.com/matutetandil/mycel
- Auth example in config: persistence + JWT + MFA + brute-force + sessions + audit
- Docker: ghcr.io/matutetandil/mycel
The author's next post will cover what happens to a service like this when the power goes out.


