meldestelle/docs/01_Architecture/Reference/Wizard-DSL-README.md
2026-04-21 16:21:22 +02:00

3.4 KiB
Raw Blame History

type status owner date
Reference ACTIVE Lead Architect 2026-04-21

WizardDSL & Orchestrator Referenz

Ziel

Deklarative Beschreibung von WizardFlows als Graph (Steps, Guards, Transitions) mit klaren SideEffects und OfflineDraftUnterstützung.

KernInterfaces (Skizze)

interface StepId

data class WizardContext(
  val origin: AppScreen,
  val role: String?,
  val isOnline: Boolean,
  val stats: MasterdataStats?
)

data class WizardState<S: StepId, A>(
  val current: S,
  val history: List<S> = emptyList(),
  val acc: A,
  val errors: List<String> = emptyList()
)

typealias Guard<S, A> = (WizardContext, A) -> Boolean

data class Transition<S: StepId>(val target: S, val guard: Guard<S, *>? = null, val id: String)

interface StepEffects<S: StepId, A> {
  suspend fun onEnter(ctx: WizardContext, state: WizardState<S, A>) {}
  suspend fun onLeave(ctx: WizardContext, state: WizardState<S, A>) {}
  suspend fun onComplete(ctx: WizardContext, state: WizardState<S, A>) {}
}

DSL (Skizze)

class FlowBuilder<S: StepId, A> {
  fun step(id: S, block: StepBuilder<S, A>.() -> Unit) { /* … */ }
}
class StepBuilder<S: StepId, A> {
  fun onEnter(block: suspend (WizardContext, WizardState<S, A>) -> Unit) { /* … */ }
  fun whenGuard(id: String, g: Guard<S, A>, go: S) { /* … */ }
  fun otherwise(go: S) { /* … */ }
}
fun <S: StepId, A> flow(start: S, build: FlowBuilder<S, A>.() -> Unit): WizardRuntime<S, A> { /* … */ }

Beispiel EventFlow (Auszug)

sealed interface EventStep: StepId {
  data object ZnsCheck: EventStep
  data object VeranstalterSelection: EventStep
  data object AnsprechpersonMapping: EventStep
  data object MetaData: EventStep
  data object TurnierAnlage: EventStep
  data object Summary: EventStep
}

data class EventAcc(
  val veranstalterId: Uuid? = null,
  val veranstalterNr: String = "",
  val veranstalterName: String = "",
  val ansprechpersonSatznr: String = "",
  val name: String = "",
  val ort: String = "",
  val start: LocalDate? = null,
  val end: LocalDate? = null,
  val turniere: List<TurnierEntry> = emptyList()
)

object EventGuards {
  val hasZns: Guard<EventStep, EventAcc> = { ctx, _ -> (ctx.stats?.vereinCount ?: 0) > 0 }
  val needsContactPerson: Guard<EventStep, EventAcc> = { _, acc -> acc.veranstalterId == null || acc.veranstalterNr.startsWith("ORG-") }
}

val EventFlow = flow<EventStep, EventAcc>(start = EventStep.ZnsCheck) {
  step(EventStep.ZnsCheck) {
    onEnter { ctx, _ -> /* prefetch stats */ }
    whenGuard("hasZns", EventGuards.hasZns, go = EventStep.VeranstalterSelection)
    otherwise(go = EventStep.VeranstalterSelection)
  }
  step(EventStep.VeranstalterSelection) {
    whenGuard("needsContact", EventGuards.needsContactPerson, go = EventStep.AnsprechpersonMapping)
    otherwise(go = EventStep.MetaData)
  }
}

DevTools

  • Strukturierte Logs je Transition (from, to, guard-id, result, duration).
  • GraphExport (DOT/PlantUML) aus der DSL für Doku & Reviews.

Tests (Empfehlungen)

  • Unit: Guards (100% BranchAbdeckung), RuntimeHistory.
  • Property: ResumeDeterminismus (Draft → korrekter Step).
  • Snapshot: ComposePanels mit Beispielkontexten.

Verweise

  • ADR0025 Orchestrator · ADR0026 ValidationPolicy · ADR0027 DraftDomain & DeltaSync
  • Roadmap: docs/01_Architecture/MASTER_ROADMAP.md#3-initiative-wizard-orchestrator--offline-drafts-q2q3-2026