Von Clickup zu Vaadin – Teil I

Sven Ruppert

Der Anlass

Seit vielen Jahren verwalte ich meine Schreibarbeit in ClickUp: die Themen, an denen ich arbeite, die einzelnen Artikel, ihre Übersetzungen, die Orte, an denen sie erscheinen, und die kaufmännische Abwicklung, die daran hängt. Über die Zeit ist dort ein ansehnlicher Bestand gewachsen, und in vielem leistet das Werkzeug, was es soll. Es ist nicht die Unzufriedenheit mit einem einzelnen Ärgernis, die mich umdenken ließ, sondern das langsame Summieren mehrerer kleiner Reibungen, bis die Frage unausweichlich wurde, ob es so weitergehen musste.

Die erste Reibung ist von grundsätzlicher Natur. Meine Daten liegen in einer Cloud jenseits des Atlantiks, auf Servern, deren rechtlichen und politischen Rahmen ich nicht bestimme. Für Notizen mag das gleichgültig sein; für die gesammelte Arbeit vieler Jahre, samt den Namen von Auftraggebern und den Bedingungen dahinter, ist es das nicht. Der Wunsch, die Hoheit über diesen Bestand zurückzugewinnen, ist mit der Zeit von einem diffusen Unbehagen zu einer klaren Anforderung geworden.

Die zweite Reibung ist gewissermaßen das Gegenteil eines Mangels. ClickUp wird von Jahr zu Jahr mächtiger und nimmt immer mehr auf, was Teams brauchen könnten – nur brauche ich das meiste davon nicht. Ich bezahle und bediene eine wachsende Fülle, von der ich nur einen schmalen Ausschnitt nutze, und mit jeder neuen Funktion wird das Werkzeug ein wenig schwerer, als es für meinen Zweck sein müsste. Damit hängt unmittelbar die dritte Reibung zusammen, die schlichteste von allen: Das kostet Geld, Monat für Monat, für einen Nutzen, der in keinem guten Verhältnis dazu steht.

Die vierte Reibung ist die eigentliche verräterische, denn sie ist keine der Preise, sondern der Passung. Mein Arbeitsablauf verlangt Unterscheidungen, die ClickUp in seinem Modell nicht vorsieht, und so behelfe ich mich, wie man sich eben behilft: mit Tags, mit Zusatzfeldern, mit einem einzigen Statusfeld, dem ich mehr Bedeutungen aufbürde, als ein Feld tragen kann. Es funktioniert, so wie ein Werkzeug, das man zweckentfremdet – nicht falsch, aber stets ein wenig gegen den Strich. Genau an dieser Stelle keimte der Verdacht, dass mein Ablauf eine Struktur hätte, die das fremde Werkzeug mir nicht zeigt, sondern verbirgt.

Und schließlich möchte ich aus diesen Daten etwas machen, das über ihre bloße Verwaltung hinausgeht: sie zu PDFs zusammenstellen, zu Sammelausgaben bündeln, aus dem Bestand heraus veröffentlichen. Das ist keine Aufgabe, für die ein Aufgabenverwalter gedacht ist, und je länger ich darüber nachdachte, desto deutlicher wurde, dass ich nicht ein besseres ClickUp suchte, sondern etwas anderes.

So stand am Anfang nicht eine Entscheidung, sondern eine Summe von Beobachtungen – und die Frage, ob man die fremde Software weiter erträgt, weil der Eigenbau zu teuer schien, oder ob sich an dieser Rechnung inzwischen etwas geändert hat.

Die These

Bis vor Kurzem hätte ich diese Frage rasch beantwortet, und zwar zu Ungunsten des Eigenbaus. Eine maßgeschneiderte Anwendung, die genau meinen Ablauf abbildet und meine Daten auf meinem eigenen Boden hält, war für einen Einzelnen schlicht unwirtschaftlich. Der Entwurf des Modells, die Oberfläche, die Persistenz, die vielen kleinen Wege dazwischen – all das kostet Zeit, und Zeit ist für den Einzelnen der teuerste Posten überhaupt. Also erträgt man die fremde Software mitsamt ihrem Funktionswildwuchs, weil die Alternative sich nicht rechnet. Diese Rechnung war jahrelang richtig.

Was sich geändert hat, ist nicht mein Anspruch, sondern der Preis seiner Erfüllung. Ein KI-gestützter Arbeitsablauf verschiebt den Aufwand, der bislang gegen den Eigenbau sprach, so weit, dass die Rechnung kippt. Nicht, weil eine Maschine mir das Denken abnähme – die fachlichen Entscheidungen bleiben meine –, sondern weil der Weg von der Idee zum lauffähigen Entwurf sich derart verkürzt, dass sich der zugeschnittene Eigenbau plötzlich lohnt. Und genau darin liegt die eigentliche These dieses Beitrags: Datenhoheit ist nicht länger ein Vorrecht großer Organisationen mit eigener Entwicklungsabteilung. Die KI-gestützte Entwicklung macht die eigene, passgenaue Anwendung auch für den Einzelnen erschwinglich.

