ADR 003: Generated API Structure and Naming
Status
Accepted
Context
Generated API must organize hundreds of constants (element IDs, messages, variables, etc.) from BPMN models. Needs a structure that prevents naming conflicts, provides IDE support, follows language conventions, and handles different property types (strings vs. structured data).
Decision
Nested Object Organization
Generate categorized nested structure:
object ProcessApi {
const val PROCESS_ID: String = "..."
object Elements { /* flow nodes */ }
object Messages { /* message definitions */ }
object ServiceTasks { /* service task types */ }
object Timers { /* timer definitions */ }
object Errors { /* error definitions */ }
object Signals { /* signal definitions */ }
object Variables { /* process variables */ }
}UPPER_SNAKE_CASE Naming Convention
Transform BPMN identifiers to follow Kotlin/Java constant conventions:
// BPMN: "Timer_EveryDay" → Generated constant name
const val TIMER_EVERY_DAY: String = "Timer_EveryDay"
// BPMN: "subscriptionId" → Generated constant name
const val SUBSCRIPTION_ID: String = "subscriptionId"
// BPMN: "Timer_After3Days" → Generated constant name
const val TIMER_AFTER_3_DAYS: String = "Timer_After3Days"Constant values preserve original BPMN identifiers unchanged. Conversion adds underscores at case boundaries and between letters/digits, then uppercases.
Service Tasks & Messages: For consistency, constant names are derived from the semantic identifier (for instance worker type for service tasks, message name for messages) rather than technical element IDs. This makes constants more meaningful and aligned with their business purpose. Moreover it allows us to generate a distinct list, since workerTypes and messageNames may be reused in multiple elements of a process:
// Service task with camunda:topic="newsletter.sendWelcomeMail"
const val NEWSLETTER_SEND_WELCOME_MAIL: String = "newsletter.sendWelcomeMail"
// Message with name="Message_FormSubmitted"
const val MESSAGE_FORM_SUBMITTED: String = "Message_FormSubmitted"Const vs Val for Property Types
Use const val for string constants, val for data class instances:
// String constants - compile-time
const val ACTIVITY_SEND_MAIL: String = "Activity_SendMail"
// Structured data - runtime
val TIMER_EVERY_DAY: BpmnTimer = BpmnTimer("Duration", "PT1M")
data class BpmnTimer(val type: String, val timerValue: String)Consequences
Positive
- Namespace isolation:
Elements.TIMERvsMessages.TIMER- no conflicts - IDE autocomplete: Typing
Elements.shows only relevant constants - Standards compliance: Eliminates IDE warnings, follows style guides
- Improved readability:
TIMER_AFTER_3_DAYSclearer thanTimer_After3Days - Compile-time optimization: String constants inlined at call sites
- Type safety: Data classes provide structured access for complex properties
Negative
- Verbosity: Longer qualified names (
ProcessApi.Elements.TIMER) - Breaking change: UPPER_SNAKE_CASE transformation incompatible with previous mixed casing
- Inconsistency: Mixed property types (
constvsval) due to Kotlin limitations - Migration burden: Users must update all constant references when upgrading
Alternatives Considered
Flat structure with prefixes (Rejected)
- Loses IDE categorization benefits
- All constants in single autocomplete list
Preserve BPMN casing (Rejected)
- Violates language conventions
- Causes IDE warnings and linter errors
All val properties (Rejected)
- Loses compile-time constant benefits for strings
Implementation
- Conversion:
StringUtils.toUpperSnakeCase()handles case transformation - Structure: Builders (Kotlin/Java) create nested static classes/objects
- Properties: Const for strings, val for data classes (Kotlin compiler requirement)