Logo Canva Austria GmbH.

Canva Austria GmbH.

Startup

Visual AI Made Simple

Description

René Koller von kaleido erläutert in seinem devjobs.at TechTalk, wie das Devteam das Konzept von MVC auf MVVC erweitert hat und wie es zum Einsatz kommt.

Beim Videoaufruf stimmst Du der Datenübermittlung an YouTube und der Datenschutzerklärung zu.

Video Zusammenfassung

In Visual AI Made Simple zeigt René Koller, wie Service Objects nach dem Command-Pattern Business-Logik in Rails klar strukturieren und typische MVC-Probleme wie fette Models, überladene Controller und Logik in Views vermeiden. Anhand eines Abo-Workflows mit mehreren Payment-Prozessoren demonstriert er einen Dienst mit Initializer, einer einzigen öffentlichen call-Methode, privaten Schritten (validate/subscribe/notify) und einem einheitlichen Response-Objekt für Fehlerbehandlung, Dependency Injection, Zustandslosigkeit und Idempotenz. So entstehen schlankere Controller und schnellere, verlässlichere Tests, weil die Logik als Einheit getestet und auf sinnvolle Ergebnisobjekte asserten werden kann statt auf fragile, request-getriebene Integrationstests.

Visual AI Made Simple: Mit Service Objects und Command Pattern zu klarer Business-Logik im MVC

Kontext: Was wir aus „Visual AI Made Simple“ von René Koller (Canva Austria GmbH.) mitgenommen haben

In der Session „Visual AI Made Simple“ erläutert René Koller (Canva Austria GmbH.) eine sehr praxisnahe Antwort auf eine immer gleiche Herausforderung: Wohin mit Business-Logik, wenn klassische MVC-Strukturen an ihre Grenzen kommen? Koller arbeitet als Lead Developer bei einer visuellen AI-Einheit in Wien, deren Mission es ist, visuelle AI einfach nutzbar zu machen. Im Vortrag nennt er drei Services als aktuellen Kontext: „RoofBG“ für das Entfernen von Hintergründen in Bildern, „Unscreen“ für Videos und „Designify“ als nächsten Schritt zur automatisierten Design-Erstellung mit mehreren AIs. Das Team setzt stark auf Rails – und genau aus dieser Alltagspraxis leitet er eine klare Architekturentscheidung ab: Service Objects, speziell im Command Pattern, schaffen Ordnung in komplexer Business-Logik.

Aus Sicht der DevJobs.at-Redaktion ist diese Session ein Handbuch für saubere Anwendungslogik: klar in den Zielen, konkret im Vorgehen und mit einem Testfokus, der unmittelbar überzeugt. Der rote Faden: Modelle sollen Daten halten, Views präsentieren, Controller koordinieren – aber Geschäftsprozesse, die mehrere Domänenobjekte berühren, gehören in dedizierte Services.

Das Ausgangsproblem: Wenn MVC groß wird

Koller beginnt mit dem bekannten Dreiklang Modell–View–Controller. Für kleine Apps funktioniert er hervorragend. Doch wenn Anwendungen wachsen, kippt die Balance – und eines von drei Symptomen tritt ein:

  • Fett gewordene Modelle: Sie verletzen das Single-Responsibility-Prinzip. Fachlogik und Fremdobjekte wandern „mal eben“ ins Modell.
  • Überladene Controller: Statt Requests zu koordinieren, übernehmen sie umfangreiche Workflows und Fehlerfälle.
  • Logik in den Views: Präsentationsbezogene Logik dringt in Templates ein; häufig führt das zur Einführung von View Models.

View Models sind bereits eine Form von Service Object. Sie lösen die Darstellungslogik, aber nicht die zentrale Frage: Wo platzieren wir Business-Logik, die über mehrere Domänenobjekte hinweg agiert? Kollers Antwort: Service Objects – konkret das Command Pattern.

Service Objects im Überblick: Fokus auf das Command Pattern

Es gibt eine Reihe von Service-Objekttypen – Adapter, Commands, Decorators, Query Objects, View Models, Presenter, Form Objects. Koller konzentriert sich auf das Command Pattern, das er so charakterisiert:

Ein Command repräsentiert und führt einen geschäftlichen Prozess aus, der spezifisch für die Anwendung ist.

Der Aufbau ist bewusst minimalistisch und eindeutig:

  • Ein Initializer/Konstruktor, der die benötigten Parameter oder Objekte entgegennimmt.
  • Eine öffentliche Methode – oft „call“ –, die der einzige Einstiegspunkt ist.
  • Eine private Schnittstelle mit den eigentlichen Arbeitsschritten.