Diese These hat drei Glieder, die man voneinander unterscheiden sollte. Die Datenhoheit ist das Warum – der Grund, warum ich diesen Weg überhaupt gehe. Die KI-gestützte Entwicklung ist das ökonomisch Neue, das den Weg erst begehbar macht; sie ist die Antwort auf die Frage, wie das für einen Einzelnen überhaupt möglich sein soll. Und womit ist ein Werkzeug, mit dem sich eine solche Anwendung durchgängig in einer einzigen Sprache bauen lässt, vom Datenmodell bis zur Oberfläche, ohne den sonst üblichen Bruch zwischen den Welten – dazu später mehr, an der Stelle, an der es sich von selbst zeigt.

An dieser Stelle drängt sich ein Einwand auf, den ich lieber selbst ausspreche, ehe ihn ein Leser formuliert. Ich rede von Souveränität und Unabhängigkeit – und lasse zugleich einen KI-Assistenten eines US-amerikanischen Anbieters mein Modell entwerfen und meine Oberfläche bauen. Ist das nicht ein Widerspruch, der die ganze Erzählung untergräbt? Er ist es nicht, sobald man genau hinsieht, was hier als souverän sein soll. Souveränität meint die Hoheit über den Ort, das Format und das Eigentum meiner Daten sowie über den Quelltext, der am Ende auf meiner eigenen Infrastruktur läuft und dort beliebig weiterlebt – ohne den Assistenten, der ihn schreiben half. Die KI ist ein Werkzeug der Herstellung, kein Bestandteil des Betriebs. Sie steht am Anfang, wie der Zimmermann am Haus steht, das anschließend ohne ihn bewohnt wird. Wer diesen Unterschied zwischen Herstellung und Betrieb nicht macht, sieht einen Widerspruch; wer ihn macht, sieht die Arbeitsteilung, die den ganzen Weg erst trägt.

So verstanden ist die KI nicht der Gegenspieler der Souveränität, sondern ihr Ermöglicher – und was am Ende bleibt, gehört mir: die Daten, das Modell, der Quelltext. Wie dieser Weg im Einzelnen aussah, davon handelt der Rest dieses Beitrags.

Der Vorgang als Beweisstück

Bevor ich die einzelnen Stationen dieses Weges beschreibe, muss ich über die Art sprechen, in der er zurückgelegt wurde – denn sie ist nicht Beiwerk, sondern selbst das Argument. Ein Beitrag, der behauptet, KI-gestützte Entwicklung mache den zugeschnittenen Eigenbau erschwinglich, könnte diese Behauptung mit Zahlen und Versprechungen untermauern. Überzeugender ist es, den Vorgang vorzuführen und den Leser selbst zu urteilen. Genau das ist die Absicht: Der Weg ist das Beweisstück.

Am Anfang stand nicht meine ClickUp-Beschreibung, sondern der unmittelbare Blick hinein. Der Assistent hatte über eine Werkzeuganbindung Zugriff auf meinen laufenden Arbeitsbereich und las ihn selbst – die Listen, die benutzerdefinierten Felder, die Statuswerte, die tatsächlichen Aufgaben. Das ist ein feiner, aber entscheidender Unterschied. Hätte ich die Struktur aus dem Gedächtnis geschildert, wäre eine geglättete, bereits gedeutete Fassung entstanden; so aber begann die Analyse an der Wirklichkeit, mitsamt ihren Unstimmigkeiten und dem, was ich selbst über Jahre hinweg nicht mehr bewusst wahrnahm.

Was folgte, war kein Diktat, sondern ein Gespräch. Ich traf die fachlichen Entscheidungen – was ein Thema ist, wie ein Status zu verstehen sei, was fortfallen darf –, während der Assistent vorschlug, ordnete, nachfragte und an einer Stelle sogar das Modell gegen meinen ersten Entwurf härtete. Die Rollen blieben dabei klar verteilt: Ich bin der Architekt, der weiß, was die Anwendung leisten soll; das Werkzeug ist der schnelle, unermüdliche Mitdenker, der Entwürfe liefert, Einwände formuliert und die Umsetzung übernimmt. Aus dieser Arbeitsteilung erwächst die Beschleunigung, die die eingangs genannte Rechnung kippen lässt.

Zur Ehrlichkeit gehört, dass dieser Vorgang nicht reibungslos verläuft. Der Assistent irrt, greift zuweilen vor, deutet etwas hinein, das ich so nicht wollte, und muss zurückgeholt werden. Doch gerade darin liegt die Bestätigung der Arbeitsteilung: Es braucht das menschliche Urteil, das die Richtung hält und den Fehltritt bemerkt. Das Werkzeug beschleunigt die Herstellung; es ersetzt nicht den, der weiß, wohin es gehen soll. Wie dieser Weg im Einzelnen verlief, davon handeln die folgenden Stationen – und die erste führt an den Ursprung des Ganzen: in den laufenden Arbeitsbereich selbst.

Die Bestandsaufnahme im laufenden System

Am Anfang stand, wie angekündigt, nicht meine Schilderung, sondern der unmittelbare Zugriff. Über die Werkzeuganbindung las der Assistent meinen laufenden Arbeitsbereich unmittelbar aus — nicht eine exportierte Kopie, nicht eine gedächtnisgestützte Nacherzählung, sondern das System, in dem die Arbeit tatsächlich lag. Dieser Umstand ist wichtiger, als er zunächst scheint. Wer sein eigenes Vorgehen aus dem Gedächtnis beschreibt, liefert bereits eine Deutung; die Unstimmigkeiten sind geglättet, die Gewohnheiten begradigt, das Gewachsene in eine Ordnung gebracht, die es so nie besaß. Der Blick in das laufende System hingegen zeigt den Bestand, wie er ist, samt allem, was sich über die Jahre unbemerkt eingeschlichen hatte.

