Skip to Content
DocsFeaturesStudioStudio

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

DirectoryTemplatesPurpose
templates/code/entity/{{modelName}}.java.peb, service/{{modelName}}Service.java.peb, service/impl/{{modelName}}ServiceImpl.java.peb, controller/{{modelName}}Controller.java.pebFallback 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.pebFallback MySQL DDL templates

Code Template Rules

  • Database mode: DesignCodeTemplate entries are loaded by codeLang and sorted by sequence, then rendered into a ModelCodeDTO.files list.
  • DesignCodeTemplate.subDirectory is a Pebble template. null, blank, whitespace, ., ./, and / are treated as the zip root directory. Leading ./, leading /, trailing /, duplicated /, and \ are normalized.
  • DesignCodeTemplate.fileName is 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-append DesignCodeLang.fileExtension.
  • DesignCodeTemplate.fileName cannot be blank after rendering and cannot contain directory separators. Directory structure must be expressed only through subDirectory.
  • Fallback mode is enabled only when no code template language is configured in the database. In that case, every classpath template under templates/code/**/*.peb is rendered.
  • In fallback mode, the rendered output file name comes from the fallback template path itself. For example, templates/code/service/{{modelName}}Service.java.peb generates service/SysModelService.java.
  • Generated single-file downloads use the rendered file name, while zip downloads use the rendered relative path. downloadAllZip prefixes each language package with <codeLang>/.

Core Classes

ClassDescription
TemplateEnginePebble engine wrapper — singleton PebbleEngine with caching, no auto-escaping
CodeGeneratorGenerates code files from DesignModel, preferring DesignCodeTemplate + DesignFieldCodeMapping; preview/download can target one language or package all configured languages
MySQLDDLDDL generator that prefers DesignSqlTemplate + DesignFieldDbMapping and falls back to classpath SQL templates
DesignGenerationMetadataResolverCentral resolver for DB-managed templates/mappings/defaults with graceful fallback
DdlContextBuilderBuilds template-friendly DDL context objects from DesignModel, DesignField, and DesignModelIndex
VersionDdl / VersionDdlImplConverts List<ModelChangesDTO> to DdlTemplateContext, then renders combined DDL string (table + index)

Code Generation Output

  • ModelCodeDTO groups the generated files of one model under one language.
  • ModelCodeDTO.files is a list of ModelCodeFileDTO, rather than fixed entity/service/controller fields.
  • ModelCodeFileDTO contains templateId, templateName, sequence, subDirectory, fileName, relativePath, and content.
  • downloadCode locates a file by the rendered relativePath returned from previewCode.

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.createdModels
  • DdlTemplateContext.deletedModels
  • DdlTemplateContext.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.peb renders a single created model with model.createdFields
  • DropTable.peb renders a single deleted model with model.tableName
  • AlterTable.peb handles table rename, table comment change, and field create/delete/update/rename
  • AlterIndex.peb handles index create/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

EntityDescription
DesignPortfolioPortfolio / project grouping for apps
DesignAppApplication definition (name, code, databaseType, packageName). packageName is passed directly into the code template context
DesignModelModel definition (fields, indexes, tableName, storageType, etc.)
DesignFieldField definition (fieldType, length, scale, relatedModel, etc.)
DesignFieldDbMappingField type to database type mapping
DesignFieldTypeDefaultDefault metadata values for each field type
DesignFieldCodeMappingLanguage-specific property type mapping for each field type
DesignSqlTemplateDatabase-managed Pebble SQL templates per database type
DesignCodeTemplateDatabase-managed Pebble code templates per language with configurable sequence, subDirectory, fileName, and templateContent
DesignModelIndexModel index definition
DesignViewView definition
DesignNavigationNavigation definition
DesignOptionSetOption set definition
DesignOptionItemOption item definition

Version Control & Deployment Models

EntityDescription
DesignAppEnvApplication environment (Dev/Test/UAT/Prod), tracks currentVersionId
DesignWorkItemChange work item — scopes a unit of design changes via ES correlationId; versionId FK links to its Version
DesignAppVersionVersion shell — aggregates WorkItem changes; versionType distinguishes Normal and Hotfix, and released ordering is determined by status + sealedTime
DesignDeploymentImmutable deployment record — merged content from the sealedTime release interval with DDL and execution results
DesignDeploymentVersionAudit 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 -> SysModel
  • DesignModelTrans -> SysModelTrans
  • DesignField -> SysField
  • DesignFieldTrans -> SysFieldTrans
  • DesignModelIndex -> SysModelIndex
  • DesignOptionSet -> SysOptionSet
  • DesignOptionSetTrans -> SysOptionSetTrans
  • DesignOptionItem -> SysOptionItem
  • DesignOptionItemTrans -> SysOptionItemTrans