Diese klare Form zwingt zur Explizitheit: Ein Command beschreibt, was getan wird, und kapselt, wie es getan wird. So wird die Business-Logik zur eigenständigen, testbaren Einheit.

Fallbeispiel: Eine Subscribe-Action unter dem Messer

Um das Vorgehen greifbar zu machen, skizziert Koller eine typische Controller-Methode – das Abonnieren eines Plans durch einen Nutzer. Der Ablauf im Ist-Zustand liest sich vertraut:

  • Plan laden.
  • Basale Checks (Plan verfügbar? Nutzer schon abonniert?).
  • Komplexere Checks, eventuell ausgelagert (darf der Nutzer diesen Plan abonnieren?).
  • Subskription schreiben und Zahlungsdienst aktualisieren – mit unterschiedlichen Providern je nach Region und Zahlungsmittel.
  • Fehlerfälle behandeln (Exceptions abfangen) und Fehlermeldung zurückgeben.
  • Bei Erfolg Benachrichtigungen auslösen.
  • Response rendern.

Koller nennt hierzu sehr konkrete Schwachstellen:

  • Unterschiedliche Fehlerbehandlungsmuster (frühe Returns, lange if-Statements, verstreute Rescue-Blöcke).
  • Mehrere Exit-Points – der Controller kehrt an verschiedenen Stellen zurück.
  • Tiefe Verschachtelung (er berichtet von fünf Ebenen) und „lange Sprünge“ im Kontrollfluss, die das Lesen erschweren.

Für die DevJobs.at-Redaktion ist das die Diagnose für einen klassischen Refaktor-Trigger: zu viele Verantwortlichkeiten an einem Ort, fehlende Struktur, mangelnde Testbarkeit.

Zielbild: Ein schlanker Controller, ein Service, ein Ausstieg

Koller setzt klare Ziele für den Refaktor:

  • Lesbarkeit verbessern.
  • Struktur der Logik klären.
  • Verständnis und Nachvollziehbarkeit erhöhen.
  • Tests vereinfachen und beschleunigen.

Das Idealbild ist ebenso klar: Der Controller sucht Plan und Payment-Processor, ruft genau ein Service Object auf und rendert dessen Ergebnis. Ergebnis: ein einzelner Exit-Point, keine tiefen Verschachtelungen, ein konsistenter Ablauf.

Einheitlicher Rückgabekanal: das Service-Response-Objekt

Koller führt für alle Services ein gemeinsames Response-Objekt ein. Wichtig sind zwei Eigenschaften:

  • Services können dem Result Fehler hinzufügen.
  • Ein Service gilt als gescheitert, sobald mindestens ein Fehler darin liegt.

So entsteht ein einheitliches Protokoll nach außen: Der Controller muss nur noch auf Erfolg oder Fehler prüfen und gegebenenfalls Fehlerdetails anzeigen. Intern behält das Service die volle Kontrolle über die Fehlerentstehung.

Der Command im Detail: validate, subscribe, notify

Das gezeigte Service Object erhält im Konstruktor die benötigten Objekte – Plan, Nutzer, Payment-Processor. Nach außen gibt es genau eine öffentliche Methode: call. Diese orchestriert die Schritte:

  • validate: Fachliche und technische Vorbedingungen prüfen.
  • subscribe: Subscription im eigenen System herstellen und Payment-Provider aktualisieren.
  • notify: Benachrichtigungen versenden, falls alles erfolgreich war.
  • return result: Einheitlich das Response-Objekt zurückgeben.

Wesentlich ist die private Struktur der drei Schritte:

validate: Nur prüfen, konsistent Fehler hinzufügen

  • Frühzeitiger Ausstieg, wenn bereits zuvor ein Fehler vorliegt. So verhindert man Kaskadenarbeit auf fehlerhaften Zuständen.
  • Prüfen, ob der Plan verfügbar ist und ob der Nutzer den Plan abonnieren darf.
  • In jedem negativen Fall: Fehler an das Result anhängen – immer auf demselben Kanal.

subscribe: Persistenz und externe Systeme – samt Exception-Handling

  • Nur ausführen, wenn noch kein Fehler im Result steht.
  • Interne Subscription speichern und den Payment-Processor aktualisieren – z. B. je nach Region oder Zahlungsmittel andere Provider.
  • Exceptions werden innerhalb des Service abgefangen; der Service übersetzt sie in einen Fehler am Result. Ergebnis: Es gibt genau einen Weg, wie das Service nach außen „fehlschlägt“.

notify: Konsequenz erst bei Erfolg

  • Bei Fehlern sofort zurückkehren.
  • Benachrichtigungen nur senden, wenn das Result erfolgreich ist.

Diese Architektur schafft eine „Single Source of Truth“ für Erfolg und Misserfolg: das Response-Objekt. Das macht die Ansteuerung im Controller trivial und die Tests klar und fokussiert.