Was sich zeigte, war zunächst von entwaffnender Schlichtheit: eine einzige Liste. Kein Geflecht aus Ordnern, keine Hierarchie aus Unterlisten, sondern eine flache Folge von Aufgaben, deren Namen einem strengen Muster gehorchten — ein Vorspann aus mit Bindestrichen getrennten Teilen, der die eigentliche Gliederung trug. Erst dieser Vorspann verriet, dass die scheinbar gleichrangigen Einträge in Wahrheit unterschiedliche Dinge bezeichneten und auf verschiedenen Ebenen lagen. Die Struktur war vorhanden, aber sie stand nicht im System, sondern in einer Namenskonvention, die ich mir selbst auferlegt hatte und im Laufe der Zeit halb vergessen hatte.

Aufschlussreicher als die Liste war die einzelne Aufgabe, sobald man sie vollständig aufklappte. Ein Name, eine Beschreibung, ein Statuswert, eine Reihe von Etiketten — und dazu benutzerdefinierte Felder, die ich einst angelegt hatte, um dem Werkzeug abzuringen, was es von sich aus nicht bot. Genau in dieser Fülle liegt der Wert der unmittelbaren Betrachtung: Sie führt vor Augen, wie viel fachliche Bedeutung sich in einem einzigen Datensatz staut, den das fremde Werkzeug als schmucklose Zeile darstellt. Was das Modell später an Ebenen und Dimensionen auseinanderlegen sollte, lag hier noch übereinander, in einem einzigen Eintrag zusammengefaltet.

Zur Ehrlichkeit dieses Vorgehens gehört eine Unstimmigkeit, die sich unterwegs zeigte. Der naheliegende Weg, die benutzerdefinierten Felder zu erheben, wäre der eigens dafür vorgesehene Aufruf gewesen — doch er lieferte für diesen Arbeitsbereich ein unbrauchbares Ergebnis, in dem die Pflichtangabe der Felder als leer zurückgemeldet wurde. Verlässlich war stattdessen der Umweg über eine einzelne, namentlich bekannte Aufgabe, die man in voller Ausführlichkeit abrief und aus der sich die Feldstruktur zweifelsfrei ablesen ließ. Das ist eine kleine Beobachtung, aber eine bezeichnende: Der unmittelbare Blick in das lebende System deckt nicht nur die eigene Struktur auf, sondern auch die Eigenheiten des Werkzeugs, mit dem man sie ausliest. Hätte ich mich auf die Beschreibung verlassen, wäre beides verborgen geblieben.

Am Ende dieser Bestandsaufnahme stand kein Entwurf, sondern ein geschärfter Blick auf das Vorhandene: eine flache Liste, die eine mehrstufige Gliederung verbarg, und Aufgaben, die weit mehr Bedeutung trugen, als ihre schlichte Erscheinung vermuten ließ. Ein Feld unter ihnen sollte sich als besonders verräterisch erweisen — jenes Statusfeld, in das über die Jahre hinweg zu viel hineingewandert war. Ihm gilt die folgende Station.

Analyse: das konfligierte Statusfeld

Der erste wirkliche Schritt war kein Entwurf, sondern eine Bestandsaufnahme – und sie begann dort, wo die Daten tatsächlich liegen. Der Assistent las meinen laufenden Arbeitsbereich selbst aus: eine einzige Liste, deren Aufgaben Namen wie „Blog – Navigation – Koppelnavigation“ tragen”. Was auf den ersten Blick wie eine schlichte Aufgabenliste aussieht, verbirgt bei näherem Hinsehen eine Struktur, die ClickUp nirgends offenlegt.

Denn ClickUp kennt nur eine Ebene: die Aufgabe. In ihr steckt alles zugleich. In Wahrheit gliedert sich meine Arbeit jedoch in vier Ebenen, die nur im Namenspräfix erkennbar sind. Das Thema – das „Issue” – ist die fachliche Klammer; darunter steht der Teil als sprachneutrale Folgeneinheit; zu ihm gehören die Sprachfassungen je Sprache; und erst die Veröffentlichung verbindet eine Fassung mit einem konkreten Ort. Diese vier Ebenen sind in der flachen Aufgabe zusammengepresst und müssen aus ihr rekonstruiert, nicht bloß übernommen werden.

Abbildung 1: Eine einzige ClickUp-Aufgabe gegenüber der vierstufigen Zielstruktur.

Die verräterischere Entdeckung betraf jedoch den Status. ClickUp führte ein einziges Statusfeld, und in dieses Feld hatte ich über die Jahre sechzehn Werte gedrängt: von backlog, planned, in progress und review über offered, accepted und in pipeline bis zu scheduled, draft published, published, update required, ready for billing, paid, on hold, skipped und closed. Für sich genommen ergibt jeder Wert Sinn. Nebeneinander in einem Feld jedoch beantworten sie ganz verschiedene Fragen – und genau das ist das Problem.

