Projektarchitekturentwicklung, Schiffe und JavaScript

Eine Geschichte darüber, was Sie berücksichtigen müssen, um eine hochwertige Architektur für Ihr Projekt zu erstellen. Wie man es unsinkbar macht und die Kunden zufrieden stellt.

Im Folgenden werden wir Beispiele aus dem wirklichen Leben betrachten und versuchen, aus den Fehlern anderer zu lernen. Unterwegs erstellen wir ein Buch mit nützlichen Empfehlungen für den Lösungsarchitekten. In allen Geschichten - architektonische Aufgaben, die mit den Hauptanforderungen des Kunden beginnen und von einer weiteren Nachbesprechung begleitet werden.



Der Artikel basiert auf einem Bericht von Alexey Bogachuk (Lösungsarchitekt von EPAM) von der HolyJS 2018 Piter- Konferenz. Unter der Zwischensequenz - Video und Transkript des Berichts.



Ansätze zur Gebäudearchitektur und zur Rolle des Projektarchitekten


Schwamm - wir wissen


So sagten die Seeleute vom schwedischen Schiff Vasa. Sie segelten nur und flohen vor dem sinkenden Schiff, das gerade von den Slipanlagen ins Wasser gesenkt worden war. Was hat Vasa damit zu tun?



Beginnen wir mit einer Kurzgeschichte, in der Sie die Ansätze zum Erstellen von Anwendungsarchitekturen und die Rolle des Projektarchitekten ganz anders betrachten.

Der schwedische König unterzeichnete einen Vertrag mit dem Architekten Schiffbauer Henrik Hubertsson. Gemäß den Vertragsbedingungen musste Henrik ein Flaggschiff bauen - die Schönheit der schwedischen Flotte, das beste Schiff in Europa. Als Hauptsponsoren beteiligten sich der König und die Schatzkammer an der Koordination aller Hauptmerkmale des Schiffes. Infolgedessen wurde der Auftrag wie folgt formuliert:

  • Das Schiff sollte das größte in der Ostseeflotte sein: 70 Meter lang, 10 Meter breit;
  • Sie benötigen drei Decks, die 300 Soldaten aufnehmen können.
  • er muss 64 Kanonen in zwei Reihen an Bord haben;
  • Für den Bau sind 3 Jahre vorgesehen.



Analoga eines solchen Schiffes gab es zu diesem Zeitpunkt nicht. Er selbst hielt jedoch auch nicht lange an und versank mitten in der Feier seines Baus.

Als sie versuchten herauszufinden, warum dies geschah, stellte sich heraus, dass es keine Abweichungen von den Anforderungen gab. Die Größe ist gleich, die Anzahl der Kanonen ist normal, die Seeleute auf den Decks sind genau die Menge, die benötigt wurde. Dies war jedoch perfekt mit der Tatsache verbunden, dass die Gesetze der Physik es einem solchen Schiff nicht erlaubten, längere Zeit auf dem Wasser zu bleiben.

Die folgenden architektonischen Fehleinschätzungen von Henryk sind offensichtlich (was ihn übrigens das Leben hätte kosten können, wenn er vor Gericht überlebt hätte):

  • Alle widersprüchlichen Beschränkungen waren nicht ausgewogen.
  • Es gab kein Risikomanagement, da noch niemand Schiffe dieser Größenordnung gebaut hatte.
  • Auch das Kundenbeziehungsmanagement fehlte - Henrik hatte nicht den Mut, mit dem König zu streiten.
  • Falsche Bautechnologien verwendet.
  • Der Architekt stimmte unmöglichen Anforderungen zu.

Infolge dieser Fehlkalkulationen "ging" das Schiff bereits in der Entwurfsphase "unter". Die Geschichte ist lehrreich, da sie den Einfluss des Architekturzyklus auf die zu erstellende Anwendung widerspiegelt. Es gibt Interessenten, die Ziele und Anforderungen formulieren, auf deren Grundlage die Architektur des Projekts und dann das Projekt selbst erstellt werden. Ein Fehler in einer dieser Phasen kann das ganze Projekt wert sein, und manchmal lässt Henrik Hubertsson Sie nicht lügen.

Mächtige Freunde


Wie viele Anwendungen mit der falschen Architektur sind tot, bevor die erste Codezeile geschrieben wird?
Der Zyklus des Einflusses der Architektur auf das Projekt ist wie folgt:



