Dynamic Fields: Runtime-Extensible Entity Properties in TeaQL 1.523
Starting with TeaQL 1.523-RELEASE, we are shipping a long-awaited capability: Dynamic Fields — a structured, type-safe, auditable way to attach custom properties to any entity at runtime, without touching the core domain model.
The Problem
In real-world enterprise systems, the core domain model can never anticipate every field a customer will need. Consider these scenarios:
- SaaS multi-tenancy: Customer A needs a "customer asset number" field on
Platform; Customer B doesn't. - Plugin systems: A plugin wants to tag
Orderentities with a priority score. - Field incubation: A new field isn't mature enough to promote into the standard model, but the business needs it now.
The traditional approaches — adding nullable columns to the schema, or dumping everything into a JSON blob — each have serious drawbacks. Nullable columns pollute the model; JSON blobs lose type safety and queryability.
The Solution: Controlled Dynamic Fields
TeaQL Dynamic Fields sits between these extremes. Every dynamic field has:
- A registered definition — field code, display name, data type (
STRING,NUMBER,BOOL), and lifecycle status. - Scoping — fields can be global or scoped to a tenant/environment.
- Type enforcement — you declare the type at registration; reads and writes are type-checked.
- Audit integration — every write carries
purposeandcomment, flowing into the standard TeaQL audit trail.
This is not raw key-value storage. It's a controlled extension mechanism with the same rigor as your compiled domain model.
Architecture
The feature ships as two new modules plus bridge points in teaql-core:
teaql-dynamic-fields-api ← API contracts & in-memory provider
├── DynamicFieldsFacade ← Fluent read/write API
├── DynamicFieldsProvider ← SPI for storage backends
├── InMemoryDynamicFieldsProvider ← Demo/test implementation
└── 20 value objects ← FieldDef, FieldValue, Selection, etc.
teaql-dynamic-fields-jdbc ← JDBC persistent provider
├── JdbcDynamicFieldsProvider ← Works with any JDBC DataSource
└── DynamicFieldsSchema ← Auto-creates 2 tables via ensureSchema()
teaql-core ← Bridge points
├── UserContext.dynamicFields() ← Access via context
├── Entity.dynamicFields() ← Read values on any entity
└── SearchRequest.selectDynamicFieldsWith(...)
The API module has zero external dependencies — it depends only on teaql-core. The JDBC module reuses your existing DataSource and auto-creates its internal tables (teaql_dynamic_field_def and teaql_dynamic_field_value) on first use.
Quick Start
1. Add the Dependency
<dependency>
<groupId>io.teaql</groupId>
<artifactId>teaql-dynamic-fields-jdbc</artifactId>
<version>1.523-RELEASE</version>
</dependency>
2. Initialize the Provider
// Reuse your existing DataSource (MySQL, PostgreSQL, SQLite, etc.)
JdbcDynamicFieldsProvider provider = new JdbcDynamicFieldsProvider(dataSource);
provider.ensureSchema(); // auto-creates tables if not exist
3. Register a Field Definition
DynamicFieldDef def = new DynamicFieldDef();
def.setScope(DynamicFieldScope.global());
def.setOwnerType("School");
def.setCode("short_name");
def.setName("School Abbreviation");
def.setDataType(DynamicDataType.STRING);
def.setStatus(DynamicFieldStatus.ACTIVE);
provider.registerFieldDef(ctx, def);
4. Read and Write via the Fluent Facade
DefaultDynamicFieldsFacade df = new DefaultDynamicFieldsFacade(provider);
// Write
df.withContext(ctx)
.purpose("Initialize school data")
.comment("Set abbreviation")
.owner("School", 1001L)
.string("short_name").set("MIT");
// Read
String name = df.withContext(ctx)
.owner("School", 1001L)
.string("short_name").get();
// → "MIT"
5. Bulk Read
DynamicFieldValues values = provider.loadValues(ctx, owner,
new DynamicFieldSelection()
.selectString("short_name")
.selectNumber("student_capacity")
.selectBool("is_key_school"));
String name = values.getString("short_name");
int capacity = values.getNumber("student_capacity").intValue();
boolean isKey = values.getBool("is_key_school");
Supported Data Types
| Type | Java Type | Use Case |
|---|---|---|
STRING | String | Labels, codes, identifiers |
NUMBER | Number (Long/Double) | Scores, quantities, amounts |
BOOL | Boolean | Feature flags, toggles |
More types (dates, enums, references) are planned for future releases.
Storage Backend Options
| Provider | Persistence | Use Case |
|---|---|---|
InMemoryDynamicFieldsProvider | None (JVM lifetime) | Unit tests, demos |
JdbcDynamicFieldsProvider | Any JDBC DataSource | Production use |
The JDBC provider has been tested with SQLite, PostgreSQL, MySQL, and H2. It uses standard SQL and should work with any JDBC-compliant database.
Design Principles
- No schema migration required. Dynamic fields live in their own tables. Your core entity tables are untouched.
- Type-safe, not a JSON dump. Every field has a declared type. The API enforces it.
- Audit-first. Every mutation carries
purposeandcomment, integrating with TeaQL's holographic trace logging. - Provider-pluggable. Implement
DynamicFieldsProviderto back dynamic fields with Redis, DynamoDB, or any other store. - Zero coupling to core entities. Dynamic fields are keyed by
(ownerType, ownerId). No changes to your generated entity classes.
What's Next
- Query integration: Filter and sort by dynamic field values in
SearchRequest. - Code generation support: Auto-generate dynamic field read/write helpers from model annotations.
- Rust parity: Bring dynamic fields to
teaql-rsfor the Rust runtime. - Rich types: Date, Decimal, and entity-reference field types.
Upgrade Guide
To upgrade to 1.523-RELEASE:
<properties>
<teaql.version>1.523-RELEASE</teaql.version>
</properties>
The dynamic fields modules are fully optional — adding them has zero impact on your existing domain model and data service layer.
Dynamic Fields fills a critical gap in model-driven development: the ability to extend the domain at runtime with the same discipline and safety as compile-time modeling. We believe this strikes the right balance between flexibility and control.
Try it out and let us know what you think on GitHub!
