Skip to main content

TeaQL Best Practices in Spring Boot / Spring Cloud

This document summarizes recommended usage patterns and best practices for TeaQL in Spring Boot / Spring Cloud projects. It is intended to help teams build clean layering, strong testability, and long-term maintainability.


In Spring Boot / Spring Cloud projects, the recommended invocation chain is:

Controller -> Service -> Util

Design Goals

  • Controller: protocol adaptation only (HTTP / RPC)
  • Service: transaction boundaries and business orchestration
  • Util: reusable, testable, pure business logic

2. Unified Context: CustomUserContext

CustomUserContext is a project-specific context class.

Why it matters

  • Centralizes user, permission, tenant, and request metadata
  • Enables shared common methods across layers
  • Prevents excessive parameter passing

3. Controller Layer Practices

Example

@GetMapping("/info")
public Object info(@TQLContext CustomUserContext userContext) {
return service.info(userContext);
}

Best Practices

  • The first parameter in Controller methods should be annotated with @TQLContext
  • Keep Controllers minimal:
    • No business logic
    • No complex condition handling
    • No transaction management
  • Controllers should only:
    • Accept request parameters
    • Delegate to Service
    • Return results

4. Service Layer Practices

Example

public Object info(@TQLContext CustomUserContext userContext, OtherParam param) {
return InfoUtil.info(userContext, param);
}

Best Practices

  • Service layer responsibilities:
    • Define transaction boundaries (e.g. @Transactional)
    • Orchestrate business flows
  • Inside Service methods:
    • Keep logic concise
    • Avoid complex TeaQL expressions
    • Delegate heavy logic to Util layer

5. Util Layer Practices

Example

public class InfoUtil {

public static Object info(@TQLContext CustomUserContext userContext, OtherParam param) {
return Q.orders()
.withUserId(userContext.getUserId())
.executeForList(userContext);
}
}

Best Practices

  • Prefer static methods
  • Decouple from Spring Beans and lifecycle
  • Key benefits:
    • Easier unit testing
    • Better reusability
    • Clearer composition across Services

6. General TeaQL Best Practices

6.1 Ensure Schema at Setup or Admin Boundary

Java projects can ensure the physical database schema from generated TeaQL metadata through SQLRepositorySchemaHelper.

@Controller
public class EnsureModelController {

@Autowired
private EntityMetaFactory factory;

@GetMapping("/ensureSchema")
@ResponseBody
public Object ensureSchema(@TQLContext UserContext context) {
new SQLRepositorySchemaHelper().ensureSchema(context, factory);
return Map.of("ok", true);
}
}

Common deployment configuration:

teaql.ensureSchema=true

Best practices:

  • Run schema ensure during setup, deployment, startup, or controlled admin operations.
  • Keep schema ensure out of domain objects and business methods.
  • Use normal generated Q queries and save(userContext) only after the schema is ready.
  • Keep environment-specific database credentials and provider configuration in application configuration, not in model or domain behavior code.

6.2 Minimize RawSQL Usage

  • Avoid using RawSQL whenever possible
  • Prefer:
    • Semantic TeaQL expressions
    • Composable query fragments

6.3 Avoid DTO Explosion

  • Do not create large numbers of scenario-specific DTOs
  • Recommended approach:
    • Extend generated Entity Classes
    • Add business logic in subclasses

6.4 Entity Classes as DDD Aggregate Roots

  • Extend Entity Classes
  • Add lightweight business behaviors directly to entities
  • Use them as Aggregate Roots in DDD design
  • Keep domain methods pure enough to unit test without Spring, database, or UserContext
  • Do not hide persistence calls such as save(userContext) inside domain methods

6.5 Keep Domain Behavior Lightweight

TeaQL supports DDD-style return types through returnType(Class). This lets a query return a subclass with additional domain behavior:

class OperableMerchant extends Merchant {

OperableMerchant openStore() {
updateStatus(MerchantStatus.ACTIVE);
return this;
}
}

var merchant = Q.merchants()
.returnType(OperableMerchant.class)
.filterByName("TeaQL Store")
.executeForOne(userContext)
.orElseThrow();

merchant.openStore();
merchant.save(userContext);

The important boundary is intentional:

  • Domain methods express business state changes and validations.
  • Service or application methods own transactions, orchestration, and persistence.
  • This keeps aggregate behavior easy to unit test.
  • This prevents domain objects from depending on runtime infrastructure.

Avoid this pattern:

class OperableMerchant extends Merchant {

void openStore(UserContext userContext) {
updateStatus(MerchantStatus.ACTIVE);
save(userContext);
}
}

The second version couples domain behavior to persistence and makes tests more expensive. Keep the object light and let the service decide when to save.


6.6 Dynamic Properties

TeaQL supports dynamic properties:

bean.setDynamicProperty("name", value);
  • After JSON serialization, the field appears as:
    • _name
  • Suitable for:
    • Temporary frontend fields
    • Non-persistent computed values

6.7 Query Fragment Reuse

TeaQL allows reusable query fragments via static methods:

public static OrderRequest shippedOrders() {
return Q.orders().withStatusAreShipped();
}

Further composition:

shippedOrders().filterByUserId(userId);

Benefits:

  • Unified business semantics
  • Reduced duplication
  • Improved readability

6.8 Readability in Complex Scenarios

  • TeaQL supports deeply nested queries
  • For complex business scenarios:
    • Use semantic Request wrappers
    • Let method names express business intent

This significantly improves:

  • Code readability
  • Maintainability in complex domains

7. Summary

By following these practices:

  • TeaQL queries remain clear and centralized
  • Spring Boot / Cloud layering stays stable
  • Code becomes easier to test and evolve

Recommended for:

  • Medium to large Spring Cloud projects
  • Systems where TeaQL is the core data access and business expression layer