Testen vorher: Schwergewichtig, fragil, langsam

Koller kontrastiert die Tests vor dem Refaktor mit denen danach. Im alten Setup testet man die Controller-Action. Was das bedeutet:

  • Man braucht stets eine vollständige HTTP-Request-Kette, inklusive Authentifizierung und Login.
  • Man muss Pfade besuchen, Optionen wählen, Buttons klicken – auch wenn der eigentliche Testfall nur die Reaktion auf einen Zahlungsfehler ist.
  • Man ist eng an das Markup gekoppelt (z. B. Button „Subscribe“), obwohl man fachlich die Update-Logik prüfen will.
  • Fehlerzustände müssen mit Spies/Doubles und aufwendigen Mocks erzwungen werden.
  • Die Tests sind langsam, weil sie die gesamte MVC-Kette durchlaufen (Koller betont die Langsamkeit explizit).

Kurz: High-Level-Integrationstests für alle Fälle, selbst wenn man eigentlich nur einen klar abgrenzbaren Geschäftsprozess prüfen möchte.

Testen nachher: Präzise, schnell, entkoppelt

Mit Service Objects ändert sich das grundlegend:

  • Man kann den Payment-Processor sehr einfach „überfahren“, etwa durch eine minimal abgeleitete Klasse, die gezielt eine Exception wirft.
  • Das Setup beschränkt sich auf die Objekte, die der Service wirklich braucht: Nutzer, Pläne, Payment-Processor.
  • Der Test ruft den Service direkt auf und prüft nur, dass das Result ein Fehler ist – genau das, was fachlich zählt.

Koller unterstreicht, wie viel leichter sich Fehler erzwingen lassen, wie deutlich die größere Lesbarkeit ist und wie sehr die Tests beschleunigt werden. Aus unserer Sicht ist das der stärkste Hebel dieses Ansatzes: Fachlogik wird in sich testbar, ohne UI- und Controller-Rauschen.

Konventionen für robuste Service Objects

Zum Abschluss des technischen Teils formuliert Koller klare Regeln für gutes Service-Design:

  • Genau eine öffentliche Methode: call (Alternativen wie execute/process sind möglich, aber nur eine!).
  • Keine Eigeninstanziierung von Abhängigkeiten: Alles kommt über Dependency Injection herein.
  • Operiere auf Objekten, nicht auf IDs: Services sollen nicht an einen konkreten ORM gekoppelt sein.
  • Gib etwas Sinnvolles zurück: Boolesche Werte können genügen, besser ist ein Result-Objekt mit Fehlern und Kontext.
  • Exceptions im Service behandeln: Der Controller bleibt frei von Rescue-Logik.
  • Keine persistente Zustandsführung im Service: Möglichst funktional halten.
  • Idempotenz, wenn möglich: Wiederholte Aufrufe dürfen den Zustand nicht kaputt machen. Beispiel: Nach Zahlungsfehler sollte ein erneuter Aufruf sauber funktionieren, ohne einen halb kaputten Zustand zu hinterlassen.
  • Regeln kennen, um sie bewusst zu brechen: Erst lange befolgen, dann gezielt anpassen, wenn die Praxis es verlangt.

Diese Leitlinien ergeben ein konsistentes, robustes Architekturpattern, das sich nahtlos in Rails-Apps einfügt, aber nicht an Rails gebunden ist.

Warum dieser Ansatz wirkt: Drei Effekte

Auch wenn Koller das nicht als Schlagwort-Dreiklang formuliert, zeigen seine Beispiele drei starke Effekte:

1) Lesbarkeit. Der Controller beschreibt den Prozess in einer Zeile – Plan besorgen, Payment-Processor ermitteln, Service.call, Result rendern. Die Business-Logik ist in verständliche, kleine Schritte zerlegt: validate, subscribe, notify.

2) Struktur. Es gibt einen einzigen Rückgabekanal (Result), eine einzige öffentliche Methode (call) und nur noch eine Fehlerübersetzung (Exceptions werden intern in Result-Fehler überführt). Die Reduktion von Exit-Points und die Eliminierung tiefer Verschachtelung machen Flüsse nachvollziehbar.

3) Testbarkeit. Services lassen sich isoliert testen, ohne Login-, Routing- oder Markup-Abhängigkeiten. Fehlerzustände sind gezielt und einfach erzwingbar. Tests werden klarer und schneller.

Diese drei Effekte decken sich exakt mit den Zielen, die Koller für den Refactor setzt – und die gezeigten Vorher/Nachher-Szenen machen sie greifbar.

Ein Wort zur Realität: Payment-Integrationen ohne Kopfschmerzen