Denn in diesem einen Feld stecken drei voneinander unabhängige Fortschritte. Der erste ist der redaktionelle Arbeitsstand: Wie weit ist der Text gediehen? Der zweite ist die Herstellung der Veröffentlichung: Ist sie geplant, vorbereitet, erschienen? Der dritte ist die Akquise: Ist die Arbeit angefragt, angeboten, zugesagt? Diese drei Fragen sind orthogonal – sie stehen senkrecht zueinander und lassen sich unabhängig voneinander beantworten. Eine Zusage kann längst vorliegen, während der Text noch entsteht und die Veröffentlichung noch nicht terminiert ist. Zwängt man alle drei in ein einziges Feld, muss stets einer der Fortschritte die beiden anderen verdrängen.

Abbildung 2: Das eine konflierte Statusfeld, zerlegt in drei orthogonale Dimensionen.

Zwei Werte entlarven die Überladung besonders deutlich. paid ist gar kein Zustand des Inhalts, sondern eine Eigenschaft der Rechnung; es hatte sich nur mangels eigener Heimat ins Statusfeld verirrt. Und on hold ist überhaupt keine Phase, sondern ein Ruhen quer zu allen dreien – trägt man es ein, so überschreibt es den tatsächlichen Fortschritt und löscht ihn faktisch aus. Beide sind untrügliche Zeichen dafür, dass ein einzelnes Feld Lasten trug, die ihm nie zugedacht waren.

Die Folgen spürt man im Alltag. Wollte ich wissen, welche Teile im Lektorat stehen, mischten sich Herstellungs- und Vertriebszustände in die Antwort; ein sauberer Blick auf eine einzelne Frage war nicht möglich. Das fremde Werkzeug zeigte mir meinen Ablauf nicht, es verbarg ihn hinter einer einzigen, überfrachteten Spalte. Damit lag die eigentliche Aufgabe offen zutage: das eine Feld in drei klare, unabhängige Dimensionen zu zerlegen – und ebendiese Entflechtung ist Gegenstand der folgenden Stationen.

Übernehmen, zerlegen, verfeinern, streichen

Die Analyse hatte die Aufgabe benannt, jedoch nicht gelöst. Sechzehn Werte lagen in einem Feld, drei Fragen steckten darin verborgen, und zwischen dieser Einsicht und einem tragfähigen Modell stand die eigentliche Arbeit: jeden einzelnen Wert daraufhin zu befragen, wohin er gehört. Diese Arbeit ordnet sich in vier Handgriffe ein, und die Überschrift nennt sie in der Reihenfolge, in der sie sich aufdrängten — übernehmen, zerlegen, verfeinern, streichen.

Der erste Handgriff ist der leichteste, weil er nichts umstürzt. Ein Teil der Werte beschrieb tatsächlich das, was sie vorgaben zu beschreiben: den Fortschritt des redaktionellen Textes. backlog, in planning, in progress und review sind echte Arbeitszustände, und sie wandern unverändert in die redaktionelle Dimension, die im Modell als Aufzählung EditorialState von BACKLOG über IN_PLANNING, IN_PROGRESS und REVIEW bis zu den Endzuständen DONE, SKIPPED und CANCELLED reicht. Für diese Werte bedeutete die Migration keine Deutung, sondern schlichte Übernahme.

Der zweite Handgriff ist der eigentliche Kern des ganzen Vorhabens. Denn mehrere Werte, die im ClickUp-Feld neben den Arbeitszuständen einträchtig standen, beantworteten in Wahrheit eine ganz andere Frage. offered, accepted und in pipeline sagen nichts über den Text aus, sondern über die Akquise — darüber, ob eine Arbeit angefragt, angeboten oder zugesagt ist. Sie gehören auf eine eigene Achse, die im Modell als AcquisitionStatus mit den Werten REQUESTED, OFFERED, ACCEPTED und REVIEW bis zu ihren eigenen Endzuständen geführt wird. Ebenso verhält es sich mit scheduled und update required: Sie betreffen die Herstellung der Veröffentlichung, nicht den Text, und finden ihren Ort in der dritten Achse ProductionStatus mit PLANNED, PREPARED, PUBLISHED und UPDATE_NEEDED. Was in einem Feld zusammengepresst lag, wird hier auf drei senkrecht zueinander stehende Dimensionen verteilt — das ist die Zerlegung, von der die gesamte Argumentation ausgeht.

Der dritte Handgriff betrifft die Fälle, in denen ein einzelner Wert nicht einfach umzieht, sondern der Klärung bedarf. Am deutlichsten zeigt sich das an published. Dieser eine Wert trägt zwei Bedeutungen zugleich: Er meldet, dass der Text fertig ist, und dass die Veröffentlichung erschienen ist — eine redaktionelle und eine herstellerische Aussage in einem Wort. In der Zerlegung muss er sich teilen, und die redaktionelle Seite fällt folgerichtig auf „DONE“. Zur Verfeinerung gehört auch die Vereinheitlichung der über die Jahre gewachsenen Schreibweisen: Ob ich to do, todo oder planning notiert hatte, ob cancelled oder canceled — die Abbildung führt diese Varianten auf je einen kanonischen Zielzustand zurück und macht die Sammlung damit erst auswertbar. Und weil kein Entwurf die Wirklichkeit vollständig vorhersieht, mündet jeder nicht vorgesehene Wert nicht ins Nichts, sondern vorsichtig in den Anfangszustand BACKLOG, von wo aus er sichtbar bleibt und von Hand richtiggestellt werden kann.

