Wie wir die alte Hütte zerbrochen und an ihrer Stelle einen Wolkenkratzer gebaut haben

Zurab Bely, Teamleiter der Java-Praxis, erzählt seine Geschichte von der Arbeit in einem Projekt für ein großes Unternehmen und teilt seine Erfahrungen.

Wie ich mich niedergelassen habe ...


Ich bin Ende Sommer 2017 als gewöhnlicher Entwickler in das Projekt eingestiegen. Ich kann nicht sagen, dass es mir damals sehr gut gefallen hat: Die im Projekt verwendeten Technologien waren alt, die Kommunikation innerhalb des Teams wurde minimiert, die Kommunikation mit dem Kunden war schwierig und unproduktiv. Das Projekt hat mich also getroffen. Zu dieser Zeit hatte ich nur einen Wunsch: schnell rauszukommen.

Ich werde ein wenig über das gesamte Projekt erzählen. Dies ist das offizielle Portal eines großen Unternehmens mit allgemeinen Informationen, Nachrichten, Werbeaktionen und anderen Inhalten. Alle Marketing-Newsletter enthalten Links zu bestimmten Seiten der Website, dh die Auslastung ist stabil durchschnittlich, kann jedoch zu bestimmten Zeitpunkten hohe Werte erreichen. Die Stabilität und Zugänglichkeit der Webanwendung erfordert besondere Aufmerksamkeit - jede Minute Ausfallzeit führt zu großen Verlusten für den Kunden.

Shanty, die im Wind blinzelte


Zuerst habe ich hauptsächlich den technischen Zustand des Projekts untersucht, kleinere Fehler behoben und kleinere Verbesserungen vorgenommen. Aus technischer Sicht sah die Anwendung schrecklich aus: Eine monolithische Architektur, die auf einer veralteten kommerziellen Version von dotCMS basiert, Code, der in der 6. Java-Version geschrieben wurde, als das neunte serverseitige Rendern des Client-Teils auf dem Velocity-Framework erfolgte, das bis vor einigen Jahren noch nicht stattgefunden hatte wurde unterstützt. Jede Instanz wurde in JBoss AS gestartet und mit Nginx geroutet. Speicherlecks führten zu einem ständigen Neustart der Knoten, und das Fehlen eines normalen Cachings führte zu einer Erhöhung der Serverlast. Der größte Splitter waren jedoch die Änderungen am CMS-Code. Sie schlossen die Möglichkeit eines schmerzlosen Upgrades auf eine neuere Version aus. Ein gutes Beispiel dafür war der Übergang von Version 3.2 zu 3.7, der gerade zu diesem Zeitpunkt endete. Der Übergang zur nächsten Nebenversion dauerte mehr als ein Jahr. Keine gängigen Lösungen wie Spring Framework, React.js, Microservice-Architektur, Docker usw. kamen nicht in Frage. Bei einem tieferen Einblick in das Projekt wurden die Konsequenzen eines solchen technischen Zustands sichtbar. Am akutesten war die Unfähigkeit, die Anwendung für die Entwicklung und das Debuggen lokal auszuführen. Das gesamte Team von 8 Mitarbeitern arbeitete an einem Entwicklungsstand, an dem eine Kopie der Produktionsversion der Anwendung bereitgestellt wurde. Dementsprechend konnte nur ein Entwickler gleichzeitig seinen Code debuggen, und das Rollen des aktualisierten Codes blockierte das gesamte Team. Der Höhepunkt war ein fehlgeschlagener Verkauf, bei dem Millionen von Briefen, SMS- und Push-Benachrichtigungen über Zehntausende von Kanälen an verschiedene Benutzer gesendet wurden - Zehntausende von Sitzungen wurden gleichzeitig eröffnet. Die Server konnten es nicht aushalten und die meiste Zeit war das Portal nicht verfügbar. Die Anwendung lässt sich nicht gut skalieren. Es gab nur einen Weg, dies zu tun: eine weitere Kopie nebeneinander bereitstellen und die Lasten mithilfe von Nginx zwischen ihnen ausgleichen. Und jede Lieferung des Produktionscodes war mit viel manueller Arbeit verbunden und dauerte mehrere Stunden.