Von links nach rechts:

  1. Es gibt interessierte Parteien oder Interessengruppen (im Fall des Schiffes ist dies der König und die Schatzkammer).
  2. Sie haben ihre eigenen Ziele (das erste Schiff in Europa).
  3. Die Ziele bestimmen die Anforderungen (spezifische Merkmale des zukünftigen Schiffes).
  4. Als nächstes werden Zeichnungen, Diagramme und Entwürfe erstellt.
  5. Bau des Projekts.

Ein Fehler in einer dieser Phasen kann die Zukunft Ihres Projekts streichen.

Handbuch für Lösungsarchitekten


Wir werden Beispiele aus dem wirklichen Leben betrachten und versuchen, aus den Fehlern anderer zu lernen. Gleichzeitig werden wir ein Buch mit nützlichen Empfehlungen für den Lösungsarchitekten zusammenstellen. Henrik Hubertsson wurde durch die Tatsache, dass er keinen hatte, ausgebrannt.

Wenn wir in der Zeit unseres Helden leben würden, in der Fehler in der Architektur mit dem Tod bestraft würden, wäre dieses Buch in Blut geschrieben.

In allen Geschichten werden architektonische Kata (Aufgaben) gegeben. Sie enthalten eine vereinfachte Anfrage mit den ersten Anforderungen des Kunden, dem Kern des Problems und der Schlussfolgerung.

Jimmys Uhrwerkgeschichte


Kundenanforderungen

  • Ersetzen Sie die aktuelle UI-Lösung.
  • Einführung eines neuen Ansatzes für die Entwicklung und Implementierung dieser Lösung.
  • Benötigen Sie eine bessere Benutzererfahrung.
  • Befolgen Sie gleichzeitig alle Best Practices.
  • Unterstützung für verschiedene Plattformen.

Was wurde getan?

Die Anforderungen sind sehr allgemein, keine Besonderheiten. Es ist nicht klar, was jeder damit machen muss. Gleichzeitig befindet sich das Entwicklungsteam in Minsk und der Kunde in Montreal. Aufgrund der Tatsache, dass es Sanktionen zwischen Weißrussland und Kanada gab, konnte keine direkte Zusammenarbeit mit Kanada durchgeführt werden. Es wurde beschlossen, mit dem Kunden über ein Büro in Dublin zusammenzuarbeiten. Aufgrund all dieser Zeit- und Vermittlungsverzögerungen war es nicht möglich, den Kunden zu kontaktieren und schließlich die Anforderungen herauszufinden und Vorschläge für die Umsetzung zu machen.



Nach einiger Zeit begann ein gewisser Jimmy immer noch Fragen zu beantworten und Anforderungen zu klären, die Entwicklung des Projekts begann. Jimmy teilte eifrig Ratschläge, Kontakte wurden von ihm genommen und die Korrespondenz wurde direkt mit ihm geführt. Als Ergebnis der Arbeit wurde eine Präsentation gemacht. Es ist Zeit, die Ergebnisse zu zeigen. Es wurde eine Konferenz mit wichtigen Kunden organisiert, aber seltsamerweise war Jimmy nicht unter ihnen, und niemand wusste, wer es war. Natürlich stellte sich heraus, dass alles völlig anders war als vom Kunden erwartet. Tatsache war, dass das Unternehmen keinen Jimmy kannte. Es stellte sich heraus, dass er ein gewöhnlicher Entwickler war und einfach seine Erfahrungen und Ratschläge teilte. Er traf keine Entscheidungen und hatte im Allgemeinen überhaupt keine Beziehung zum Projekt.

Was ist der Fehler? In der ersten Phase der Architekturdefinition wurden die Stakeholder falsch definiert.

Fazit

Jede Architektur beginnt mit der Identifizierung von Stakeholdern. Um sie zu identifizieren, gibt es viele Ansätze, wir werden einen davon betrachten - wir werden eine RACI-Matrix erstellen.

Raci


Die Abkürzung steht für: R - verantwortlich, diejenigen, die implementieren werden; A - verantwortliche Entscheidungsträger; C - Beratung (Geschäftsleute); Ich - informiert, Leute, die informiert werden müssen. Jede der interessierten Parteien muss der einen oder anderen Kategorie zugeordnet sein. Die Matrix zeigt Rollen und Aufgaben an.