Wie eng diese Verfeinerung am Code liegt, zeigt jene Fallunterscheidung, mit der der Import später jeden Quellwert auf die redaktionelle Achse abbildet:

/** Distributes the conflated ClickUp status onto the editorial state. */
public static EditorialState mapStatus(String clickup) {
  String s = clickup == null ? "" : clickup.toLowerCase().strip();
  return switch (s) {
    case "in progress", "in-progress", "progress" -> EditorialState.IN_PROGRESS;
    case "review", "in review" -> EditorialState.REVIEW;
    case "planning", "in planning", "planned", "to do", "todo" -> EditorialState.IN_PLANNING;
    case "done", "complete", "completed", "closed", "published" -> EditorialState.DONE;
    case "skipped" -> EditorialState.SKIPPED;
    case "cancelled", "canceled" -> EditorialState.CANCELLED;
    default -> EditorialState.BACKLOG;
  };
}

Die in jedem Zweig zusammengeführten Schreibweisen sind die Verfeinerung, der letzte Zweig der vorsichtige Rückfall in den Rückstand. Zugleich benennt der Auszug seine eigene Grenze: Er kennt allein die redaktionelle Achse. Weshalb die beiden anderen Dimensionen aus der Quelle leer bleiben müssen, ist Gegenstand des zweiten Teils.

Der vierte Handgriff schließlich entfernt, der auf keiner der drei Achsen etwas zu suchen hat. paid ist kein Zustand eines Inhalts, sondern eine Eigenschaft der Rechnung; es hatte sich nur mangels einer eigenen Heimat in das Statusfeld verirrt und verlässt die inhaltlichen Dimensionen ersatzlos. ready for billing teilt dieses Schicksal, denn auch die Abrechnungsbereitschaft ist eine Frage der Rechnung, nicht des Textes. Und on hold, der zweite verräterische Wert aus der Analyse, ist überhaupt keine Phase, sondern ein Ruhen quer zu allen dreien; trüge man es als Statuswert ein, so überschriebe es den tatsächlichen Fortschritt und löschte ihn faktisch aus. Es wird deshalb aus der Statusachse gestrichen, statt sie zu verfälschen.

Am Ende dieser vier Handgriffe steht eine Abbildungstabelle: links die gewachsenen Werte der Quelle, rechts ihr Ort in einer der drei Dimensionen — oder die begründete Feststellung, dass sie dort keinen Ort haben. Diese Tabelle ist mehr als nur eine Übersicht. Sie ist die Brücke zwischen der Analyse und dem Code, und sie kehrt in zwei Gestalten wieder: als gehärtetes Modell in der folgenden Station und als ausführbare Fallunterscheidung im Import des zweiten Teils – wobei der Import nur ihre redaktionelle Spalte ausführt; warum die beiden anderen Dimensionen leer beginnen, zeigt der zweite Teil. Aus der Beobachtung ist damit eine Vorschrift geworden, die sich prüfen lässt.

Abbildung 1: Wie die sechzehn ClickUp-Statuswerte durch die vier Handgriffe aufgelöst wurden – Übernahme auf die redaktionelle Dimension, Zerlegung auf Akquise und Herstellung, die geteilte Verfeinerung von „published” sowie die Streichungen von der Statusachse.

Modellhärtung: das Werkzeug denkt mit

Die Analyse endete mit einer klaren Aufgabe: das eine, überladene Feld in drei unabhängige Dimensionen zu zerlegen. Doch eine Zerlegung allein genügt nicht – es kommt darauf an, wie man sie im Code verankert, damit sie hält. Genau an dieser Stelle geschah etwas, das ich als das eigentliche Wesen der KI-gestützten Arbeit empfinde: Aus einer beiläufigen Beobachtung erwuchs eine strukturelle Verbesserung, die ich in meinem ersten Entwurf gar nicht vorgesehen hatte.

Ausgangspunkt war eine schlichte Sorge, die ich im Gespräch äußerte. Wenn jede der drei Dimensionen ihre Geschichte als Liste von Statuswechseln führt, dann liegt diese Liste offen – und eine offene Liste lässt sich versehentlich umsortieren, an falscher Stelle ergänzen oder um Einträge erweitern. Für einen Prüfpfad, der beweisen soll, in welcher Reihenfolge sich ein Zustand entwickelt hat, ist das eine gefährliche Schwäche. Was als kleine Anmerkung begann, führte zu einer Härtung, die auf zwei Säulen ruht.

Die erste Säule macht die Reihenfolge unabhängig von der Listenposition. Jeder Statuswechsel trägt eine streng aufsteigende Folgenummer, und nicht der Zeitstempel entscheidet über die Ordnung, sondern die Folgenummer. Der Zeitstempel bleibt beschreibend – denn Uhren springen, und zwei Ereignisse könnten denselben Augenblick tragen.