Sechs Monate nach meiner Beteiligung an dem Projekt, als die Situation bereits außer Kontrolle geriet, wurde beschlossen, die Situation radikal zu ändern. Der Übergangsprozess hat begonnen. Änderungen betrafen alle Bereiche: Teamzusammensetzung, Arbeitsprozesse, Architektur und technische Komponente der Anwendung.

Wir haben gebaut, gebaut ...


Zunächst sind personelle Veränderungen eingetreten. Sie wurden von mehreren Entwicklern ersetzt und haben mich zum Teamleiter gemacht. Der Übergang zu modernen Lösungen begann mit der Einbeziehung von Mitarbeitern in das Team, die Erfahrung in der Arbeit mit ihnen hatten.

Verfahrensänderungen waren globaler. Zu diesem Zeitpunkt wurde bereits die Agile- + Scrum-Methode entwickelt, zweiwöchige Sprints mit Lieferung am Ende jeder Iteration. Tatsächlich hat dies jedoch nicht nur die Arbeitsgeschwindigkeit nicht erhöht, sondern im Gegenteil verlangsamt. Die täglichen Rallyes dauerten anderthalb bis zwei Stunden und führten zu keinen Ergebnissen. Planung und Pflege wurden zu Streitigkeiten, Fluchen oder einfacher Kommunikation. Damit hatte etwas zu tun. Es war anfangs sehr schwierig, etwas in dieser Richtung zu ändern - im Namen des Kunden verlor das Team fast das Vertrauen, insbesondere nach einem erfolglosen Verkauf. Jede Änderung musste lange begründet, diskutiert und bewiesen werden. Seltsamerweise kam die Initiative aber vom Kunden. Ihrerseits war ein Scrum-Master beteiligt, um die korrekte Anwendung von Ansätzen und Methoden zu kontrollieren, Prozesse zu debuggen und das Team an die Arbeit zu bringen. Obwohl er nur von wenigen Sprints angezogen wurde, half es uns, das Fundament richtig zusammenzubauen. Der Ansatz zur Kommunikation mit dem Kunden hat sich stark verändert. Wir haben begonnen, Probleme in Prozessen häufiger zu diskutieren, Rückblicke wurden produktiver, Entwickler waren eher bereit, Feedback zu geben, und der Kunde seinerseits hat den Übergangsprozess in jeder Hinsicht unterstützt.

Aber ehrlich gesagt, ich sage ehrlich: Es gab einige Momente, in denen einige Änderungen innerhalb des Teams „blind“ durchgeführt wurden, und nach dem Auftreten positiver Ergebnisse wurde dies dem Kunden gemeldet. Seit sechs Monaten ändert sich die Einstellung zu einer komfortablen Arbeitskommunikation. Es folgten mehrere Teambuildings, eintägige und zweitägige Besprechungen des gesamten Entwicklungsteams mit dem Kundenteam (Vermarkter, Analyst, Designer, Produktberater, Content Manager usw.) sowie gemeinsame Besuche an der Bar. Nach einem Jahr und bis heute kann Kommunikation als freundlich bezeichnet werden. Die Atmosphäre ist freundlich, entspannt und komfortabel geworden. Natürlich kommt es nicht ohne Konflikte aus, aber selbst in der glücklichsten Familie gibt es manchmal Streitigkeiten.

In diesem Zeitraum gab es keine weniger interessanten Änderungen im Anwendungscode, in der Architektur und in den verwendeten Lösungen. Wenn Sie technisch nicht versiert sind, können Sie den gesamten Text sicher zum Abschluss überspringen. Und wenn Sie wie ich Glück haben - willkommen! Der gesamte Übergang kann in mehrere Stufen unterteilt werden. Über jeden im Detail.

