:service — The Workshop
:service — The Workshop
Section titled “:service — The Workshop”“Where ideas become applications.”
:service is where Janus becomes a proper tool for building real software. You get everything in :core — plus error handling that doesn’t lie to you, async operations that don’t block your brain, and channels that make concurrency actually comprehensible.
What :service Gives You
Section titled “What :service Gives You”Error-as-Values
Section titled “Error-as-Values”No exceptions. No try/catch ambiguity. Errors are values, handled like any other data.
func fetch_url(url: String) !Response do let response := try http.get(url) if response.status != 200 do fail Error.http_error(response.status) end return responseend!T— Function returns eitherTor an errortry— Propagate error upwardcatch— Handle specific errors?— Short-circuit on error
Structured Concurrency
Section titled “Structured Concurrency”async func main() do let result := try await fetch_user(user_id) print(result.name)endasync— Marks a function as asynchronousawait— Pauses until operation completes- No callback hell — async code reads like sync code
CSP Channels
Section titled “CSP Channels”func worker(input: Channel[String], output: Channel[Result]) do for msg in input do output.send(process(msg)) endend- Typed —
Channel[String]vs. untyped queues - Buffered — Optional capacity for backpressure
- Select — Wait on multiple channels simultaneously
Additional Features
Section titled “Additional Features”usingstatement for resource cleanupdeferfor guaranteed teardown- Generics with trait constraints
- First-class error types with rich context
What :service Excludes
Section titled “What :service Excludes”| Excluded | Available In |
|---|---|
| Actors and grains | :cluster |
| Supervision trees | :cluster |
| Tensors and GPU | :compute |
Raw pointers and unsafe | :sovereign |
When to Use :service
Section titled “When to Use :service”Perfect for:
- Web services and REST APIs
- Background job processors
- Database clients and ORMs
- Microservices
- Business logic layer
- Any application that needs to handle errors gracefully
- Services that communicate over the network
The rule: If it needs to run for more than a few seconds and handle errors, :service is your starting point.
Code Examples
Section titled “Code Examples”A Simple HTTP Server
Section titled “A Simple HTTP Server”async func handle_request(req: Request) !Response do let path := req.path
match path do | "/" => return Response.html(home_page()) | "/api/users" => let users := try await db.get_all_users() return Response.json(users) | _ => return Response.status(404) endend
async func main() do let server := Server.new(8080) try await server.serve(handle_request)endConcurrent Data Fetching
Section titled “Concurrent Data Fetching”async func fetch_all(user_ids: [UserId]) ![User] do let nursery := Nursery.new()
for id in user_ids do nursery.spawn(async do return try await db.get_user(id) end) end
return try nursery.join_all()endChannel-Based Processing
Section titled “Channel-Based Processing”func main() do let jobs := Channel[Job].new(100) let results := Channel[Result].new(100)
# Start workers for i in 0..4 do spawn worker(jobs, results) end
# Send work for job in load_jobs() do jobs.send(job) end jobs.close()
# Collect results for _ in 0..job_count do let result := results.recv() print("Job ${result.id}: ${result.status}") endendError Handling Patterns
Section titled “Error Handling Patterns”func process_config(path: String) !Config do let content := try read_file(path) | catch FileError.not_found => fail ConfigError.missing(path) | catch FileError.permission_denied => fail ConfigError.unreadable(path)
let parsed := try TOML.parse(content) | catch TOMLError => fail ConfigError.invalid(path)
return try Config.from_toml(parsed)endWhy :service Wins
Section titled “Why :service Wins”vs. Node.js:
- Types throughout — no
undefined is not a function - Error handling that’s explicit — no uncaught exceptions
- Structured concurrency — no callback pyramid
vs. Go:
- Generics — no code duplication for different types
- Error handling as values — no
if err != nilspam - Type safety — Go’s
interface{}has no place here
vs. Python (FastAPI/Django):
- Compile-time type checking — catch bugs before deployment
- Zero-cost async — no event loop overhead
- Single language end-to-end — same Janus for frontend, backend, ops
Next Steps
Section titled “Next Steps”- Move to :cluster — When you need resilience
- Move to :script — For quick prototyping
- Concurrency Tutorial — Deep dive
- Error Handling Tutorial — Master the
!Tpattern
Build applications that matter.