TeaQL Rust Best Practices
TeaQL Rust keeps the Java TeaQL programming model, but makes runtime boundaries more explicit. Generated APIs should carry the business vocabulary, while application code owns orchestration, provider setup, and persistence timing.
1. Start With Runtime Assembly
Register generated metadata, repositories, behavior registries, and the selected database provider before running queries.
let mut ctx = teaql_runtime::UserContext::new()
.with_module(crm_erp_service::module())
.with_repository_registry(crm_erp_service::repository_registry())
.with_repository_behavior_registry(crm_erp_service::behavior_registry());
ctx.use_postgres_provider(PgMutationExecutor::new(pg_pool));
ctx.ensure_schema().await?;
ensure_schema() belongs at startup or test setup time. It should not be hidden
inside a domain method.
2. Install Platform Policy at Runtime Assembly
Use RequestPolicy when a rule protects the whole platform rather than one
entity. Tenant isolation, raw SQL restrictions, page-size caps, and sensitive
read audit should be installed on UserContext, close to provider registration.
let ctx = teaql_runtime::UserContext::new()
.with_module(crm_erp_service::module_with_behaviors_and_checkers())
.with_request_policy(CrmRequestPolicy);
Entity-specific defaults still belong in repository behavior. Platform policy gets the last runtime decision before the request reaches the repository.
3. Prefer Generated Q APIs
Use generated query methods before raw SQL or ad-hoc repository code.
let merchants = Q::merchants()
.with_names_contain("TeaQL")
.select_platform_with(Q::platforms().select_self())
.execute_for_list(&ctx)
.await?;
For repeated subqueries, wrap them in helper functions:
fn active_merchants() -> MerchantRequest {
Q::merchants()
.with_statuses_are_active()
.select_self()
}
4. Keep Domain Behavior Lightweight
TeaQL supports DDD-style typed return values. A query can return a project-defined behavior type, and application code can call the behavior immediately after loading.
struct OperableMerchant {
inner: Merchant,
}
impl OperableMerchant {
fn open_store(&mut self) -> &mut Self {
self.inner.update_status_id(MerchantStatus::active_id());
self
}
}
let mut merchant = Q::merchants()
.return_type::<OperableMerchant>()
.with_names_are("TeaQL Store")
.execute_for_one(&ctx)
.await?
.expect("merchant should exist");
merchant.open_store();
merchant.inner.save(&ctx).await?;
The domain behavior changes state. The application/service boundary decides when to persist.
Avoid this pattern:
impl OperableMerchant {
async fn open_store(&mut self, ctx: &impl TeaqlRuntime) -> Result<()> {
self.inner.update_status_id(MerchantStatus::active_id());
self.inner.save(ctx).await?;
Ok(())
}
}
That version couples business behavior to persistence and makes the behavior harder to unit test. Keep behavior methods small, deterministic, and free of database I/O when possible.
5. Use Graph Save at the Boundary
Generated entities expose graph save helpers such as:
merchant.save(&ctx).await?;
Use graph save from application services, command handlers, tests, or workflow boundaries. That keeps domain methods focused on invariants and state transitions.
6. Use E for Safe Value Access
Use generated E expressions for long object chains instead of nested
unwrap() calls.
let platform_name = E::merchant(merchant)
.get_platform()
.get_name()
.eval();
This keeps optional relation traversal readable and safer for AI-generated code.
7. Keep Provider Details Out of Business Logic
Provider selection belongs in runtime assembly:
- PostgreSQL SQLx.
- MySQL SQLx.
- SQLite SQLx.
- rusqlite.
- memory repositories for tests.
Business logic should accept a runtime context or repository boundary, not build database pools or choose providers itself.
Summary
- Let generated
QandEAPIs carry business vocabulary. - Install platform-level request policy on
UserContext. - Keep domain behavior light and testable.
- Keep persistence at the application/service boundary.
- Register providers and run
ensure_schema()during runtime setup. - Use graph save intentionally after state transitions are complete.