Stufe 1. Identifizierung kritischer Problembereiche.

Alles war so einfach und klar wie möglich. Zunächst war es notwendig, die Abhängigkeit eines kommerziellen Produkts von Drittanbietern zu beseitigen, einen Monolithen zu schneiden und ein lokales Debuggen zu ermöglichen. Ich wollte den Client- und Servercode trennen, um ihn architektonisch und physisch zu verteilen. Ein weiterer Problembereich ist die Qualifikation. Dem Projekt fehlten jegliche automatischen Tests. Dies machte den Übergang etwas schwierig, da alles manuell überprüft werden musste. Da es nie technische Aufgaben für die Funktion gegeben hat (die Besonderheiten des Projekts sind hier betroffen), bestand eine hohe Wahrscheinlichkeit, dass etwas fehlte. Nachdem wir die Problembereiche gestrichen hatten, sahen wir uns noch einmal die Liste an. Es sah aus wie ein Plan. Es ist Zeit, einen Wolkenkratzer zu bauen!

Stufe 2. Aktualisieren der Codebasis.

Die am längsten laufende Etappe. Alles begann mit dem Übergang zu einer Service-Architektur (nicht zu verwechseln mit Microservices). Die Idee war folgende: die Anwendung in mehrere separate Dienste aufzuteilen, von denen jeder seine spezifische Aufgabe behandelt. Dienstleistungen sollten nicht „Mikro“ sein, aber ich wollte auch nicht alles in einen Kessel packen. Jeder Dienst sollte eine Spring Boot-Anwendung sein, die in Java SE 8 geschrieben und auf Tomcat ausgeführt wurde.

Der erste war der sogenannte. "Content Service", der zu einer Schicht zwischen der zukünftigen Anwendung und dem CMS geworden ist. Es ist eine Abstraktion auf dem Weg zum Inhalt geworden. Es wurde davon ausgegangen, dass alle Anforderungen, die wir zuvor direkt im CMS gestellt haben, über diesen Dienst ausgeführt werden, jedoch bereits das HTTP-Protokoll verwenden. Eine solche Lösung ermöglichte es uns, die Konnektivität zu reduzieren und die Möglichkeit zu schaffen, dotCMS später durch ein moderneres Analog zu ersetzen oder sogar die Verwendung kommerzieller Produkte zu eliminieren und unsere eigene Lösung zu schreiben, die auf unsere Aufgaben zugeschnitten ist (mit Blick auf die Zukunft werde ich sagen, dass dies der Weg ist, den wir gegangen sind).

Sofort wurde der Grundstein für die Trennung von Front- und Backend-Code gelegt. Sie erstellten einen Front-End-Service, der für die Verteilung des auf die Reaktion geschriebenen Codes verantwortlich wurde. Wir haben npm geschraubt, den Knoten konfiguriert und die Baugruppe debuggt - alles ist so, wie es sollte, entsprechend den modernen Trends des Client-Teils.

Im Allgemeinen wurde die Funktionalität dem Dienst gemäß dem folgenden Algorithmus zugewiesen:

  • hat eine neue Spring Boot-Anwendung mit allen erforderlichen Abhängigkeiten und Einstellungen erstellt;
  • portierte die gesamte Grundlogik (schrieb sie oft von Grund auf neu und bezog sich dabei auf den alten Code, nur um sicherzustellen, dass Sie keine Nuancen vergessen haben). Für den Caching-Dienst sind dies beispielsweise die Optionen zum Hinzufügen zum Cache, Lesen aus dem Cache und Deaktivieren.
  • Alle neuen Funktionen wurden immer mit dem neuen Dienst geschrieben.
  • schrittweise Umschreiben alter Teile der Anwendung in einen neuen Dienst in der Reihenfolge ihrer Wichtigkeit.