Ein besonderer Pluspunkt: Der Subscribe-Use-Case beinhaltet externe Systeme (Payment-Provider). Koller erwähnt unterschiedliche Provider pro Region/Zahlungsart und unterstreicht, dass Ausnahmen immer möglich sind. Gerade hier hilft das Muster doppelt:

  • Durch Dependency Injection kommt jeder Payment-Processor als Objekt in den Service; er lässt sich in Tests leicht ersetzen.
  • Exceptions aus dem Provider werden im Service gefangen und einheitlich als Fehler am Result abgebildet. Der Controller bleibt frei von Ausnahmebehandlung.

Damit lässt sich Komplexität an einer Stelle bündeln, statt sie im Controller oder gar im Modell zu verteilen.

Von der Controller-Action zur Domänenoperation

Der vielleicht wichtigste Perspektivwechsel: Ein Command beschreibt eine Domänenoperation – nicht die Reaktion auf einen HTTP-Request. Die zentrale Botschaft aus Kollers Aufbau:

  • Controller: orchestriert I/O (HTTP rein, HTTP raus).
  • Service/Command: führt die Business-Operation aus (validieren, persistieren, externe Systeme informieren).

Dieser Schnitt sorgt für eine nachhaltige Trennung von Belangen: Transport vs. Fachlogik. Sobald man das verinnerlicht, verschwinden viele Controller-Anti-Patterns fast automatisch.

Was wir besonders einprägsam fanden

Einige Aussagen bzw. Paraphrasen aus der Session, die den Kern treffen:

  • „Ein Command repräsentiert und führt einen geschäftlichen Prozess aus, der spezifisch für die Anwendung ist.“
  • Nur ein Weg, das Service scheitern zu lassen: Fehler ins Result schreiben. Keine seitlichen Ausnahmen oder externe Rückgabekanäle.
  • Früher Ausstieg in jedem Schritt, sobald das Result Fehler enthält – das verhindert unnötige Arbeit und Folgefehler.
  • Services sollen keine Zustände halten und möglichst idempotent sein.

Diese Punkte sind klein, aber in Summe mächtig. Sie machen aus „nur ein weiteres Objekt“ eine verlässliche Einheit.

Checkliste: Wann lohnt sich ein Service Object?

Auf Basis von Kollers Beispiel lässt sich eine pragmatische Checkliste ableiten:

  • Der Controller hat mehr als einen Exit-Point oder tief geschachtelte ifs.
  • Das Modell lädt fachfremde Logik und Abhängigkeiten.
  • Fehlerbehandlung ist verstreut und inkonsistent.
  • Tests benötigen vollständige Requests, Auth, Shadow-Browser – obwohl es um reine Business-Logik geht.
  • Externe Systeme (Payments, Benachrichtigungen) sind eng mit Controller-/Modellcode verwoben.

Trifft eines davon zu, kann ein Command-Service die notwendige Klarheit schaffen.

Abschluss der Session

Koller schließt mit einem Hinweis auf das Teamumfeld: Das Team sei international, mit vielen Nutzerinnen und Nutzern der Produkte, und es werde an hoher Bild- und Videoanzahl gearbeitet. Er erwähnt, dass sie einstellen und verweist auf eine Karriereseite.

Der technische Kern bleibt jedoch klar: Service Objects – insbesondere im Command Pattern – geben Business-Logik in wachsenden Rails-Anwendungen ein Zuhause. Sie reduzieren kognitiven Ballast im Controller, verhindern fette Modelle und machen Tests zielgerichtet und schnell.

Unser Fazit

„Visual AI Made Simple“ von René Koller (Canva Austria GmbH.) liefert eine präzise Anleitung für saubere Business-Logik in MVC-Apps:

  • Service Objects sind der Ort für domänenübergreifende Prozesse.
  • Ein einheitliches Result-Objekt bündelt Fehlerbehandlung.
  • Der Controller bleibt schlank und vorhersehbar.
  • Tests werden fokussiert, billig und robust.

Gerade der Subscribe-Case zeigt exemplarisch, wie man von einem schwer lesbaren, tief verschachtelten Controller mit vielen Exit-Points zu einer eleganten, idempotenten Domänenoperation kommt. Für Teams, die Rails „schwer“ nutzen und mit Wachstum kämpfen, ist dieser Ansatz unmittelbar anwendbar.

Wer die gezeigten Regeln zunächst strikt befolgt und erst später bewusst variiert, wird die Vorteile schnell spüren: weniger Reibung, mehr Klarheit, bessere Tests. Genau so wird aus „Visual AI Made Simple“ auch „Business Logic Made Simple“ – mit Commands, die tun, was sie sagen.

Weitere Tech Talks