Data & Enterprise
Application Layer
Zornux ships the building blocks of a layered backend as language constructs — DTOs with validation, services and repositories as injectable components, and a native dependency-injection container. No frameworks, no attributes, no wiring code.
Records (DTOs) & validation
A record declares the shape of data crossing a boundary —
what a client may send — with declarative validation rules
right on each field:
record SignupRequest
has username
required
minimum length 3
maximum length 20
has email
required
email
has age
minimum 18
end
Check an instance with validate. The result exposes
is_valid and a list of errors, one per broken
field:
create bad from SignupRequest
bad.username = "a"
bad.email = "nope"
bad.age = 15
create result = validate bad
show "valid: " + text(result.is_valid)
for each problem in result.errors
show problem.field + " " + problem.message
end
| Rule | Checks |
|---|---|
required | The field must be present and non-empty. |
minimum length N / maximum length N | Text length bounds. |
minimum N / maximum N / range A to B | Numeric bounds. |
email / url | Well-formed email / URL. |
matches "pattern" | Matches a pattern. |
validate x (record validation) and validate(x) (the security built-in that tests trust) are told apart by the syntax — validate is not a reserved word.
Services & repositories
A repository holds data access; a
service holds business rules and depends on
repositories. Methods are functions (often async function), called as
Component.method(…):
repository UserRepository
async function save with user
save user into AppDb.Users
give back user
end
end
service UserService
use UserRepository
async function create_user with request
create user from User
user.name = request.name
user.email = request.email
give back wait for UserRepository.save(user)
end
end
A component method may be named with a reserved word — e.g. async function save — because it's only ever called as UserRepository.save(…), never as a bare statement.
Dependency injection
A component declares what it needs with use. The
application … end block is the composition root
— it registers every component, and the container resolves the graph:
application
use UserRepository
use UserService
use UserApi
end
Registrations may declare a lifetime after as —
singleton (the default), scoped, or
transient. A missing or cyclic dependency is caught up front
(ZX2503).
Web integration — it all connects
A controller route can take a DTO directly: on POST "/" with
CreateUserRequest request deserializes the JSON
body, validates it against the record's rules
(answering 400 on failure), and injects the services the
controller declared — automatically.
controller Users at "/users"
use UserService
on POST "/" with CreateUserRequest request
create created = wait for UserService.create_user(request)
give back created message "Created " + created.name
end
end
web UserApp
use Users
end
publish UserApp on port 8080
That's the full path — HTTP request → validation → service → repository → ORM → database — with security threaded through, and no glue code.
Diagnostics are ZX2500–ZX2599. Next: settings and secrets — Configuration.