Zu Beginn hatten wir einige davon:

  • Inhaltsservice. Wirkte als Schicht zwischen der Anwendung und dem CMS.
  • Cache-Dienst. Einfaches Repository im Spring Cache.
  • AA-Service. Zu Beginn war er nur mit der Verbreitung von Informationen über einen autorisierten Benutzer beschäftigt. Der Rest blieb in dotCMS.
  • Frontservice. Verantwortlich für die Verteilung des Kundencodes.

Stufe 3. Autotests.

Unter Berücksichtigung aller Erfahrungen des Projekts haben wir festgestellt, dass das Vorhandensein von Funktionstests das Leben und die mögliche weitere Aktualisierung der Anwendung erheblich vereinfacht. Es ist Zeit, sie in das Projekt einzuführen. Unit-Tests des Codes kamen leider fast sofort zum Stillstand. Das Schreiben und der Support haben viel Zeit in Anspruch genommen, und wir hatten sehr wenig davon, da neben dem Umschreiben des Codes auch aktuelle Aufgaben zu neuen Funktionen an uns hingen und häufig Fehler auftauchten. Es wurde beschlossen, sich nur auf das Testen der Anwendungsoberfläche mit Selenium zu konzentrieren. Dies erleichterte uns einerseits die Durchführung von Regressionstests vor der Auslieferung an die Produktion, andererseits wurde es möglich, auf der Serverseite ein Refactoring durchzuführen und den Status auf der Clientseite zu überwachen. Das Team hatte keinen Automator, und das Schreiben und Aufrechterhalten der Relevanz von Autotests erfordert zusätzliche Kosten. Sie haben keinen der Tester umgeschult, und eine andere Person wurde dem Team hinzugefügt.

Phase 4. Automatisierung der Bereitstellung.

Jetzt, da wir separate Services haben, wenn sich das Frontend vom Backend getrennt hat und die Hauptfunktionalität durch Selbsttests abgedeckt wurde, ist es an der Zeit, eine Dose Bier zu öffnen und alle manuellen Arbeiten zur lokalen Bereitstellung und Unterstützung der Anwendung auf Demo- und Produktservern zu automatisieren. Das Schneiden des Monolithen in Stücke und die Verwendung von Spring Boot haben uns neue Horizonte eröffnet.

Die Entwickler konnten den Code lokal debuggen und nur den Teil der Funktionalität ausführen, der dafür benötigt wird. Endlich wurden die Prüfstände für den vorgesehenen Zweck verwendet - es wurde bereits mehr oder weniger debuggter Code für Erst- und Qualifikationstests bereitgestellt. Aber es gab immer noch viel handgemachte Arbeit, die unsere kostbare Zeit und Energie verschwendete. Nachdem wir das Problem untersucht und die Lösungen sortiert hatten, entschieden wir uns für eine Reihe von Gradle + TeamCity. Da das Projekt von Gradle erstellt wurde, machte es keinen Sinn, etwas Neues hinzuzufügen, und die geschriebenen Skripte erwiesen sich als plattformunabhängig. Sie können auf jedem Betriebssystem, remote oder lokal ausgeführt werden. Dies ermöglichte nicht nur die Verwendung einer beliebigen Lösung für CI / CD, sondern auch die schmerzlose Umstellung der Plattform auf eine andere. TeamCity wurde aufgrund seiner umfangreichen integrierten Funktionalität, der Präsenz einer großen Community und einer langen Liste von Plug-Ins für alle Gelegenheiten sowie der Integration in die Intellij IDEA-Entwicklungsumgebung ausgewählt.

Derzeit gibt es mehr als 100 Optimierungsskripte und mehr als 300 Aufgaben im CI-System, um sie mit unterschiedlichen Parametern auszuführen. Dies ist nicht nur eine Bereitstellung zum Testen von Bänken und zur Lieferung an die Produktion, sondern auch zum Arbeiten mit Protokollen, Serververwaltung, Routing und nur Lösungen für Routineaufgaben des gleichen Typs. Einige der Aufgaben wurden von unseren Schultern genommen. Content Manager konnten den Cache selbst leeren. Die Mitarbeiter des technischen Supports hatten die Möglichkeit, einzelne Dienste selbst in Anspruch zu nehmen und primäre Wiederbelebungsmaßnahmen durchzuführen. Der Schlaf der Entwickler ist tiefer und ruhiger geworden.