public record StatusChange<S extends Enum<S>>(
    long sequence, S from, S to, String actor, Instant timestamp) {
  public StatusChange {
    if (sequence < 0) {
      throw new IllegalArgumentException("sequence must be >= 0, was " + sequence);
    }
    Objects.requireNonNull(from, "from");
    Objects.requireNonNull(to, "to");
    Objects.requireNonNull(timestamp, "timestamp");
  }
}

Dass dieses Ereignis ein Record ist, wirkt beiläufig, trägt aber die gesamte Zusicherung: Ein Record ist unveränderlich, sodass ein einmal festgehaltener Wechsel nachträglich nicht mehr zu verändern ist – weder die Folgenummer noch den Ausgangs- oder Zielzustand. Die Fälschungssicherheit der Historie ruht damit nicht auf Vereinbarungen, sondern auf einer Eigenschaft der Sprache. Die Anwendung übersetzt gegen Java 26 und darf solche Mittel selbstverständlich nutzen.

Die zweite Säule verhindert das Umsortieren von vornherein, indem sie die Liste gar nicht erst preisgibt. An ihre Stelle tritt ein generischer Verlauf, der die Ereignisse privat hält und nach außen nur zwei Fähigkeiten kennt: das lesende events(), das eine unveränderliche Sicht zurückgibt, und das anhängende record. Ein Einfügen, Entfernen oder Umsortieren gibt es nicht.

public final class StatusHistory<S extends Enum<S>> {
  private final S initial;
  private final List<StatusChange<S>> events = new ArrayList<>();
  public StatusHistory(S initial) {
    this.initial = Objects.requireNonNull(initial, "initial");
  }
  public S current() {
    return events.isEmpty() ? initial : events.get(events.size() - 1).to();
  }
  public void record(S to, String actor) {
    record(to, actor, Instant.now());
  }
  public void record(S to, String actor, Instant timestamp) {
    Objects.requireNonNull(to, "to");
    Objects.requireNonNull(timestamp, "timestamp");
    S from = current();
    events.add(new StatusChange<>(events.size(), from, to, actor, timestamp));
  }
  public List<StatusChange<S>> events() {
    return List.copyOf(events);
  }
}

An diesem kleinen Typen steckt die ganze Pointe. Weil record den Ausgangswert from selbst aus dem aktuellen Zustand ableitet und die nächste Folgenummer vergibt, lässt sich weder ein unstimmiger Übergang einschleusen noch die Reihenfolge verfälschen. Und weil current() den aktuellen Zustand stets als Ziel des letzten Ereignisses liest, statt ihn getrennt zu speichern, kann er niemals von der Historie abweichen. Was zuvor eine Frage der Disziplin war – die Liste bitte nicht umsortieren –, ist nun eine Eigenschaft des Typs. Diese Zusicherungen sind kein bloßes Versprechen des Fließtextes; das Modell wird von Tests wie StatusHistoryTest und StatusChangeTest begleitet, die genau sie festnageln.

Der eigentliche Gewinn zeigt sich, wenn derselbe Verlauf in allen drei Dimensionen gilt. Die Dimensionen selbst sind schlichte Aufzählungen – die Herstellung etwa kennt vier Werte:

public enum ProductionStatus { PLANNED, PREPARED, PUBLISHED, UPDATE_NEEDED }

Der redaktionelle Arbeitszustand und die Akquise tragen je sieben Werte samt einem isTerminal(), das ihre Endzustände DONE, SKIPPED und CANCELLED kenntlich macht. Am Part hängt allein der Arbeitszustand; die Publication hingegen führt zwei orthogonale Lebenszyklen zugleich – Herstellung und Akquise –, jeden mit seinem eigenen, gehärteten Verlauf. Genau hier wird die Entflechtung aus der Analyse zu ausführbarem Code:

public Publication(UUID id, LanguageVersion version, PublicationPlace place) {
  this.id = Objects.requireNonNull(id, "id");
  this.version = Objects.requireNonNull(version, "version");
  this.place = checkLanguageRule(version, Objects.requireNonNull(place, "place"));
  this.production  = new StatusHistory<>(ProductionStatus.PLANNED);
  this.acquisition = new StatusHistory<>(AcquisitionStatus.REQUESTED);
}
public void changeProductionStatus(ProductionStatus to, String actor) {
  production.record(to, actor);
}
public void changeAcquisitionStatus(AcquisitionStatus to, String actor) {
  acquisition.record(to, actor);
}

Zwei unabhängige Verläufe an einem einzigen Objekt – das ist die Zerlegung, die im konflierten ClickUp-Feld unmöglich war, hier aber als bloße Selbstverständlichkeit erscheint. Nebenbei wacht der Konstruktor über die Sprachregel: Eine Veröffentlichung darf nur auf einen Ort zeigen, dessen unterstützte Sprachen die Sprache der Fassung einschließen; andernfalls entsteht sie gar nicht erst.

Rückblickend ist dieser Schritt für mich der überzeugendste Beleg der eingangs formulierten These. Nicht, weil eine Maschine das Modell erdacht hätte – die fachliche Richtung blieb meine –, sondern weil ein aufmerksamer Mitdenker eine beiläufige Sorge aufgriff und in eine Konstruktion übersetzte, die das Modell an einer echten Schwachstelle härtet. Das ist es, was „das Werkzeug denkt mit” konkret bedeutet. Wie aus diesem gehärteten Modell ein zusammenhängendes Konzept und daraus die Oberfläche erwachsen, zeigen die folgenden Stationen.