The following design-time entities currently have CRUD support in studio-starter, but are not included in the release pipeline:

  • DesignView
  • DesignNavigation

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

  1. Create a Portfolio and App
  2. Design Models, Fields, OptionSets, Views, Navigations, etc.
  3. Preview DDL and generated files for models

Version Control Workflow

  1. Create a WorkItem and mark it IN_PROGRESS
  2. Optionally mark the WorkItem as READY via readyWorkItem
  3. Make design changes (CRUD on models, fields, etc. — changes are logged to ES by WorkItem correlationId)
  4. Complete the WorkItem → doneWorkItem
    • Optionally: mergeToLatestVersion to quickly add the WorkItem to the latest app-level DRAFT version
    • If no DRAFT version exists, the system auto-creates one as Normal
  5. Create a Version (DRAFT) with versionType = Normal | Hotfix -> add completed WorkItems -> sealVersion
    • Sealing aggregates WorkItem changes, computes diffHash, transitions to SEALED
    • unsealVersion can revert a SEALED version back to DRAFT if it has not been deployed
  6. freezeVersion after production deployment (immutable)

Notes:

  • The current API exposes readyWorkItem; there is no startWorkItem endpoint in code.
  • DesignWorkItem.startTime is defined in the entity but is not populated by the current implementation.

Deployment Workflow

  1. Incremental Deploy: POST /DesignDeployment/deployToEnv?envId=&targetVersionId=
    • Selects released versions by sealedTime in (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
  2. Retry: POST /DesignDeployment/retry

Notes:

  • DesignDeploymentStatus contains ROLLED_BACK, but there is currently no rollback API or rollback implementation in studio-starter.
  • DesignAppEnv.asyncUpgrade currently falls back to synchronous execution.

Key APIs

Model Design

EndpointDescription
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

EndpointDescription
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

EndpointDescription
POST /DesignAppVersion/createOneCreate 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

EndpointDescription
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

EndpointDescription
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 ActionV2 ActionNet Result
CREATEUPDATECREATE (with V2 data)
CREATEDELETECancelled (no net change)
UPDATEUPDATEUPDATE (merged before/after)
UPDATEDELETEDELETE (V1 dataBeforeChange)
DELETECREATEUPDATE (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 SEALED and FROZEN versions participate
  • versions are ordered by sealedTime ASC
  • the merge interval is (env.currentVersionId, targetVersion]
  • targetVersion must itself be SEALED or FROZEN

Current Gaps

Highest-Priority Fixes

  1. Bring DesignView / DesignNavigation into 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.
  2. Repair the WorkItem activation model
    • README previously documented startWorkItem, but the implementation exposes readyWorkItem and does not populate DesignWorkItem.startTime. The API name, workflow semantics, and persisted timestamps should be aligned.
  3. Close the deployment contract gaps
    • README previously mentioned rollback, and DesignDeploymentStatus includes ROLLED_BACK, but no rollback endpoint/service exists. DesignAppEnv.asyncUpgrade also still executes synchronously.
  4. Clean up reserved or partially wired environment/config fields
    • DesignAppEnv.protectedEnv, active, clientId, and autoUpgrade are not currently enforced in deployment flow.

Additional Notes

  • DesignView.defaultView is currently only a design-time flag. Runtime personal default views are managed by SysViewDefault.
  • DesignOptionSet.optionItems exists on the entity, but the current starter relies on separate DesignOptionItem records rather than automatically hydrating this collection.
  • Some metadata fields are consumed outside studio-starter, mainly by softa-orm and metadata-starter. For example, displayName, searchName, activeControl, multiTenant, versionLock, relatedField, joinLeft, joinRight, cascadedField, translatable, encrypted, and maskingType do take effect at runtime.

Status Enums

DesignAppVersionStatus

DRAFTSEALEDFROZEN

  • DRAFT → SEALED: sealVersion — aggregates WorkItem changes, computes diffHash
  • SEALED → DRAFT: unsealVersion — only if not deployed (no Deployment reference)
  • SEALED → FROZEN: freezeVersion — after production deployment, immutable

DesignAppVersionType

Normal / Hotfix

  • Normal: planned release version
  • Hotfix: emergency patch release version

DesignWorkItemStatus

IN_PROGRESSREADYDONE

  • IN_PROGRESS -> READY: readyWorkItem
  • IN_PROGRESS / READY -> DONE: doneWorkItem
  • IN_PROGRESS / READY / DEFERRED -> CANCELLED: cancelWorkItem
  • IN_PROGRESS -> DEFERRED: deferWorkItem
  • DONE / CANCELLED / DEFERRED -> IN_PROGRESS: reopenWorkItem

DesignDeploymentStatus

PENDINGDEPLOYINGSUCCESS / FAILURE / ROLLED_BACK

DesignAppEnvType

DEV, TEST, UAT, PROD

Last updated on