Logo

Die Ablösung der If-Else-Wüste und die Freiheit der Konfiguration.

„Wer immer tut, was er schon kann, bleibt immer das, was er schon ist.“ - Henry Ford

Wir kamen in unserem Entwicklerleben oft an den Punkt, an dem ein Kunde eine Konfiguration oder einen anderen als den von uns vorgesehenen Ablauf benötigt.

So wollte erst letztens ein Kunde eine E-Mail an seinen Einkauf versendet haben, sobald ein bestimmtes Produkt nicht vorhanden war. Für alle anderen Produkte sollten die Standardfunktionen zur Nachbestellung benutzt werden.

Wie geht man also vor? Die erste Umsetzung ist sicher ein "Wenn Produkt ausverkauft, dann E-Mail senden"



// Wenn das Produkt 1425 keinen Lagerbestand hat, 
// dann eine E-Mail senden.
if($produkt===1425 && $lagerbestand===0) {
   sendEMail($to, $from, $subject, "Kein Bestand für Produkt 1425");
}

    

Durch Ableitung der entsprechenden Klasse wäre es jetzt möglich dies als Kundenanpassung in sein System zu integrieren.


// Standardklasse
class A {

    public function checkStock() {
         // Tue etwas ....
    }

}

// Angepasste Kundenklasse
class B extends A {

    public function checkStock() {
        parent::checkStock();
    
        // Wenn das Produkt 1425 keinen Lagerbestand hat, 
        // dann eine E-Mail senden.
        if($produkt===1425 && $lagerbestand===0) {
           sendEMail($to, $from, $subject, "Kein Bestand für Produkt 1425");
        }
    
    }


}
    

Nach der Änderung kann man sich Fragen, ob diese Änderung für andere Kunden interessant ist. Wenn man das bejaht, dann könnte man das Verhalten in die zentrale Klasse übernehmen und im Anschluss über eine Konfiguration ein, bzw. ausschaltbar machen.



// Wenn das Produkt, das im $konfigurationschluessel definiert wurde
// keinen Lagerbestand hat, dann eine E-Mail senden.
if(produkt===$konfigurationschluessel && $lagerbestand===0) {
   sendEMail($to, $from, $subject, "Kein Bestand für Produkt "+$konfigurationschluessel);
}

    

Mit der Zeit erhält man so eine große Anzahl von Schaltern, um ein bestimmtes Verhalten einzurichten. Dieser Ansatz ist aber weder flexibel noch besonders übersichtlich.

Also wie kann man es besser machen? Richtig, durch die Definition von Arbeitsabläufen, besser bekannt unter dem Namen Workflow.