Stufe 5. Eigenes CMS.

Nachdem es möglich war, vom kommerziellen CMS zu abstrahieren, wurde es möglich, Daten von einer anderen Quelle zu empfangen, ohne den Anwendungscode neu zu schreiben. Woher diese oder jene Daten kommen, wurde vom Inhaltsdienst festgelegt. Nachdem wir nach vorgefertigten Lösungen gesucht und diese analysiert hatten, beschlossen wir, ein eigenes Content-Management-System zu schreiben, da keines von ihnen unsere Anforderungen vollständig erfüllte. Das Schreiben Ihres eigenen CMS ist ein endloser Prozess, ständig tauchen neue Bedürfnisse und Wünsche auf. Wir haben einige grundlegende Funktionen ausgewählt und sind in die schöne Welt der Entwicklung gegangen. Um die erste Version in Prod zu starten, hatten wir eineinhalb Mannmonate. Da die Funktionalität im neuen CMS bereit ist, übertragen wir Inhalte vom alten auf das CMS. Alle neuen Seiten haben nichts mehr mit dotCMS zu tun. Im Laufe der Zeit war es möglich, die kostenpflichtige Version aufzugeben und zur Community-Version zu wechseln und in Zukunft etwas von Drittanbietern vollständig aufzugeben.

Stufe 6. Korrekturlesen.

Nachdem wir unsere Hosen hochgekrempelt hatten, begannen wir unsere Reise in die Welt der Hipster-Programmierung. Diese Phase für mein Projekt war die letzte im Umstrukturierungsprozess. Es geht bis heute weiter. Der Hauptbereich, für den diese Phase im Allgemeinen auftrat, ist die Skalierung. Mit dem Docker + Kubernetes + Consul-Bundle können Sie nicht nur die Bereitstellung auf verschiedenen Servern vereinfachen und jeden Dienst separat verwalten, sondern auch die gesamte Anwendung flexibel skalieren, nur an den Stellen, an denen dies erforderlich ist, und nur so lange, wie dies erforderlich ist. Im Detail kann ich nur malen, wenn wir in der Produktion vollständig auf diese Lösung umsteigen.

... und endlich gebaut. Hurra!



Seit Beginn des Anwendungsupdates ist ein Jahr vergangen. Dies sind jetzt 26 REST-Services, die in Spring Boot geschrieben wurden. Jedes hat eine detaillierte API-Dokumentation in der Swagger-Benutzeroberfläche. Die Clientseite ist in React.js geschrieben und von der Serverseite getrennt. Alle Hauptseiten des Portals sind mit Tests abgedeckt. Durchführung mehrerer wichtiger Verkäufe. Der Übergang zu modernen Technologien, die Beseitigung von altem Code und der stabile Betrieb von Servern motivieren Entwickler stark. Von „wie gesagt, wir machen es“ hat sich das Projekt in den Mainstream verlagert, wo jeder am Erfolg interessiert ist und seine eigenen Optionen für Verbesserungen und Optimierungen bietet. Die Einstellung des Kunden zum Team hat sich geändert, eine freundliche Atmosphäre wurde geschaffen.

Dies ist das Ergebnis der Arbeit des gesamten Teams, jedes Entwicklers und Testers, Managers und Kunden. Es war sehr schwer, nervös und manchmal kurz vor einem Foul. Der Zusammenhalt des Teams, die kompetente Planung und das Bewusstsein für die Ergebnisse ermöglichten es jedoch, alle Schwierigkeiten zu überwinden.

Source: https://habr.com/ru/post/de457624/


All Articles