ADR-012: JSON Schema para forms¶
- Status: Accepted
- Date: 2026-05-14
- Tags: webapps, tasklist, forms
Context and Problem Statement¶
User tasks requieren forms para que humanos ingresen data. Camunda soporta 3 tipos: embedded en BPMN XML, deployed forms con formId+version, external forms via formKey. ¿Qué soporta el MVP?
Decision Drivers¶
- Forms son standard pattern - reusar tools existentes
- JSON Schema es industry standard (W3C/IETF)
- react-jsonschema-form maduro para rendering
- AJV maduro para validation
- Embedded forms en BPMN XML coupling tight
Considered Options¶
- JSON Schema forms únicamente (deployed resources)
- Soportar los 3 tipos como Camunda (embedded, deployed, external)
- External forms únicamente (formKey link)
- HTML templates custom
Decision Outcome¶
Chosen option: JSON Schema forms únicamente porque: - Standard maduro con ecosystem rico - react-jsonschema-form, Vue/Angular equivalents - AJV para server-side validation - Decoupled del BPMN XML (cleaner) - Versionable como recurso independiente
Positive Consequences¶
- Tools existing (rjsf, AJV)
- Validation client + server desde mismo schema
- Forms versionable independientemente
- Decoupling BPMN ↔ Forms
- Cero código de form designer
- Standard sobrevivirá
Negative Consequences¶
- Visual form designer requires external tool
- Migration desde Camunda forms requires conversion
- Custom widgets para casos complejos
- Aprendizaje JSON Schema syntax
Schema storage¶
CREATE TABLE forms (
form_key BIGINT PRIMARY KEY,
form_id TEXT NOT NULL,
version INT NOT NULL,
tenant_id TEXT NOT NULL,
schema JSONB NOT NULL, -- JSON Schema
ui_schema JSONB, -- UI hints
deployment_key BIGINT NOT NULL,
deployed_at TIMESTAMPTZ NOT NULL,
UNIQUE (form_id, version, tenant_id)
);
Ejemplo¶
{
"form_id": "loan-application-v1",
"schema": {
"type": "object",
"properties": {
"amount": { "type": "number", "minimum": 1000, "maximum": 1000000 },
"purpose": { "type": "string", "enum": ["personal", "business"] },
"notes": { "type": "string", "maxLength": 500 }
},
"required": ["amount", "purpose"]
},
"ui_schema": {
"notes": { "ui:widget": "textarea" }
}
}
Rendering¶
import Form from '@rjsf/core';
import validator from '@rjsf/validator-ajv8';
<Form
schema={form.schema}
uiSchema={form.ui_schema}
validator={validator}
formData={variables}
onSubmit={({ formData }) => completeTask(formData)}
/>
Server-side validation¶
import Ajv from 'ajv';
const ajv = new Ajv();
async function completeTask(taskKey, variables) {
const task = await getTask(taskKey);
const form = await getForm(task.form_id, task.form_version);
const validate = ajv.compile(form.schema);
if (!validate(variables)) {
throw new ValidationError(validate.errors);
}
await persistComplete(taskKey, variables);
}
Doble validación (defense in depth).
Migration path desde Camunda forms¶
Tool/script de conversion:
mvp-cli forms convert --from=camunda --input=camunda-form.json
# Output: mvp-form.json con JSON Schema
Camunda forms (custom JSON schema) → MVP forms (standard JSON Schema). Conversion automatic para forms simples; manual para complejos.
BPMN reference¶
En BPMN XML, user task referencia form:
<bpmn:userTask id="review">
<bpmn:extensionElements>
<zeebe:formDefinition formId="loan-application-v1" />
</bpmn:extensionElements>
</bpmn:userTask>
Engine resuelve formId+version a form schema.
Links¶
- analysis/operate-tasklist-mvp-detailed — Spec Tasklist
- entities/tasklist — Tasklist features
- JSON Schema
- react-jsonschema-form
- AJV