Wie man eine Website effektiv refaktorisiert
Sie haben also eine Website erstellt. Sie läuft und Ihr Kunde - ob Geschäftskunde oder interner Kunde - ist zufrieden. Ein Erfolg. Zeit zum Feiern.
Aber dann kommt der Kunde nach einer Woche, manchmal nach einem Monat oder vielleicht sogar nach einem Jahr zurück und bittet um Änderungen.
The website is up. The requirements change
Die inhaltliche Struktur wurde offenbar nicht so gut durchdacht wie erwartet. Oder vielleicht liegt es daran, dass sich die geschäftlichen Anforderungen oder die Art und Weise, wie die Kunden das Web nutzen, geändert haben. Aber Sie sind mit der Struktur der Inhalte, die Sie haben, aufgeschmissen. Es geht nicht nur um Vorlagen, die können Sie relativ leicht umschreiben, aber was ist mit Ihrer Inhaltsstruktur? Angenommen, Ihre Geschäftskunden haben bereits hundert oder mehr Artikel, Seiten oder einfache Inhalte mit der alten Struktur geschrieben, und sie wollen sie nicht von Grund auf neu schreiben. Was können Sie dann tun? Manipulation von Live-Kundendaten auf dem Produktionsserver? Autsch, das ist eine heiße, gefährliche Sache. Die meisten von uns würden instinktiv vor dieser Aufgabe zurückschrecken. Wir werden versuchen, die Kosten für solche Arbeiten in die Höhe zu treiben, um den Kunden dazu zu bringen, nicht so sehr auf einer solchen Änderung zu bestehen.
Aber vielleicht gibt es einen einfachen Ausweg aus diesem Dilemma. Vielleicht kann Magnolia Ihnen bei der Umstrukturierung Ihrer Live-Inhalte helfen, ohne dass es zu viel Mühe und Aufwand bedeutet. Ohne die Gefahr, den Inhalt zu sehr zu verunstalten.
In den folgenden Zeilen beschreibe ich eine Möglichkeit, ein solches Refactoring durchzuführen. Ich bin mir natürlich nicht sicher, ob das Beispiel, das ich beschreibe, genau auf Ihr Szenario zutrifft, aber wenn nicht, kann es Ihnen zumindest einige Ideen und Anregungen für die anstehende Aufgabe geben und Ihr Vertrauen in die Durchführung der Aufgabe stärken.
A case of website refactoring
Das Refactoring, das ich zu bewältigen hatte, sah folgendermaßen aus. Ursprünglich wollte der Kunde, dass eine Reihe von Elementen des Typs A oder B als verwandte Elemente zum Inhalt hinzugefügt werden. Um es konkreter zu machen, wollte er einen Inhalt, z. B. eine Tour, die mit den Führern verbunden war, die entweder interne Mitarbeiter oder externe Berater sein konnten.
Ursprünglich wurden nur die Fremdenführer aufgelistet, die für die Besichtigung angeheuert werden konnten, wenn sie daran interessiert waren. So war es auch bis heute. Heute kommt der Kunde und sagt, dass er gerne eine kleine Änderung vornehmen würde. Er möchte, dass ich nicht nur die Fremdenführer aufliste (zuerst die internen, dann die externen), sondern dass diese jetzt auch geordnet werden. Und zwar nicht nur auf der Grundlage einer berechneten Punktzahl. Sie möchten die volle Kontrolle über die Reihenfolge haben, so dass sie die Reiseleiter, die sie bevorzugen, an die Spitze setzen können. Wie stelle ich das nun an?
Ursprünglich schien die Aufgabe recht einfach. Ein Multi-Select-Feld für externe Reiseleiter und ein Multi-Select-Feld für interne Reiseleiter. Ein Kinderspiel. Aber jetzt kann ich nicht wirklich kombinieren diese, da sie aus verschiedenen Inhalt apps kommen und es gibt nicht eine einzige ChooseDialog, die über beide funktioniert.
Was ist also der Ausweg aus dieser Situation?
Wie wird die Code-Umstrukturierung durchgeführt?
Ändern wir unsere 2 Arten von Reiseleitern von einfachen Referenzen in Unterkomponenten. Ändern wir auch unsere 2 Multi-Select-Felder in einen Unterbereich mit einer Liste von Komponenten, von denen eine die Art des Reiseleiters und die andere die Art des Auftragnehmers ist. Auf diese Weise kann unser Content Editor die Reihenfolge kontrollieren. In der Vorlage ist es auch recht einfach, die Unterkomponenten anstelle der Werte der beiden Multi-Select-Felder aufzurufen.
Kommen wir nun zum komplizierteren Teil - der Datenmigration. Wir haben 2 Felder (externalGuide, internalGuide) mit Multi-Werten:
tourKomponente
|-tourComponent |--title="fu bar" |--internalGuide={1234-1234-1234, 1234-abcd-1234} |--externalGuide={2345-2345-2345, 2345-abcd-2345} `--mgnl:template=foo-web:components/guides
Was wir wollen, ist etwas wie:
tourKomponente
|-tourComponent |--title="fu bar" `--guides |-0 | |-link=1234-1234-1234 | `-mgnl:template=foo-web:components/guides_employeeGuides |-1 | |-link=2345-2345-2345 | `-mgnl:template=foo-web:components/guides_consultantGuides |-2 | |-link=2345-abcd-2345 | `-mgnl:template=foo-web:components/guides_consultantGuides `-3 |-link=1234-abcd-1234 `-mgnl:template=foo-web:components/guides_employeeGuides
What we have to do is move those id’s into components to allow editors to fix the order, but not to lose any id.
Let’s start by finding components that we need to migrate:
website = ctx.getJCRSession("website")
qm = website.getWorkspace().getQueryManager();
q = qm.createQuery("select * from [mgnl:component] where [mgnl:template]=‘foo-web:components/guides'", "JCR-SQL2");
rs = q.execute();
Versuchen wir nun herauszufinden, ob es an der gegebenen Komponente etwas zu ändern gibt:
results = rs.nodes
while (results.hasNext()) {
result = results.next()
if (!result.hasNode(“guides")) {
guides = result.addNode(“guides", "mgnl:area");
intP = processProfiles("internalGuide", guides, “foo-web:components/guides_employeeGuides")
extP = processProfiles("externalGuide", guides, “foo-web:components/guides_consultantGuides")
if ( intP || extP ) {
website.save()
} else {
guides();
}
}
}
Now let’s look at how to implement processProfiles() method so that we can actually upgrade the profiles:
def processProfiles(name, guides, template) {
foundProfiles = false
if(result.hasProperty(name)){
intProfiles = result.getProperty(name).getValues();
intProfiles.each {
foundProfiles = true
profileId = it.string
comp = guides.addNode(helper.getUniqueName(guides, "0"), "mgnl:component")
comp.setProperty("link", profileId)
comp.setProperty("mgnl:template", template)
}
}
return foundProfiles
}
One extra thing I’d need to make this work, would be access to the helper class (preferably at the beginning of the script):
helper = Components.getComponent(NodeNameHelper.class)
You might also want to add possibility to activate the nodes on top of the changes so that you can propagate all you did to public instances as part of the migration. How do we do that? Let’s see …
To be able to activate only those tours that have been already published, we first need to check the status of publication prior to any node modification:
while (results.hasNext()) { result = results.next() status = NodeTypes.Activatable.getActivationStatus(result); }
and once we know the status, we can act upon after migrating our tour node:
... if ( intP || extP ) { website.save() println("Gespeichert") if (status == NodeTypes.Activatable.ACTIVATION_STATUS_MODIFIED){ println "wurde geändert, nicht veröffentlicht";
} else if (status == NodeTypes.Activatable.ACTIVATION_STATUS_ACTIVATED){println "veröffentlichen"; commands.executeCommand("publish",["path":guides.path, "userName": "migration-script", "recursive":true, "repository": "website"]) } ...
one last thing we need is to obtain commands manager before even starting the loop:
Befehle = Components.getComponent(CommandsManager.class)
Erledigt. Sicher. Zukunftssicher
And that's really it. Not so difficult after all, right? We managed to migrate all our customers data and not cause any damage while doing so. We also did so in an automated, replicable way so we can easily run the process on test instance before doing so in production.
Of course your use case will likely be slightly different, but can still be done with little modifications.
Good luck.