Wo die Daten liegen: der Objektgraph

Ein Wort zur Persistenz, die neben Modell und Oberfläche unscheinbar wirkt, für das Anliegen des Ganzen jedoch wesentlich ist. Die Daten liegen nicht in einer relationalen Datenbank, sondern als Objektgraph, den EclipseStore in Dateien auf der eigenen Platte speichert. Damit entfällt die gesamte Zwischenschicht, die sonst zwischen Objekten und Tabellen vermittelt: kein Schema, keine Abbildung, keine Wanderungsskripte, kein Datenbankserver, der gepflegt und gesichert werden will. Bei einem Bestand dieser Größenordnung – ein einzelner Benutzer, einige hundert Themen – ist das schlicht die schlankere Lösung. Vor allem aber macht es die Datenhoheit greifbar: Was mir gehört, liegt als Verzeichnis auf meiner Platte, kopierbar, sicherbar, mitnehmbar. Ein Objektspeicher bindet mich freilich enger an Java, als es ein offenes Tabellenformat täte; für eine Anwendung, die ohnehin ganz aus Java besteht, halte ich diesen Preis für angemessen.

Vom Modell zum Konzept

Ein gehärtetes Datenmodell ist noch keine Anwendung. Zwischen den Klassen und der Oberfläche liegen zwei Schritte, die man leicht überspringt und dann teuer bezahlt: die Verdichtung des Modells zu einem zusammenhängenden Konzept und die Ableitung der Bildschirmmasken aus den Arbeitsprozessen. Beide Schritte entstanden in derselben Weise wie alles Bisherige – im Gespräch, mit klarer Rollenverteilung.

Das Fachkonzept fasst zusammen, was das Modell nur in Typen ausdrückt: die vier Inhaltsebenen und dass sie aus der flachen ClickUp-Aufgabe rekonstruiert werden müssen; die drei Statusdimensionen und ihre Träger; die Historie als beweisende Kette; die Sprachregel, wonach eine Veröffentlichung nur auf einen Ort zielen darf, dessen Sprachen die der Fassung einschließen. Vor allem aber hält es fest, was nicht dazugehört. Die gesamte kaufmännische Welt – Rechtsträger, Vergütungsmodelle, Rechnungen, Forecast – blieb bewusst außen vor. Dieser Verzicht war kein Sparen an der falschen Stelle, sondern eine Methode: Ein Modell, das alles zugleich abbilden will, wird nie fertig, und für den ersten lauffähigen Stand zählt der Kern.

Der Prozesskatalog: Views folgen aus Prozessen

Der zweite Schritt war der wichtigere, und er ist derjenige, den ich künftig nie wieder auslassen werde. Es wäre naheliegend gewesen, die Masken unmittelbar aus dem Datenmodell abzuleiten – zu jeder Klasse ein Formular, zu jeder Liste eine Tabelle. Das Ergebnis solcher Ableitungen kennt jeder: Oberflächen, die alles anzeigen und nichts unterstützen. Stattdessen entstand zunächst ein Katalog der Arbeitsprozesse: zwanzig Vorgänge mit festen Kennungen, von „Thema erfassen” über „Arbeitszustand fortschreiben” und „Akquise führen” bis zu den drei Schritten des Imports. Jeder Eintrag benennt seinen Zweck, die betroffene Ebene und den ausgelösten Zustandsübergang.

Erst aus diesen Prozessen wurden die Views abgeleitet, und zwar so, dass jede Maske ausdrücklich vermerkt, welche Prozesse sie bedient. Diese Rückbindung klingt nach Bürokratie, erweist sich aber als das schärfste Werkzeug gegen überflüssige Oberflächen: Eine Maske, die sich auf keinen Prozess berufen kann, ist entbehrlich; ein Prozess ohne Maske ist eine Lücke. Sieben Views blieben übrig, dazu die Frage, wie sie zueinander stehen.

Der Entwurf: sieben Ansichten und ihre Grenzen

An dieser Stelle trat das Werkzeug erstmals mit einer Einschränkung hervor, statt mit einer Möglichkeit – und das ist berichtenswert. Vaadin bringt einen reichen Bestand an Bausteinen mit, und für die meisten Masken war die Zuordnung einfach: Tabellen, Formularlayouts, Auswahlfelder, Reiter, Datumswähler. Für zwei Ansichten gibt es jedoch keinen Fertigbaustein. Die Redaktionstafel mit ihren Spalten je Arbeitszustand ist kein Standardbestandteil, ebenso wenig eine Zeitleiste für die Verlaufsansicht. Beide mussten aus Layouts, Ziehen-und-Ablegen und eigenen Karten zusammengesetzt werden. Es hätte nahegelegen, das zu verschweigen; aber eine Entwurfsplanung, die solche Stellen nicht ausweist, verschiebt den Aufwand bloß in die Umsetzung, wo er unangenehmer auffällt.

