Deshalb haben wir dem Kunden ein B2B-Softwareprodukt verkauft.
Bei der Präsentation mochte er alles, aber während der Implementierung stellte sich heraus, dass etwas immer noch nicht passte. Sie können natürlich sagen, dass Sie „Best Practice“ befolgen und sich in ein Produkt verwandeln müssen und nicht umgekehrt. Dies kann funktionieren, wenn Sie eine starke Marke haben (z. B. drei große Buchstaben, und Sie können alle drei kleinen Buchstaben senden). Andernfalls erklären sie Ihnen schnell, dass der Kunde dank seiner einzigartigen Geschäftsprozesse alles erreicht hat, und lassen Sie uns Ihr Produkt besser ändern, sonst funktioniert es nicht. Es besteht die Möglichkeit, die Tatsache abzulehnen und darauf hinzuweisen, dass bereits Lizenzen erworben wurden, und das U-Boot kann nirgendwo hingehen. In relativ engen Märkten wird eine solche Strategie jedoch noch lange nicht funktionieren.
Wir müssen ändern.
Die Ansätze
Es gibt verschiedene grundlegende Ansätze zur Produktanpassung.
Monolith
Alle Änderungen werden direkt am Quellcode des Produkts vorgenommen, sind jedoch in bestimmten Optionen enthalten. In solchen Produkten gibt es in der Regel monströse Formen mit Einstellungen, denen, um nicht verwirrt zu werden, ihre Nummern oder Codes zugewiesen werden. Der Nachteil dieses Ansatzes besteht darin, dass der Quellcode zu einem großen Spaghetti wird, in dem es so viele verschiedene Anwendungsfälle gibt, dass die Wartung sehr lang und teuer wird. Jede nachfolgende Option erfordert immer mehr Ressourcen. Die Leistung eines solchen Produkts lässt ebenfalls zu wünschen übrig. Und wenn die Sprache, in der das Produkt geschrieben ist, moderne Praktiken wie Vererbung und Polymorphismus nicht unterstützt, wird alles sehr traurig.
Kopieren
Der Kunde erhält den gesamten Quellcode des Produkts mit einer Lizenz zum Ändern. Oft sagen solche Anbieter dem Kunden, dass sie das Produkt nicht selbst anpassen werden, da es zu teuer ist (es ist für einen Anbieter viel rentabler, Lizenzen zu verkaufen, als Dienste zu kontaktieren). Aber sie haben vertraute Outsourcer, die irgendwo in Drittländern relativ preiswerte und qualitativ hochwertige Mitarbeiter einstellen, die bereit sind, ihnen zu helfen. Es gibt auch Situationen, in denen Verbesserungen direkt von den Spezialisten des Kunden durchgeführt werden (sofern diese über Personaleinheiten verfügen). In solchen Fällen wird der Quellcode als Ausgangspunkt genommen, und der geänderte Code hat keine Verbindung zu dem, was ursprünglich war, und lebt sein eigenes Leben. In diesem Fall können Sie mindestens die Hälfte des Originalprodukts sicher entfernen und durch Ihre eigene Logik ersetzen.
Fusion
Dies ist eine Mischung aus den ersten beiden Ansätzen. Aber darin sollte sich der Entwickler, der den Code korrigiert, immer daran erinnern: "Zusammenführung kommt". Wenn eine neue Version des Quellprodukts veröffentlicht wird, müssen die Änderungen im Quellcode und im geänderten Code in den meisten Fällen manuell zusammengeführt werden. Das Problem ist, dass in jedem Konflikt daran erinnert werden muss, warum bestimmte Änderungen vorgenommen wurden, und dies könnte sehr lange her sein. Und wenn das Code-Refactoring im Originalprodukt durchgeführt wurde (z. B. wurden Codeblöcke einfach neu angeordnet), ist das Zusammenführen sehr mühsam.
Modularität
Logischerweise der korrekteste Ansatz. Der Quellcode des Produkts wird in diesem Fall nicht geändert, und es werden zusätzliche Module hinzugefügt, die die Funktionalität erweitern. Um ein solches Schema zu implementieren, muss das Produkt jedoch über eine Architektur verfügen, mit der es auf diese Weise erweitert werden kann.
Beschreibung
Anhand von Beispielen werde ich zeigen, wie wir Produkte erweitern, die auf der Basis der offenen und kostenlosen
lsFusion- Plattform entwickelt wurden.
Ein Schlüsselelement des Systems ist das Modul. Ein Modul ist eine Textdatei mit der Erweiterung
lsf , die
lsFusion-Code enthält. In jedem Modul werden sowohl die Domänenlogik (Funktionen, Klassen, Aktionen) als auch die Präsentationslogik (Formulare, Navigator) deklariert. Module befinden sich in der Regel in Verzeichnissen, die durch ein logisches Prinzip unterteilt sind. Ein Produkt ist eine Sammlung von Modulen, die seine Funktionalität implementieren und in einem separaten Repository gespeichert werden.
Module sind voneinander abhängig. Ein Modul hängt von einem anderen ab, wenn es seine Logik verwendet (bezieht sich beispielsweise auf Eigenschaften oder Formulare).
Wenn ein neuer Client angezeigt wird, wird ein separates Repository (Git oder Subversion) dafür gestartet, in dem Module mit den erforderlichen Änderungen erstellt werden. Dieses Repository definiert das sogenannte Top-Modul. Beim Start des Servers werden nur die Module verbunden, von denen es direkt oder transitiv über andere Module abhängt. Dadurch kann der Kunde nicht alle Funktionen des Produkts nutzen, sondern nur den Teil, den er benötigt.
Jenkins erstellt eine Aufgabe, die die Produkt- und Clientmodule in einer einzigen JAR-Datei kombiniert, die dann auf einem Produktions- oder Testserver installiert wird.
Betrachten Sie mehrere Hauptfälle von Verbesserungen, die sich in der Praxis ergeben:
Angenommen, wir haben ein Bestellmodul im Produkt, das die Standardbestelllogik beschreibt:
Kunde
X möchte einen Rabattprozentsatz und einen Rabattpreis für die Bestellposition hinzufügen.
Zunächst wird im Kunden-Repository ein neues
OrderX- Modul erstellt. In der Kopfzeile wird eine Abhängigkeit vom ursprünglichen Bestellmodul platziert:
In diesem Modul deklarieren wir neue Eigenschaften, unter denen zusätzliche Felder in den Tabellen erstellt werden, und fügen sie dem Formular hinzu:
Wir machen den reduzierten Preis für die Aufnahme nicht verfügbar. Es wird als separates Ereignis berechnet, wenn sich entweder der Anfangspreis oder der Rabattprozentsatz ändert:
Jetzt müssen Sie die Berechnung des Betrags in der Bestellposition ändern (dies muss unseren neu erstellten Rabattpreis berücksichtigen). Dazu erstellen wir normalerweise bestimmte „Einstiegspunkte“, an denen andere Module ihr Verhalten einfügen können. Anstelle der anfänglichen Deklaration der sum -Eigenschaft im Order-Modul verwenden wir Folgendes:
In diesem Fall wird der Wert der Summeneigenschaft in einem CASE gesammelt, in dem WHEN auf verschiedene Module verteilt werden kann. Es ist garantiert, dass, wenn Modul A von Modul B abhängt, alle WANN von Modul B später als WANN von Modul A funktionieren. Um den reduzierten
Betrag korrekt zu berechnen, wird dem
OrderX- Modul die folgende Deklaration hinzugefügt:
Wenn ein Rabatt festgelegt wird, unterliegt dies dem Betrag, andernfalls dem ursprünglichen Ausdruck.
Angenommen, ein Kunde möchte eine Einschränkung hinzufügen, dass der Bestellbetrag einen bestimmten angegebenen Betrag nicht überschreiten darf. Im selben
OrderX- Modul deklarieren
wir eine Eigenschaft, in der der Grenzwert gespeichert wird, und fügen ihn dem Standardoptionsformular hinzu (Sie können auf Wunsch ein separates Formular mit Einstellungen erstellen):
Dann deklarieren wir im selben Modul den Betrag der Bestellung, zeigen ihn auf dem Formular an und fügen ein Limit für den Überschuss hinzu:
Und schließlich bat der Kunde, das Design des Auftragsbearbeitungsformulars geringfügig zu ändern: den Auftragskopf links von den Zeilen mit einem Trennzeichen zu versehen und die Preise immer mit einer Genauigkeit von zwei Zeichen anzuzeigen. Zu diesem Zweck wird dem Modul der folgende Code hinzugefügt, der das standardmäßig generierte Design des Bestellformulars ändert:
Als Ergebnis erhalten wir zwei Bestellmodule (im Produkt), in denen die Grundlogik der Bestellung implementiert ist, und
OrderX (beim Kunden), in denen die erforderliche
Rabattlogik implementiert ist:
Es ist zu beachten, dass das
OrderX- Modul
OrderDiscount heißen und direkt auf das Produkt übertragen werden kann. Bei Bedarf kann dann jeder Kunde die Funktionalität problemlos mit Rabatten verbinden.
Dies ist weit entfernt von allen Möglichkeiten, die die Plattform bietet, um die Funktionalität in einzelnen Modulen zu erweitern. Mithilfe der Vererbung können Sie beispielsweise die
Registerlogik modular implementieren.
Wenn sich im Quellcode des Produkts Änderungen ergeben, die dem Code im abhängigen Modul widersprechen, wird beim Start des Servers ein Fehler generiert. Wenn das
Bestellformular beispielsweise im
Bestellmodul gelöscht wird, tritt beim Start die Fehlermeldung auf, dass das
Bestellformular nicht im
Bestellmodul gefunden wurde . Außerdem wird der Fehler in der
IDE hervorgehoben . Darüber hinaus verfügt die IDE über eine Funktion zum Suchen nach allen Fehlern im Projekt, mit der Sie alle Probleme identifizieren können, die aufgrund der Aktualisierung der Produktversion aufgetreten sind.
In der Praxis haben wir alle Repositorys (des Produkts und aller Kunden) mit demselben Projekt verbunden, sodass wir das Produkt ruhig umgestalten und gleichzeitig die Logik in den Kundenmodulen ändern, in denen es verwendet wird.
Fazit
Eine solche mikromodulare Architektur bietet die folgenden Vorteile:
- Jeder Kunde ist nur mit der Funktionalität verbunden, die er benötigt . Die Struktur seiner Datenbank enthält nur die Felder, die er verwendet. Die Schnittstelle der endgültigen Lösung enthält keine unnötigen Elemente. Der Server und der Client führen keine unnötigen Ereignisse und Überprüfungen durch.
- Flexibilität bei Änderungen der Grundfunktionalität . Direkt im Kundenprojekt können Sie Änderungen an absolut jeder Form des Produkts vornehmen, Ereignisse, neue Objekte und Eigenschaften, Aktionen, Änderungen am Design und vieles mehr hinzufügen.
- Die Lieferung neuer Verbesserungen, die der Kunde benötigt, wird erheblich beschleunigt . Bei jeder Änderungsanforderung müssen Sie nicht darüber nachdenken, wie sich dies auf andere Kunden auswirkt. Aufgrund dessen können viele Verbesserungen vorgenommen und so schnell wie möglich (oft innerhalb weniger Stunden) in Betrieb genommen werden.
- Ein bequemeres Schema zur Erweiterung der Funktionalität des Produkts . Zunächst kann jede Funktionalität für einen bestimmten Kunden aufgenommen werden, der bereit ist, sie zu testen. Bei einer erfolgreichen Implementierung werden die Module dann vollständig in das Produkt-Repository übertragen.
- Unabhängigkeit von der Codebasis . Da im Rahmen von Kundendienstverträgen formal viele Verbesserungen vorgesehen sind, gehört der gesamte im Rahmen dieser Verträge entwickelte Code dem Kunden. Mit diesem Schema wird eine vollständige Trennung des Produktcodes, der dem Anbieter gehört, vom Code des Kunden sichergestellt. Auf Anfrage übertragen wir das Repository auf den Server des Kunden, wo er die von ihm benötigten Funktionen mit seinen eigenen Entwicklern ändern kann. Wenn der Lieferant Lizenzen für einzelne Produktmodule erteilt, verfügt der Kunde nicht über den Quellcode der Module, für die keine Lizenz vorhanden ist. Daher verfügt er nicht über die technische Fähigkeit, sie unter Verstoß gegen die Lizenzbedingungen unabhängig voneinander zu verbinden.
Das oben beschriebene Modularitätsschema mit Hilfe von Erweiterungen in der Programmierung wird am häufigsten als
Mix-In bezeichnet . Beispielsweise hat Microsoft Dynamics kürzlich das Konzept der Erweiterung eingeführt, mit dem Sie auch die Basismodule erweitern können. Dort ist jedoch viel weniger Programmierung auf niedrigerer Ebene erforderlich, was wiederum höhere Qualifikationen der Entwickler erfordert. Im Gegensatz zu lsFusion erfordert die Erweiterung von Ereignissen und Einschränkungen außerdem die anfänglichen „Einstiegspunkte“ für das Produkt, um dies nutzen zu können.
Derzeit unterstützen und implementieren wir gemäß dem oben beschriebenen Schema ein
ERP-System für den Einzelhandel mit mehr als 30 relativ großen Kunden, das aus mehr als 1000 Modulen besteht. Zu den Kunden zählen FMCG-Netzwerke sowie Apotheken, Bekleidungsgeschäfte, Drogerieketten, Großhändler und andere. Im Produkt gibt es jeweils separate Kategorien von Modulen, die je nach Branche und verwendeten Geschäftsprozessen miteinander verbunden sind.