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.
1. Recommended Call Flow
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
CustomUserContextis 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
- Define transaction boundaries (e.g.
- 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
Qqueries andsave(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