Nachdem wir eine solche Matrix erstellt haben, können wir verstehen, wer die Stakeholder sind.

Darüber hinaus wurde festgestellt, dass es unter den Stakeholdern Personen gibt, die falsche Behauptungen aufstellen und das Projekt beiseite legen. In diesem Fall stellte sich heraus, dass dies Vertreter anderer Anbieter waren, die nicht an dem Projekt interessiert waren. Die RACI-Matrix weiß jedoch nicht, wie solche Kunden zu unterscheiden sind, dafür gibt es einen Zwiebelansatz.

Zwiebel



Der Zwiebelansatz unterscheidet sich etwas von RACI-Matrizen.



Das Wesentliche ist, dass Schichten um das System herum aufgebaut sind, in denen bestimmte Kundengesichter angegeben sind. In diesem Beispiel kommunizieren Entwickler, DevOps und Content Manager mit dem System selbst. Etwas höher in der Abstraktion sind Menschen aus der Wirtschaft. Es gibt auch externe Regulierungsbehörden: Medien, Gesetze usw. Um beispielsweise einen Antrag in einigen Ländern freizugeben, müssen Sie ein von einem Drittunternehmen durchgeführtes Audit bestehen. Es zeigt die Zugänglichkeit und andere für das Projekt erforderliche Eigenschaften auf. Dies sind Anforderungen externer Stakeholder.

Daher schreiben wir in das Handbuch unseres Architekten, dass die erste und notwendige Phase darin besteht, die Stakeholder zu bestimmen.

Geschichte: nicht schnell genug


Das Unternehmen hatte einen Kunden aus dem Handel, in dieser Angelegenheit ist die Transaktionsgeschwindigkeit sehr wichtig. Auf dieser Grundlage wurde eine Reihe von Anforderungen formuliert.

Kundenanforderungen
  • Sei schneller als die Konkurrenz

  • Die Transaktion muss in nicht mehr als 0,5 Sekunden erfolgen.

Was hat getan

Das Projekt wurde abgeschlossen, ist aber fehlgeschlagen. Warum? Auch hier waren die Anforderungen nicht ganz korrekt. Das Ziel war nicht, Transaktionen mit einer Geschwindigkeit von 0,5 Sekunden durchzuführen, sondern sie schneller als ein Konkurrent zu machen. Infolgedessen wurde die Geschwindigkeit auf 0,5 Sekunden gebracht, aber der Konkurrent erreichte zu diesem Zeitpunkt einen Wert von 0,4 Sekunden. Wir sehen einen Fehler bei der Bestimmung des Geschäftsziels. Warum braucht der Kunde ein System?

Ein Geschäftsziel ist nur die Spitze des Eisbergs, dahinter stehen Geschäftstreiber, Regulierungsbehörden und interne Ziele des Unternehmens. Oft bleiben sie unbekannt. Wir sind mehr an technischen Zielen interessiert, zu denen auch ein Geschäftsziel gehört. Sie werden durch Geschäftsgrundsätze geregelt, zum Beispiel will niemand die Qualität der Arbeit bei der Umsetzung eines technischen Ziels opfern, weil dies auf lange Sicht eine Fehleinschätzung ist. All dies müssen Sie wissen und beachten, wenn Sie Architektur bauen. Ebenso wie die Tatsache, dass Projekte ohne Ziel zunächst tot sind.



Fazit

Selbst wenn Sie in Startups arbeiten, deren Zweck häufig darin besteht, neue Technologien zu testen, ist der Einsatz neuer Technologien im Projekt kein Geschäftsziel. Das Problem ist, dass das Projekt, wenn das Ziel der Einsatz neuer Technologien ist, ständig wächst und neue unnötige finanzielle und vorübergehende Investitionen erfordert. Geschäftsziele sollten beim Entwerfen einer Architektur niemals übersehen werden.

Nützliche Empfehlungen finden Sie im Buch "Discovering Requirements", Autoren - Ian Alexander, Ljerka Beus-Dukic.



Die Geschichte, wie die Pioniere des Pferdes gemolken haben


Ein Unternehmen, das Versicherungen verkauft, hat eine eigene Website. Es funktioniert großartig und hat eine gut etablierte Funktionalität. Es verfügt bereits über eine komplexe Geschäftslogik.