In einem Workflow wird festgelegt, welche Arbeitsschritte überhaupt durchgeführt werden müssen und in welcher Reihenfolge. Außerdem können Abhängigkeiten und Events abgebildet werden.


    
<xml version="1.0" encoding="UTF-8" standalone="yes"?>

    <!-- Mit diesem Tag kann der Eintritszustand in den Workflow festgelegt werden. 
       Besitzt ein Objekt kein Status, so wird dieser Status angenommen.
       class, state-class und step-class sind optional.
    -->
    <admission state="created"
               step="create"
               class="\Alvine\Application\Workflow\Admission"
               state-class="\Alvine\Application\Workflow\State"
               step-class="\Alvine\Application\Workflow\Step"
               container-class="\MyNamespace\Item" />

    <!-- Definition aller vorhandenen Status die ein Objekt annehmen kann -->
    <states> 
        <!-- Ein Status muss einen Namen besitzen, es kann eine von \Alvine\Application\Workflow\State
        abgeleitete Klasse angegeben werden. -->
        <state name="created" class="\Alvine\Application\Workflow\State" />
        <state name="start" />
        <state name="end" />
    </states>

    <!-- Steps definieren die Aktionen, die beim Übergang von einem Status,
    zu einem anderem Status gegangen werden. -->
    <steps> 
        <!-- Jeder Step muss einen eindeutigen Namen enthalten, zusätzlich
        kann noch eine von \Alvine\Application\Workflow\Step abgeleitetet Klasse 
        definiert werden. -->

        <!-- Dieser Step wird in <admission> referenziert und beim Erstellen eines
        Containers getriggert. -->
        <step name="create"> 
            <actions>
                <!-- Es können Aktionen, die beim Übergang ausgeführt werden, definiert werden. -->
                <action class="\MyNamespace\MyCreateAction" />
            </actions>
        </step>

        <step name="step1" class="\Alvine\Application\Workflow\Step"> 
            <actions>
                <!-- Es können Aktionen, die beim Übergang ausgeführt werden, definiert werden. -->
                <action class="\MyNamespace\MyAction" />
            </actions>
            <validations> 
                <!-- Mittels Validatoren kann geprüft werden, ob ein Statusübergang möglich ist. -->
                <validation class="\MyNamespace\MyValidation" />
            </validations> 
        </step>

        <step name="step2"> 
            <actions>
                <action class="\MyNamespace\MyAction" />
            </actions>
        </step>
    </steps>

    <!-- Automation des Workflows;
         Mit diesen Regeln lassen sich unabhänging von einem
         Übergang Änderungen an einem Container vornehmen -->
    <automation>
        <rules>
            <!-- Jede Regel verfügt über Bedingungen (die alles zutreffen müssen)
                 und Aktionen, die ausgeführt werden. Diese Regel wird
                 automatisch nach jeder Transition (\Alvine\Application\Workflow\Event\EndTransition)
                 ausgeführt -->
            <rule class="\Alvine\Application\Workflow\Automation\Rule"
                  name="my-rule"
                  on="\Alvine\Application\Workflow\Event\EndTransition">
                <conditions>
                    <!-- Beide Bedingungen müssen erfüllt sein; die Methode müssen
                         true zurück geben -->           
                    <condition class="\MyNamespace\AutomationCondition1" />
                    <condition class="\MyNamespace\AutomationCondition2" />
                </conditions>
                <actions>
                    <!-- Diese Aktionen werden ausgeführt -->
                    <action class="\MyNamespace\AutomationMyAction1" />
                    <action class="\MyNamespace\AutomationMyAction2" />
                </actions>
            </rule>
        </rules>
    </automation>

    <!-- transitions definieren eine Zustandsänderung eines Objektes. -->
    <transitions>
        <!-- Ein Übergang hat einen eindeutigen Namen und einen Zielstatus.
        Wird kein Ausgagsstatus (from) definiert, so ist dieser Übergang 
        von allen Status aus möglich. -->
        <transition name="transfer1" to="start" />
        <!-- Zusätzlich kann noch ein Step und ein Ausgangsstatus definiert werden.
        Es kann eine von \Alvine\Application\Workflow\Transition abgeleitete Klasse verwendet werden. -->
        <transition name="transfer2" class="\Alvine\Application\Workflow\Transition" from="start" to="end" with="step2">
            <!-- Überprüfung vor dem Übergang -->
            <validations> 
                <!-- Mittels Validatoren kann geprüft werden, ob ein Statusübergang möglich ist. -->
                <validation class="\MyNamespace\MyTransitionValidation" />         
            </validations>

            <!-- Fehler / Exceptions abfangen -->            
            <exceptions>
                <exception match="\Alvine\Core\FrameworkException" >
                    <!-- Exception ignorieren -->
                    <handler class="\Alvine\Application\Workflow\Transition\Exception\Handler\ThrowAway" />
                </exception>
            </exceptions>


        </transition>
    </transitions>

</workflow>

    

Wir setzen für die Gestaltung von Arbeitsabläufen auf unsere Workflow-Komponente. Mithilfe dieser Komponente wird ein Workflow über eine XML-Datei definiert. In der Dokumentation zur Komponente finden sich auch viele weitere Beispiele.

Also anstatt eine Funktionalität über If-Else fest im Code zu verdrahten, integrieren wir in zentrale Abläufe unsere Workflow-Engine.

Diese sorgt dafür, dass die definierten Aktionen in der gewünschten Reihenfolge durchgeführt werden.

Mithilfe eines Workflows können wir die Anforderung aus dem obigen Beispiel einfach durch eine weitere Aktion umsetzen.

Quellen und weiterführende Artikel

Welchen Ansatz verfolgen Sie?
Zusätzliche Schlüsselwörter und Phrasen: Softwareentwicklung, Entscheidung, PHP, Workflow