Configurable campaign platform: from copied pages to a controlled DSL
On a gaming platform, campaign rules, assets, dates, rewards, and game-area themes change continuously. Copying a page for every campaign feels fast initially, but creates duplicated code, release congestion, and version drift. Configuration should separate stable capabilities from frequent, constrained change—not replace all code with arbitrary JSON.
Define the boundary first
Good candidates for configuration are recurring and structurally similar: banners, task lists, rankings, reward displays, rules, and controlled button actions. Complex real-time interactions and one-off creative experiences may remain custom code.
A page DSL
interface ActivityPageSchema {
schemaVersion: 3
pageId: string
theme: ThemeTokenSet
dataSources: DataSourceConfig[]
modules: ActivityModule[]
release: {
startAt: string
endAt: string
audience?: AudienceRule
}
}
type ActivityModule =
| { type: 'hero'; version: 2; props: HeroProps }
| { type: 'task-list'; version: 1; props: TaskListProps }
| { type: 'ranking'; version: 2; props: RankingProps }Each module has a type, version, and props schema. The editor generates and validates forms from that contract; the runtime resolves only allow-listed components. Actions use a controlled protocol rather than arbitrary scripts.
One renderer for editing, preview, and runtime
Separate preview and production renderers create “preview passed, release failed” drift. A safer design shares the rendering kernel and injects explicit environment and data-source adapters. Preview should show the schema version, data environment, and permission identity.
Versioning, migration, and rollback
Draft → validate → preview → approve → release → canary/observe
↑ ↓
└──────── create a new draft ← rollback ─────┘- Published versions are immutable; editing creates a new draft.
- Configuration carries
schemaVersion; modules provide tested migrations. - Pre-release validation checks assets, APIs, dates, and permissions.
- Rollback changes a version pointer instead of modifying production history.
Failure paths
- Unknown module: show a controlled placeholder and report it without crashing the page.
- Resource failure: fallback, retry, or skip with module-level context.
- API timeout: isolate the error to the module.
- Version mismatch: validate capabilities and block an incompatible release.
When commercial metrics cannot be disclosed, credible evidence includes one module serving several game areas, operational changes without copied pages, immutable previewable releases, and tested contracts and error boundaries.