Aus den View-Briefs entstand schließlich ein interaktiver Entwurf. Ich übergab die vier Dokumente – Typskizzen, Konzept, Prozesskatalog und View-Briefs – an ein Entwurfswerkzeug mit der schlichten Aufforderung, daraus einen ersten Entwurf einer eleganten, für den täglichen Einsatz gedachten Arbeitsumgebung zu erstellen. Was zurückkam, überraschte mich in einem Punkt. Der Entwurf hatte nicht nur die Masken gezeichnet, sondern die Logik dahinter verstanden: Die Verlaufsansicht trug den Hinweis, dass die Ordnung auf der Folgenummer ruhe und nicht auf dem Zeitstempel, und die Herstellungsdimension kam ohne die Endzustände der anderen aus, dafür mit einem eigens beschrifteten Rücksprung.

Abbildung 1: Die Veröffentlichungssicht – Akquise und Herstellung als zwei gleichwertige Spalten.

Diese eine Maske ist der Höhepunkt des ganzen Weges. Links die Akquise, rechts die Herstellung, gleich groß, gleich gewichtig, jede mit ihrem eigenen aktuellen Zustand und ihrem eigenen Verlauf. Was im ClickUp-Feld unmöglich war, steht hier als Selbstverständlichkeit da: Eine Zusage ist erteilt, während die Herstellung noch läuft – zwei Wahrheiten nebeneinander, ohne dass eine die andere verdrängt. Man muss die Entflechtung nicht erklären; man sieht sie.

Abbildung 2: Der Themen-Arbeitsplatz – ein Thema mit seinen Teilen, Tags und dem Vermerk zur ClickUp-Herkunft.

Abbildung 3: Die Verlaufsansicht – die anhängende Kette, sortiert nach Folgenummer, mit Akteur und Zeitpunkt.

Ein Wort zur Ehrlichkeit über die Grenze dieses Schritts hinaus. Der Entwurf ist ein freies Frontend-Mock, kein Vaadin-Code; das Entwurfswerkzeug kennt Vaadins Komponentenbestand nicht und kann ihn auch nicht abfragen. Zwischen Entwurf und Umsetzung verläuft daher eine Naht, die niemand wegreden sollte – die View-Briefs mit ihrer Zuordnung zu konkreten Bausteinen sind genau der Faden, mit dem sie später vernäht wird. Wie diese Naht tatsächlich geschlossen wurde und was dabei zutage trat, ist Gegenstand des zweiten Teils.

Hält das der Wirklichkeit stand?

Am Ende dieses ersten Teils steht ein Modell, und Modelle haben die unangenehme Eigenschaft, in der Betrachtung stets besser auszusehen als im Gebrauch. Bevor die erste Zeile der Anwendung entsteht, ist deshalb die Frage angebracht, der diese Station den Namen gibt. Drei Einwände halte ich für die ernsthaftesten, und keiner von ihnen lässt sich durch weiteres Nachdenken erledigen.

Der erste betrifft die Grundlage. Das gesamte Modell ist aus einem einzigen Arbeitsbereich hergeleitet, dem eines einzigen Autors mit seinen gewachsenen Gewohnheiten. Was sich dabei als drei orthogonale Dimensionen zeigte, könnte ebenso gut die Eigenart eines Einzelfalls sein. Ich behaupte nicht, eine allgemeine Ordnung des Publikationswesens gefunden zu haben; ich behaupte, für diesen Bestand eine tragfähige gefunden zu haben. Der Unterschied ist erheblich, und wer denselben Weg beschreitet, sollte bei der eigenen Bestandsaufnahme beginnen und nicht bei meiner.

Der zweite Einwand richtet sich gegen die Entflechtung selbst. Dass Akquise, redaktionelle Arbeit und Herstellung voneinander unabhängig sind, ist eine Behauptung über die Wirklichkeit, keine Ableitung aus ihr. Sie stützt sich auf die Beobachtung, dass in der Quelle Werte nebeneinander lagen, die einander nie ausschlossen. Doch ob sich die drei Achsen im täglichen Gebrauch tatsächlich als unabhängig erweisen — oder ob sich zwischen ihnen doch eine stillschweigende Kopplung einstellt, die dann wiederum in Bemerkungsfeldern und Etiketten Zuflucht sucht —, entscheidet keine Modellierung, sondern allein die Benutzung.

Der dritte Einwand ist der unangenehmste, weil er bereits feststeht. Die beiden neu gewonnenen Dimensionen kommen aus der Migration leer. Was nie erhoben wurde, lässt sich nicht rückwirkend füllen, und so beginnt der übernommene Bestand in Anfangszuständen, die nichts über seine Vergangenheit aussagen. Der Gewinn der Entflechtung liegt vollständig in der Zukunft. Ein solches Versprechen kann sich einlösen oder verpuffen, und wie es ausgegangen ist, gehört in die Bilanz und nicht in den Entwurf.

Bleibt festzuhalten, was aus dieser Prüfung folgt. Ein Modell, das sich nur gegen sich selbst behaupten muss, gewinnt immer. Die einzige Prüfung, die zählt, besteht darin, den wirklichen Bestand hindurchzuschicken, die Ansichten zu bedienen und zu beobachten, an welcher Stelle die Ordnung reißt. Genau das ist der Gegenstand des zweiten Teils — nicht als Beweis eines gelungenen Entwurfs, sondern als dessen erste ernsthafte Belastung.

Total
0
Shares
Previous Post

LAngChain4j – Teil 2

Next Post

Von ClickUp zu Vaadin – Teil II

Related Posts