Kundenanforderungen

  • Zusätzlich zur Website müssen Sie eine mobile Anwendung erstellen, die Mitarbeiter auf ihren Telefonen verwenden
  • Die Anwendung muss über einen Offline-Modus verfügen.

Was hat getan

Aufgrund der Anforderungen wurde beschlossen, in React Native zu schreiben. Die Entwicklung hat begonnen. Während des ersten Anrufs gingen Verfeinerungen und Ergänzungen der Anforderungen ein:

  • Das Unternehmen gibt Telefone an Mitarbeiter aus, die alle mit Android arbeiten.
  • Der Kunde interessiert sich nur für den Offline-Modus.
  • Frist - zwei Monate.

Offensichtlich passt die Aufgabe, ein fertiges Produkt eines Drittanbieters mit komplexer Geschäftslogik zu sortieren und in zwei Monaten ein neues zu schreiben, nicht in einen solchen Zeitrahmen. Es wurde beschlossen, PWA (Progressive Web Apps) zu verwenden.

Ich hatte bereits Erfahrung mit solchen Arbeiten. Es wurde nicht nur eine PWA-Anwendung geschrieben, sondern sie war isomorph. Dienste vom Server auf dem Client wurden wiederverwendet, spezielle Wrapper wurden geschrieben, mit denen Sie mit diesen Diensten kommunizieren konnten. Es wurde ein Router geschrieben, der alle Anforderungen an die MongoDB-Datenbank umleitete. Auf dem Client arbeiteten sie über den Adapter mit IndexedDB. Schema unten.



Die Probleme lagen also bei den Anforderungen, die nicht so einfach sind. Betrachten Sie ein Beispiel für die Anforderungen:



Es gibt ein Formular, und wir müssen Validierungsfehler erzeugen. Wenn die falsche URL eingegeben wird, müssen wir Seite 404 anzeigen. Was ist mit den Anforderungen falsch? Sie sprechen darüber, was das System tun soll. Aber für den Architekten sollte welches System wichtiger sein. Wenn Sie sich mit den Funktionen des Systems befassen, können Sie zu tief in die Details gehen und den falschen Weg einschlagen. Solche Anforderungen werden als funktional bezeichnet, sie werden auch als MoSCoW-Anforderungen bezeichnet. In diesen Anforderungen häufig vorkommende Wörter:



Alle Anforderungen, die diese Wörter enthalten, interessieren Sie nicht. Wenn Sie sich auf solche Anforderungen konzentrieren, wird ein monolithisches System ohne spezielle Architektur erstellt.

Fazit

Der Architekt sollte sich auf nicht funktionale Anforderungen konzentrieren, insbesondere auf Einschränkungen und Qualitätsmerkmale. Die nächste Kata dazu.

Die Geschichte der weißen Krähe


Kundenanforderungen

  • Entwickeln Sie einen separaten Dienst, der Daten im XML-Format konvertiert und zwischenspeichert.
  • Er muss dies von einem Drittanbietersystem aus tun.

Was hat getan

Wir haben einen guten Arbeitsservice entwickelt und dies auf Node.js getan. So begann das gesamte System zusammen mit dem eingeführten neuen Dienst schematisch auszusehen.



Offensichtlich ist Node.js hier ein schwarzes Schaf, obwohl alles gut funktioniert hat.

Der Fehler wurde bei der Übertragung des Dienstes an Kunden entdeckt, die mit Node.js nicht vertraut waren. Diese Situation zeigt perfekt die Rolle der Identifizierung von Einschränkungen für das Projekt. Was sind die Einschränkungen?

  • Technisch
  • Zeit und Budget
  • Kundenentwickler-Stack

Fazit

Der Architekt ist verpflichtet, alle bestehenden Einschränkungen herauszufinden, die sich auf das Endprodukt auswirken können. Einschränkungen sind architektonische Entscheidungen, die zuvor und für Sie getroffen wurden.

Als nächstes wenden wir uns den Attributen Qualität zu, an denen wir ein besonderes Interesse haben.

Qualitätsmerkmale


Sehr sichere Bibliothek


Es gibt viele Qualitätsmerkmale, Sie müssen sich nur die Liste ansehen, die Wikipedia uns gibt.



