Studio Starter
Overview
Studio Starter provides a metadata design-time IDE that enables visual model design, code generation, version control, and multi-environment deployment. It manages the full lifecycle from design to production deployment for Softa metadata-driven applications.
Key capabilities:
- Model Designer: design Models, Fields, OptionSets, Views, Navigations, Validations, Onchange rules, Indexes, and generation mappings/templates
- Code Generator: generate template-driven code files from models via Pebble templates, preferring database-managed templates and field type mappings and falling back to classpath templates under
templates/code/ - DDL Generator: generate DDL (CREATE TABLE, ALTER TABLE, DROP TABLE, indexes) from model definitions and merged change sets via Pebble templates, preferring database-managed SQL templates and DB type mappings
- DDL Preview: preview DDL SQL at every stage — WorkItem, Version, and Deployment — for easy copy-paste to a DB client
- Version Control: WorkItem-based change tracking with ES changelog integration, version sealing/unsealing, and freezing
- Deployment: direct Version-to-Env deployment with automatic released-version merging by
sealedTime, DDL generation, and execution tracking; new environments merge all released versions up to the target - Multi-Environment Deployment: deploy to Dev/Test/UAT/Prod environments with local or remote upgrade
Template Engine
The Studio Starter uses Pebble (v4.1.1) as its template engine for both
Java code generation and SQL DDL generation. Pebble uses {{ var }} / {% if %} syntax, which is
consistent with the project-wide {{ }} placeholder convention.
Template Files
| Directory | Templates | Purpose |
|---|---|---|
templates/code/ | entity/{{modelName}}.java.peb, service/{{modelName}}Service.java.peb, service/impl/{{modelName}}ServiceImpl.java.peb, controller/{{modelName}}Controller.java.peb | Fallback code generation templates. When no DesignCodeTemplate is configured, all templates/code/**/*.peb files are scanned; the relative directory becomes the default output subdirectory and the last path segment before .peb becomes the rendered output file name |
templates/sql/mysql/ | CreateTable.peb, AlterTable.peb, DropTable.peb, AlterIndex.peb | Fallback MySQL DDL templates |
Code Template Rules
- Database mode:
DesignCodeTemplateentries are loaded bycodeLangand sorted bysequence, then rendered into aModelCodeDTO.fileslist. DesignCodeTemplate.subDirectoryis a Pebble template.null, blank, whitespace,.,./, and/are treated as the zip root directory. Leading./, leading/, trailing/, duplicated/, and\are normalized.DesignCodeTemplate.fileNameis a Pebble template and is used directly as the output file name. It must include the desired suffix itself, such as{{modelName}}Service.java; the current implementation does not auto-appendDesignCodeLang.fileExtension.DesignCodeTemplate.fileNamecannot be blank after rendering and cannot contain directory separators. Directory structure must be expressed only throughsubDirectory.- Fallback mode is enabled only when no code template language is configured in the database. In that case, every classpath template under
templates/code/**/*.pebis rendered. - In fallback mode, the rendered output file name comes from the fallback template path itself. For example,
templates/code/service/{{modelName}}Service.java.pebgeneratesservice/SysModelService.java. - Generated single-file downloads use the rendered file name, while zip downloads use the rendered relative path.
downloadAllZipprefixes each language package with<codeLang>/.
Core Classes
| Class | Description |
|---|---|
TemplateEngine | Pebble engine wrapper — singleton PebbleEngine with caching, no auto-escaping |
CodeGenerator | Generates code files from DesignModel, preferring DesignCodeTemplate + DesignFieldCodeMapping; preview/download can target one language or package all configured languages |
MySQLDDL | DDL generator that prefers DesignSqlTemplate + DesignFieldDbMapping and falls back to classpath SQL templates |
DesignGenerationMetadataResolver | Central resolver for DB-managed templates/mappings/defaults with graceful fallback |
DdlContextBuilder | Builds template-friendly DDL context objects from DesignModel, DesignField, and DesignModelIndex |
VersionDdl / VersionDdlImpl | Converts List<ModelChangesDTO> to DdlTemplateContext, then renders combined DDL string (table + index) |
Code Generation Output
ModelCodeDTOgroups the generated files of one model under one language.ModelCodeDTO.filesis a list ofModelCodeFileDTO, rather than fixedentity/service/controllerfields.ModelCodeFileDTOcontainstemplateId,templateName,sequence,subDirectory,fileName,relativePath, andcontent.downloadCodelocates a file by the renderedrelativePathreturned frompreviewCode.
DDL Template Context
SQL templates do not read raw DesignModel / DesignField / DesignModelIndex entities directly.
Instead, VersionDdlImpl first converts merged row changes into template-friendly DTOs so template authors
only need to focus on SQL syntax instead of diff logic.
Top-level context:
DdlTemplateContext.createdModelsDdlTemplateContext.deletedModelsDdlTemplateContext.updatedModels
Per-model context (ModelDdlCtx):
- Base metadata:
modelName,labelName,description,tableName,oldTableName,pkColumn - Table change flags:
renamed,tableCommentChanged,tableCommentText - Field groups:
createdFields,deletedFields,updatedFields,renamedFields - Index groups:
createdIndexes,deletedIndexes,updatedIndexes,renamedIndexes - Render flags:
hasTableChanges,hasFieldChanges,hasIndexChanges,hasAlterTableChanges
Per-field context (FieldDdlCtx):
- Identity:
fieldName,columnName,oldColumnName,renamed - Display/comment:
labelName,description,commentText - Type/defaults:
fieldType,dbType,length,scale,required,autoIncrement,defaultValue
Per-index context (IndexDdlCtx):
- Identity:
indexName,oldIndexName,renamed - Definition:
columns,unique
Current MySQL template behavior:
CreateTable.pebrenders a single created model withmodel.createdFieldsDropTable.pebrenders a single deleted model withmodel.tableNameAlterTable.pebhandles table rename, table comment change, and fieldcreate/delete/update/renameAlterIndex.pebhandles indexcreate/delete/update/rename- Field rename is rendered as
DROP COLUMN oldColumnName+ADD COLUMN columnName ... - Index rename is rendered as
DROP INDEX oldIndexName+ADD INDEX indexName ... - Pure deleted indexes also generate SQL
- New-table indexes are sourced from
DesignModelIndex.createdRows
This structure is intentionally aligned with metadata terminology (Model, Field, Index) so the same
Pebble SQL templates can be stored in the database and customized per application with lower cognitive load.
Dependency
<dependency>
<groupId>io.softa</groupId>
<artifactId>studio-starter</artifactId>
<version>${softa.version}</version>
</dependency>Requirements
- metadata-starter: provides runtime metadata model management and upgrade API.
- es-starter: provides Elasticsearch changelog storage for version control change tracking.
- Database contains the studio metadata tables (see Data Model below).
Data Model
Core Design Models
| Entity | Description |
|---|---|
DesignPortfolio | Portfolio / project grouping for apps |
DesignApp | Application definition (name, code, databaseType, packageName). packageName is passed directly into the code template context |
DesignModel | Model definition (fields, indexes, tableName, storageType, etc.) |
DesignField | Field definition (fieldType, length, scale, relatedModel, etc.) |
DesignFieldDbMapping | Field type to database type mapping |
DesignFieldTypeDefault | Default metadata values for each field type |
DesignFieldCodeMapping | Language-specific property type mapping for each field type |
DesignSqlTemplate | Database-managed Pebble SQL templates per database type |
DesignCodeTemplate | Database-managed Pebble code templates per language with configurable sequence, subDirectory, fileName, and templateContent |
DesignModelIndex | Model index definition |
DesignView | View definition |
DesignNavigation | Navigation definition |
DesignOptionSet | Option set definition |
DesignOptionItem | Option item definition |
Version Control & Deployment Models
| Entity | Description |
|---|---|
DesignAppEnv | Application environment (Dev/Test/UAT/Prod), tracks currentVersionId |
DesignWorkItem | Change work item — scopes a unit of design changes via ES correlationId; versionId FK links to its Version |
DesignAppVersion | Version shell — aggregates WorkItem changes; versionType distinguishes Normal and Hotfix, and released ordering is determined by status + sealedTime |
DesignDeployment | Immutable deployment record — merged content from the sealedTime release interval with DDL and execution results |
DesignDeploymentVersion | Audit record linking a deployment to the versions it merged |
Current Runtime Sync Coverage
The current version-control and deployment pipeline only upgrades the following design-time models to runtime metadata:
DesignModel->SysModelDesignModelTrans->SysModelTransDesignField->SysFieldDesignFieldTrans->SysFieldTransDesignModelIndex->SysModelIndexDesignOptionSet->SysOptionSetDesignOptionSetTrans->SysOptionSetTransDesignOptionItem->SysOptionItemDesignOptionItemTrans->SysOptionItemTrans
The following design-time entities currently have CRUD support in studio-starter, but are not included in the release pipeline:
DesignViewDesignNavigation
DDL Storage Design
Version stores only change data (versionedContent = List<ModelChangesDTO> JSON), not DDL.
DDL is always generated on-the-fly from change data via VersionDdlImpl -> DdlTemplateContext -> Pebble templates.
This design ensures:
- DDL reflects the latest template version (templates may be upgraded independently)
- No consistency maintenance burden between stored DDL and templates
- Near-zero computation cost for on-the-fly generation
- Version DDL is intermediate (Deployment merges multiple Versions before execution)
Deployment stores pre-rendered DDL strings (mergedDdlTable, mergedDdlIndex) as the final
deployment artifact. This is the DDL that is actually executed against the target database.
Deployment is a self-contained, immutable record that includes merged content, DDL, and execution results.
Workflow
Design-Time Workflow
- Create a Portfolio and App
- Design Models, Fields, OptionSets, Views, Navigations, etc.
- Preview DDL and generated files for models
Version Control Workflow
- Create a WorkItem and mark it
IN_PROGRESS - Optionally mark the WorkItem as READY via
readyWorkItem - Make design changes (CRUD on models, fields, etc. — changes are logged to ES by WorkItem correlationId)
- Complete the WorkItem →
doneWorkItem- Optionally:
mergeToLatestVersionto quickly add the WorkItem to the latest app-level DRAFT version - If no DRAFT version exists, the system auto-creates one as
Normal
- Optionally:
- Create a Version (DRAFT) with
versionType = Normal | Hotfix-> add completed WorkItems ->sealVersion- Sealing aggregates WorkItem changes, computes diffHash, transitions to SEALED
unsealVersioncan revert a SEALED version back to DRAFT if it has not been deployed
freezeVersionafter production deployment (immutable)
Notes:
- The current API exposes
readyWorkItem; there is nostartWorkItemendpoint in code. DesignWorkItem.startTimeis defined in the entity but is not populated by the current implementation.
Deployment Workflow
- Incremental Deploy:
POST /DesignDeployment/deployToEnv?envId=&targetVersionId=- Selects released versions by
sealedTimein(env.currentVersionId, targetVersion] - Merges version contents via
VersionMerger, generates DDL - Creates a self-contained Deployment record with merged content + DDL
- Executes deployment (local or remote) and updates
env.currentVersionId - Auto-freezes versions after successful PROD deployment
- For new environments (no
currentVersionId), all released versions up to the target are merged
- Selects released versions by
- Retry:
POST /DesignDeployment/retry
Notes:
DesignDeploymentStatuscontainsROLLED_BACK, but there is currently no rollback API or rollback implementation instudio-starter.DesignAppEnv.asyncUpgradecurrently falls back to synchronous execution.
Key APIs
Model Design
| Endpoint | Description |
|---|---|
GET /DesignModel/previewDDL?id= | Preview CREATE TABLE DDL for a model |
GET /DesignModel/previewCode?id=&codeLang= | Preview generated code files for one language, including rendered relative paths. codeLang is optional only when exactly one language package is available |
GET /DesignModel/previewAllCode?id= | Preview all generated language packages for a model |
GET /DesignModel/downloadCode?id=&codeLang=&relativePath= | Download one generated file by its rendered relative path. relativePath must come from previewCode |
GET /DesignModel/downloadZip?id=&codeLang= | Download one language package as a ZIP. codeLang is optional only when exactly one language package is available |
GET /DesignModel/downloadAllZip?id= | Download all generated language packages in one ZIP, grouped under <codeLang>/ |
WorkItem Lifecycle
| Endpoint | Description |
|---|---|
POST /DesignWorkItem/readyWorkItem?id= | Mark a WorkItem as READY from IN_PROGRESS |
POST /DesignWorkItem/doneWorkItem?id= | Complete — end change tracking |
GET /DesignWorkItem/previewChanges?id= | Preview accumulated metadata changes |
GET /DesignWorkItem/previewDDL?id= | Preview DDL SQL from WorkItem changes (copy to DB client) |
POST /DesignWorkItem/mergeToLatestVersion?id= | Merge a DONE WorkItem into the latest app-level DRAFT version (auto-creates if none exists) |
POST /DesignWorkItem/cancelWorkItem?id= | Cancel the work item |
POST /DesignWorkItem/deferWorkItem?id= | Defer the work item |
POST /DesignWorkItem/reopenWorkItem?id= | Reopen a completed/cancelled/deferred work item |
Version Lifecycle
| Endpoint | Description |
|---|---|
POST /DesignAppVersion/createOne | Create a new version (DRAFT, versionType = Normal or Hotfix, default Normal) |
POST /DesignAppVersion/addWorkItem?versionId=&workItemId= | Add a DONE WorkItem to the version |
POST /DesignAppVersion/removeWorkItem?versionId=&workItemId= | Remove a WorkItem from the version |
GET /DesignAppVersion/previewVersion?id= | Preview merged version content |
GET /DesignAppVersion/previewDDL?id= | Preview DDL SQL from version content (copy to DB client) |
POST /DesignAppVersion/sealVersion?id= | Seal version (DRAFT → SEALED) |
POST /DesignAppVersion/unsealVersion?id= | Unseal version (SEALED → DRAFT, if not deployed) |
POST /DesignAppVersion/freezeVersion?id= | Freeze version (SEALED → FROZEN) |
Deployment
| Endpoint | Description |
|---|---|
POST /DesignDeployment/deployToEnv?envId=&targetVersionId= | Deploy a version to an environment |
POST /DesignDeployment/retry?deploymentId= | Retry a failed deployment |
GET /DesignDeployment/previewDeployment?deploymentId= | Preview deployment content and DDL |
GET /DesignDeployment/previewDDL?deploymentId= | Preview DDL SQL of deployment (copy to DB client) |
Environment
| Endpoint | Description |
|---|---|
POST /DesignAppEnv/previewBetweenEnv?sourceEnvId=&targetEnvId= | Preview changes between two environments |
Row-Level Merge (model + rowId)
Changes are tracked and merged at the model + rowId level throughout the entire pipeline:
WorkItem → Version (seal)
VersionControlImpl queries ES changelogs by correlationId IN (workItemIds), groups by rowId,
and merges multiple changelog entries for the same row into a single RowChangeDTO.
A row that is both created and deleted within the same WorkItem set is cancelled (no net change).
Version → Deployment (deploy)
VersionMerger merges multiple Version contents using a modelName → (rowId → RowChangeDTO) map.
When the same row is modified across multiple Versions, the state machine folds the changes:
| V1 Action | V2 Action | Net Result |
|---|---|---|
| CREATE | UPDATE | CREATE (with V2 data) |
| CREATE | DELETE | Cancelled (no net change) |
| UPDATE | UPDATE | UPDATE (merged before/after) |
| UPDATE | DELETE | DELETE (V1 dataBeforeChange) |
| DELETE | CREATE | UPDATE (re-creation) |
This ensures a single record modified multiple times across WorkItems and Versions is published as one net change in the Deployment — no duplicate or redundant operations.
Deployment selects the versions to merge from the app’s released stream:
- only
SEALEDandFROZENversions participate - versions are ordered by
sealedTime ASC - the merge interval is
(env.currentVersionId, targetVersion] targetVersionmust itself beSEALEDorFROZEN
Current Gaps
Highest-Priority Fixes
- Bring
DesignView/DesignNavigationinto the release pipeline- These entities have design-time CRUD, and runtime counterparts already exist, but they are not part of
VERSION_CONTROL_MODELS. This is the highest-value functional gap because design changes cannot be promoted through environments today.
- These entities have design-time CRUD, and runtime counterparts already exist, but they are not part of
- Repair the WorkItem activation model
- README previously documented
startWorkItem, but the implementation exposesreadyWorkItemand does not populateDesignWorkItem.startTime. The API name, workflow semantics, and persisted timestamps should be aligned.
- README previously documented
- Close the deployment contract gaps
READMEpreviously mentioned rollback, andDesignDeploymentStatusincludesROLLED_BACK, but no rollback endpoint/service exists.DesignAppEnv.asyncUpgradealso still executes synchronously.
- Clean up reserved or partially wired environment/config fields
DesignAppEnv.protectedEnv,active,clientId, andautoUpgradeare not currently enforced in deployment flow.
Additional Notes
DesignView.defaultViewis currently only a design-time flag. Runtime personal default views are managed bySysViewDefault.DesignOptionSet.optionItemsexists on the entity, but the current starter relies on separateDesignOptionItemrecords rather than automatically hydrating this collection.- Some metadata fields are consumed outside
studio-starter, mainly bysofta-ormandmetadata-starter. For example,displayName,searchName,activeControl,multiTenant,versionLock,relatedField,joinLeft,joinRight,cascadedField,translatable,encrypted, andmaskingTypedo take effect at runtime.
Status Enums
DesignAppVersionStatus
DRAFT ⇄ SEALED → FROZEN
DRAFT → SEALED:sealVersion— aggregates WorkItem changes, computes diffHashSEALED → DRAFT:unsealVersion— only if not deployed (no Deployment reference)SEALED → FROZEN:freezeVersion— after production deployment, immutable
DesignAppVersionType
Normal / Hotfix
Normal: planned release versionHotfix: emergency patch release version
DesignWorkItemStatus
IN_PROGRESS → READY → DONE
IN_PROGRESS -> READY:readyWorkItemIN_PROGRESS / READY -> DONE:doneWorkItemIN_PROGRESS / READY / DEFERRED -> CANCELLED:cancelWorkItemIN_PROGRESS -> DEFERRED:deferWorkItemDONE / CANCELLED / DEFERRED -> IN_PROGRESS:reopenWorkItem
DesignDeploymentStatus
PENDING → DEPLOYING → SUCCESS / FAILURE / ROLLED_BACK
DesignAppEnvType
DEV, TEST, UAT, PROD