Skip to main content

2 posts tagged with "api"

View All Tags

Generated APIs for domain-driven development

· 2 min read
TeaQL Code Gen
Core Contributor

Domain-driven development breaks down when the domain language disappears from the code path.

That happens easily in data-heavy systems. The model may be discussed in design sessions, but implementation code becomes SQL strings, mapper files, repository methods, DTOs, and service glue.

TeaQL uses generated APIs to keep the domain model visible.

Model First

TeaQL starts from a domain model:

  • entities;
  • scalar fields;
  • object relations;
  • child lists;
  • constants;
  • validation expectations;
  • data-service targets.

The generator turns that model into APIs. Application code then works with the generated vocabulary instead of reconstructing it by hand.

Queries as Domain Language

Generated query methods make intent explicit:

Q.orders()
.filterByMerchant(ctx.getMerchant())
.selectCustomer(Q.customers().selectName())
.selectLineItemList(Q.lineItems().selectSku().selectQuantity())
.countLineItems()
.executeForList(ctx);

The code names the domain structure:

  • orders;
  • merchant;
  • customer;
  • line items;
  • SKU;
  • quantity.

That is more reviewable than a service method that hides several mapper calls and response transformations.

Relations Matter

DDD systems are rarely flat. Relations carry meaning:

  • an order has line items;
  • a merchant belongs to a platform;
  • an employee belongs to a department;
  • a ticket has comments and assignments.

TeaQL relation loading APIs give those relationships a generated shape. The runtime can still decide how to execute the load efficiently.

Graph Writes

Domain changes often arrive as object graphs:

  • create an order with lines;
  • update a parent and merge children;
  • remove a child row;
  • attach an existing reference;
  • keep missing children for a relation that should not delete absent rows.

TeaQL Rust models this through graph nodes, graph operation state, mutation planning, and transaction boundaries. Java TeaQL carries the same high-level idea through entity save and graph-oriented runtime behavior.

Validation and Runtime Policy

Domain logic is not only data shape. It also includes:

  • required fields;
  • status transitions;
  • tenant rules;
  • permission policy;
  • audit metadata;
  • language-aware validation messages.

TeaQL keeps those concerns near runtime context and generated model hooks instead of scattering them across controllers.

Why Generate?

Handwritten domain APIs are possible. The problem is consistency.

Generation gives every entity the same baseline:

  • field selectors;
  • filters;
  • relation selectors;
  • aggregate helpers;
  • request builders;
  • checker hooks;
  • behavior hooks;
  • runtime registration.

Teams can then focus on the parts of the domain that are actually special.

The Short Version

Generated APIs are not a shortcut around domain modeling.

They are a way to make the domain model executable, reviewable, and reusable across application code, runtime providers, and AI coding tools.

Why AI-generated software needs deterministic business APIs

· 2 min read
TeaQL Code Gen
Core Contributor

AI coding tools can produce useful application code quickly. The problem is not speed. The problem is boundary control.

If an AI tool has to infer persistence behavior from scattered SQL, repositories, mapper XML, DTOs, and service conventions, it will eventually guess wrong.

TeaQL exists to make that boundary deterministic.

The Guessing Problem

When AI writes data-access code directly, it must infer:

  • table names;
  • column names;
  • relation cardinality;
  • tenant filters;
  • permission rules;
  • deleted-row semantics;
  • transaction boundaries;
  • pagination rules;
  • response shape;
  • database dialect differences.

Some of these guesses can pass a simple test and still be wrong in production.

The Business API Boundary

TeaQL generates APIs from the domain model. That means AI code can compose with named business methods instead of inventing storage behavior.

let orders = Q::orders()
.select_customer_with(Q::customers().select_name())
.select_line_item_list_with(Q::line_items().select_sku())
.which_statuses_are("PAID")
.page(1, 20)
.execute_for_list(&ctx)
.await?;

The model controls what fields and relations exist. The runtime controls how the query executes. The AI composes within those boundaries.

Deterministic Does Not Mean Rigid

A deterministic API can still be expressive:

  • filters can be combined;
  • relation loads can be nested;
  • aggregates can be grouped;
  • graph writes can save parent and child objects together;
  • provider-specific behavior can be selected below the API.

The point is that the API surface is stable and reviewable.

Better Prompts

TeaQL also makes prompts smaller.

Instead of giving an AI tool the entire schema, SQL examples, repository conventions, and DTO rules, a team can provide a generated API guide:

Use TeaQL Q APIs for reads.
Use generated relation selectors.
Execute through UserContext.
Do not write raw SQL unless explicitly requested.
Use graph save for parent-child persistence.

That is a stronger contract than a long explanation of database structure.

Runtime Safety

The generated API is only half the story. Runtime boundaries matter too.

TeaQL keeps execution behind context and provider layers:

  • Java uses UserContext as the runtime boundary.
  • Rust uses teaql_runtime::UserContext, repository registries, behavior hooks, and provider registration.
  • Database providers execute against PostgreSQL, MySQL, SQLite, rusqlite, or memory.

AI-generated application code should not own those decisions.

The Short Version

AI tools are fast at composition. They are unreliable at reconstructing business rules from infrastructure code.

TeaQL gives them deterministic business APIs to compose.