Die Geschichte basiert auf dem Attribut "Sicherheit". Wenn Sie die Bibliothek besuchen, erwarten Sie kaum, dass Sie mit einem Computer dort eine Zwei-Faktor-Autorisierung durchlaufen müssen, indem Sie E-Mail, Telefon und einen Bestätigungscode vom Telefon eingeben. Trotzdem passiert dies. Wir sehen, dass die blinde Anwendung von Qualitätsmerkmalen auch schwierig sein kann.

Telefon im Wald


Was ist mit Leistung? Es ist klar, dass es keine Menschen gibt, denen Leistung egal ist. Hier ist das Skript. Angenommen, eine Person möchte im Wald eine mobile Anwendung von ihrem Telefon aus verwenden. Somit betrifft es unser System, aber nicht das gesamte, sondern das Webinterface. Zum Beispiel muss er innerhalb von drei Sekunden einige Daten erhalten. Dies ist das Leistungsszenario, das wir bekommen sollten.



Es sind solche Anwendungsfälle, die ein Architekt sammeln muss, um am Ausgang ein qualitativ hochwertiges System aufzubauen. Wenn wir eine Liste mit Geschäftsanforderungen, Qualitätsmerkmalen und Einschränkungen haben, erkennen wir alle Stakeholder und beginnen, sie in Diagrammdiagrammen zu behandeln. Diese Adressierung von Attributen in Diagrammen wird als Architekturtaktik bezeichnet. Welche architektonischen Taktiken können basierend auf vorhandenen Szenarien auf ein Telefonsystem im Wald angewendet werden?

  • Verbessern Sie UX so, dass eine Person den Eindruck hat, dass die Produktivität höher ist.
  • Optimieren Sie Ressourcen (JS, CSS, Bilder, Schriftarten usw.).
  • Caching durchführen.
  • Fügen Sie Servicemitarbeiter hinzu, kritischer Pfad.
  • Komprimierung anwenden.
  • HTTP / 2.

Bei einem Telefon in der Gesamtstruktur passen UX und kritischer Pfad jedoch nicht sofort zu uns. Und wieder sollte die Taktik durch Skripte diktiert werden. Dies ist die Aufgabe des Architekten, aus allen im Einzelfall erforderlichen Taktiken auszuwählen. Eine Anwendung ist aber nicht nur ein Frontend. Die Leistung wird auch durch DNS, Backend und Datenbanken beeinflusst, und all dies kann ebenfalls optimiert werden. Es gibt viele Taktiken, um dies zu tun, aber auch hier hängt die Anwendung der einen oder anderen vom Verwendungsszenario ab.

Versuchen wir, das CQRS-Muster (Command Query Responsibility Segregation) zu verwenden. Die Verwendung dieses Musters als Taktik wirkt sich sogar auf mehrere Ebenen der Anwendung aus: sowohl das Back-End als auch das Front-End.



Angenommen, es gibt eine sehr langsame Legacy-Datenbank. Wir senden dort eine Anfrage und erhalten nach zehn Sekunden eine Antwort. Außerdem wird es repliziert und Sie müssen Einträge in beiden Kopien vornehmen. Wir im Wald wollen mit einem Telefon schnell unsere Daten aus dieser Datenbank lesen. Das Muster besagt, dass wir die Lese- und Schreibanforderungen trennen sollten. Fügen Sie eine schnell gelesene Datenbank hinzu. Wir fügen alle Mittel hinzu, um diese Datenbank mit einer vorhandenen zu synchronisieren.



Die Verwendung dieser Art von umständlicher Taktik sollte sehr klar von den Anforderungen bestimmt werden.

Also haben wir einige der Taktiken angewendet. Wir starten die Anwendung und stellen fest, dass sie nicht geholfen hat, da keine Internetverbindung besteht. So kamen wir zu der Fehlertoleranz, um die sich der Lösungsarchitekt kümmern sollte.

Unerschütterlicher Zinnsoldat


Niemand möchte, dass die Anwendung mehrmals am Tag stabil fällt, jeder möchte Fehlertoleranz.

Damit die Anwendung stabil funktioniert, kann das Fail-Fast-Prinzip angewendet werden:

  • Überprüfen Sie die Integrationspunkte immer im Voraus.
  • Vermeiden Sie langsame Verbindungen.
  • Überprüfen Sie Ihre Eingaben.

