Skip to main content

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 Q and E APIs 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.