Schauen wir uns das Beispiel von Lie Connection an. Dies ist eine Verbindung, die vielleicht etwas pingt, aber in der Tat nicht funktioniert. Dies kann zwischen dem Client und dem Server, der Datenbank und dem Server zwischen den Diensten geschehen. Wenden Sie das Leistungsschaltermuster darauf an.



Während alles gut läuft, machen wir nichts. Sobald das Zeitlimit überschritten ist, gehen wir offline und stellen nach einer Weile eine neue Anfrage. Wenn die Anforderung erneut mit einem Fehler auftritt, bleiben wir erneut im Offline-Modus mit einer erhöhten Zeitüberschreitung. Und wenn die Anfrage gut gelaufen ist, kehren wir in den Online-Modus zurück. Somit ermöglicht dieses Muster die Überprüfung von Integrationspunkten und die Vermeidung langsamer Verbindungen.

U-Boot


Es gibt viele Ansätze zur Bereitstellung von Fehlertoleranz. Eines davon ist das Schott (Schott, zum Beispiel in einem U-Boot). Dies bedeutet, dass die Anwendung so geschrieben ist, dass sie auch dann weiter funktioniert, wenn in einer ihrer Komponenten ein Fehler aufgetreten ist.

Wir haben eine Geschäftslogik, in der wir Daten senden und eine Antwort erhalten. Wir fangen an, es mit unserer Taktik zu gestalten, Validierung hinzuzufügen, zu skalieren. In der Geschäftslogik ist ein Fehler aufgetreten. Wir protokollieren es. Als Fehlerinformation benötigen wir den Kontext, in dem sie aufgetreten ist. Das Problem ist, dass bei einem solchen Logikrahmen der Kontext wahrscheinlich nicht bestehen bleibt. Daher müssen wir einen Speicherauszug erstellen. Ein Speicherauszug ist ziemlich umfangreich, und JavaScript-Fehler treten nicht so selten auf. Daher handelt es sich um eine Strategie, die für die Berechnung von Ressourcen recht teuer ist. Wir müssen die Fehler in kritische und nicht kritische Fehler aufteilen und nur für die ersten ausgeben.

Undichter Eimer


Eine Strategie ähnlich der oben beschriebenen wird im Leaky Bucket-Muster verwendet.



Wir haben Zähler für verschiedene Arten von Fehlern. Wenn ein Fehler auftritt, wird ein Fehlerzähler dieses Typs inkrementiert. Mit dem Timeout nimmt dieser Zähler ab. Wenn jedoch die Anzahl der Fehler von der Skala abweicht, hat der Zähler keine Zeit zum Verringern und läuft im übertragenen Sinne über den Bucket, wonach wir einen Speicherauszug erstellen. Als nächstes arbeiten wir an dem Fehler, der den Überlauf des Zählers verursacht hat.

Bitte beachten Sie, dass der Dienst, der dieses Muster implementiert, auch in Taktiken und Validierungen eingebunden und skaliert wird. Architektur wird also gebaut.



Was bedeuten grüne Pfeile? Verschiedene Dienste interagieren über verschiedene Protokolle, Datenbanken und andere Dienste miteinander.

, , «Patterns for fault tolerant software», — Robert S. Hanmer.



«Software Architecture in Practice», — Len Bass, Paul Clements, Rick Kazman. .






  • .
  • 400 .
  • , .
  • - .
  • Drupal.

, :



. .

— .

- — ( ).
: , -.

:

  • Drupal, - .
  • ReactJS VueJS, , , .

quality-, :

  • 400 (maintainability).
  • , (testability).
  • (re-usability).
  • (performance).
  • (accessibility).

, . C4. .

:



, . , -.

:



-, -, . , - -. .

:



, , Template Service, . - . , , , -.

-? - . , Template Service. , Drupal -. , NuxtJS Next.js, , , Vue.js React.js. GitHub , , JAM-. JavaScript, API, Markdown.



, , , . Node.js - . Drupal -, , CMS, , , . Javascript, API, Markdown .

Fazit

. .




, , , . . , .

?

, , , . -, .

Fazit

, , .



Zusammenfassung


, . . , , , :

  • (stakeholders).
  • - .
  • , . , .
  • .
  • .
  • , .

, : 24-25 HolyJS , . — , .

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


All Articles