So lernen Sie Software-Design und Architektur - eine Roadmap

Dieser Artikel ist eine Zusammenfassung dessen, worüber ich in meinem neuesten Projekt, solidbook.io - Das Handbuch zu Software-Design und -Architektur mit TypeScript, schreibe. Probieren Sie es aus, wenn Ihnen dieser Beitrag gefällt.

Es ist verrückt für mich, die Tatsache zu berücksichtigen, dass Facebook einst eine leere Textdatei auf einem Computer war.

Lol.

Im vergangenen Jahr habe ich mich intensiv mit Software-Design und -Architektur, Domain-Driven Design und dem Schreiben eines Buches beschäftigt. Ich wollte mir einen Moment Zeit nehmen, um zu versuchen, es zu etwas Nützlichem zusammenzusetzen, das ich mit der Community teilen kann .

Hier ist meine Roadmap zum Erlernen von Software-Design und -Architektur.

Ich habe es in zwei Artefakte unterteilt: den Stapel und die Karte .

Der Stapel

Ähnlich wie beim OSI-Modell im Netzwerk baut jede Schicht auf der Grundlage der vorherigen auf.

Der Stapel

Die Karte

Während ich denke, dass der Stapel gut ist, um das Gesamtbild zu sehen, wie alles zusammenarbeitet, ist die Karte etwas detaillierter (und von der Roadmap der Webentwickler inspiriert) und daher denke ich, dass sie nützlicher ist.

Hier ist es unten! Um das Repo zu teilen, lesen Sie meine ausführliche Beschreibung und laden Sie sie hochauflösend herunter. Klicken Sie hier.

Software Design und Architektur Roadmap

Stufe 1: Code bereinigen

Der allererste Schritt zur Erstellung langlebiger Software besteht darin, herauszufinden, wie sauberer Code geschrieben werden kann .

Sauberer Code ist Code, der leicht zu verstehen und zu ändern ist. Auf niedriger Ebene manifestiert sich dies in einigen Designoptionen wie:

  • konsequent sein
  • Bevorzugen Sie aussagekräftige Variablen-, Methoden- und Klassennamen gegenüber dem Schreiben von Kommentaren
  • Sicherstellen, dass der Code richtig eingerückt und beabstandet ist
  • Sicherstellen, dass alle Tests ausgeführt werden können
  • Schreiben von reinen Funktionen ohne Nebenwirkungen
  • nicht null übergeben

Das Schreiben von sauberem Code ist unglaublich wichtig.

Betrachten Sie es als eine Partie Jenga.

Um die Struktur unseres Projekts über die Zeit stabil zu halten, zahlen sich Dinge wie Einrückungen, kleine Klassen und Methoden sowie aussagekräftige Namen auf lange Sicht viel aus.

Die beste Quelle, um zu lernen, wie man sauberen Code schreibt, ist Onkel Bobs Buch "Clean Code".

Stufe 2: Programmierparadigmen

Jetzt, da wir lesbaren Code schreiben, der einfach zu warten ist, wäre es eine gute Idee, die drei wichtigsten Programmierparadigmen und die Art und Weise, wie sie das Schreiben von Code beeinflussen, wirklich zu verstehen.

In Onkel Bobs Buch "Clean Architecture" macht er darauf aufmerksam, dass:

  • Die objektorientierte Programmierung ist das Werkzeug, das am besten geeignet ist, um zu definieren, wie wir mit Polymorphismus und Plugins architektonische Grenzen überschreiten
  • Funktionale Programmierung ist das Werkzeug, mit dem wir Daten an die Grenzen unserer Anwendungen bringen
  • und Strukturierte Programmierung ist das Werkzeug, mit dem wir Algorithmen schreiben

Dies impliziert, dass effektive Software alle drei Programmierparadigmenstile zu unterschiedlichen Zeiten verwendet.

Während Sie könnten eine streng funktionale oder streng objektorientierten Ansatz das Schreiben von Code zu nehmen, zu verstehen , wo jeder zeichnet sich die Qualität Ihres Designs verbessern.

Wenn Sie nur einen Hammer haben, scheint alles wie ein Nagel.

Ressourcen

Informationen zur funktionalen Programmierung finden Sie unter:

  • Professor Frisbys größtenteils angemessener Leitfaden zur funktionalen Programmierung
  • Domänenmodellierung funktionsfähig gemacht

Stufe 3: Objektorientierte Programmierung

Es ist wichtig zu wissen, wie jedes der Paradigmen funktioniert und wie sie Sie dazu drängen, den Code in ihnen zu strukturieren. In Bezug auf die Architektur ist die objektorientierte Programmierung jedoch das klare Werkzeug für diesen Job .

Objektorientierte Programmierung ermöglicht es uns nicht nur, eine Plugin-Architektur zu erstellen und Flexibilität in unsere Projekte einzubauen. OOP enthält die 4 Prinzipien von OOP (Kapselung, Vererbung, Polymorhismus und Abstraktion), mit denen wir umfangreiche Domänenmodelle erstellen können .

Die meisten Entwickler, die objektorientiertes Programmieren lernen, kommen nie zu diesem Teil: Sie lernen, wie Sie eine Softwareimplementierung der Problemdomäne erstellen und diese in der Mitte einer mehrschichtigen Web-App lokalisieren .

Funktionale Programmierung kann in diesem Szenario als Mittel zu allen Zwecken erscheinen, aber ich würde empfehlen, sich mit modellgetriebenem Design und domänengesteuertem Design vertraut zu machen, um das Gesamtbild zu verstehen, wie Objektmodellierer ein gesamtes Unternehmen in einem Szenario zusammenfassen können Domänenmodell ohne Abhängigkeit.

Warum ist das eine große Sache?

Es ist riesig, denn wenn Sie ein mentales Modell eines Unternehmens erstellen können, können Sie eine Software-Implementierung dieses Unternehmens erstellen.

Stufe 4: Gestaltungsprinzipien

An diesem Punkt verstehen Sie, dass die objektorientierte Programmierung sehr nützlich ist, um Rich-Domain-Modelle zu kapseln und den dritten Typ von "Hard-Software-Problemen" - komplexe Domänen - zu lösen.

OOP kann jedoch einige Designherausforderungen mit sich bringen.

Wann sollte ich Komposition verwenden?

Wann sollte ich die Vererbung verwenden?

Wann sollte ich eine abstrakte Klasse verwenden?

Designprinzipien sind wirklich gut etablierte und kampferprobte objektorientierte Best Practices, die Sie als Railguards verwenden.

Einige Beispiele für gängige Gestaltungsprinzipien, mit denen Sie sich vertraut machen sollten, sind:

  • Zusammensetzung über Vererbung
  • Fassen Sie zusammen, was variiert
  • Programm gegen Abstraktionen, nicht Konkretionen
  • Das Hollywood-Prinzip: "Rufen Sie uns nicht an, wir rufen Sie an"
  • Die SOLID-Prinzipien, insbesondere das Prinzip der Einzelverantwortung
  • TROCKEN (Wiederholen Sie sich nicht)
  • YAGNI (Du wirst es nicht brauchen)

Stellen Sie jedoch sicher, dass Sie zu Ihren eigenen Schlussfolgerungen kommen. Folgen Sie nicht einfach dem, was jemand anderes Ihnen sagt. Stellen Sie sicher, dass es für Sie sinnvoll ist.

Stufe 5: Entwurfsmuster

Nahezu jedes Problem in der Software wurde bereits kategorisiert und gelöst. Wir nennen diese Muster eigentlich Designmuster.

Es gibt drei Kategorien von Entwurfsmustern: Kreation , Struktur und Verhalten .

Kreativ

Erstellungsmuster sind Muster, die steuern, wie Objekte erstellt werden.

Beispiele für Kreationsmuster sind:

  • Das Singleton-Muster , um sicherzustellen, dass nur eine einzige Instanz einer Klasse vorhanden sein kann
  • Das Abstract Factory-Muster zum Erstellen einer Instanz mehrerer Klassenfamilien
  • Das Prototypmuster , um mit einer Instanz zu beginnen, die von einer vorhandenen Instanz geklont wurde

Strukturell

Strukturmuster sind Muster, die die Definition von Beziehungen zwischen Komponenten vereinfachen.

Beispiele für strukturelle Entwurfsmuster umfassen:

  • Das Adaptermuster zum Erstellen einer Schnittstelle, mit der Klassen, die normalerweise nicht zusammenarbeiten können, zusammenarbeiten können.
  • Das Bridge-Muster zum Aufteilen einer Klasse, die eigentlich eine oder mehrere sein sollte, in eine Reihe von Klassen, die zu einer Hierarchie gehören, sodass die Implementierungen unabhängig voneinander entwickelt werden können.
  • Das Decorator-Muster zum dynamischen Hinzufügen von Verantwortlichkeiten zu Objekten.

Verhalten

Verhaltensmuster sind übliche Muster zur Erleichterung einer eleganten Kommunikation zwischen Objekten.

Beispiele für Verhaltensmuster sind:

  • Das Vorlagenmuster zum Verschieben der genauen Schritte eines Algorithmus auf eine Unterklasse.
  • Das Mediator-Muster zum Definieren der genauen Kommunikationskanäle, die zwischen Klassen zulässig sind.
  • Das Observer-Muster , mit dem Klassen etwas Interessantes abonnieren und benachrichtigt werden können, wenn eine Änderung eingetreten ist.

Designmusterkritik

Designmuster sind großartig, aber manchmal können sie unsere Designs zusätzlich komplexer machen. Es ist wichtig, sich an YAGNI zu erinnern und zu versuchen, unsere Designs so einfach wie möglich zu halten. Verwenden Sie Designmuster nur, wenn Sie wirklich sicher sind, dass Sie sie benötigen. Sie werden wissen, wann Sie werden.

Wenn wir wissen, was jedes dieser Muster ist, wann wir sie verwenden müssen und wann wir sie nicht einmal verwenden müssen, sind wir in guter Verfassung, um zu verstehen, wie größere Systeme zu erstellen sind.

Der Grund dafür ist, dass Architekturmuster nur Entwurfsmuster sind, die im Maßstab auf die hohe Ebene vergrößert werden, wobei Entwurfsmuster Implementierungen auf niedriger Ebene sind (näher an Klassen und Funktionen).

Ressourcen

Refactoring Guru - Design Patterns

Stufe 6: Architekturprinzipien

Jetzt sind wir auf einer höheren Ebene des Denkens als die Klassenebene.

Wir wissen jetzt, dass die Entscheidungen, die wir treffen, um Beziehungen zwischen Komponenten auf hoher und niedriger Ebene zu organisieren und aufzubauen, einen erheblichen Einfluss auf die Wartbarkeit, Flexibilität und Testbarkeit unseres Projekts haben werden.

Lernen Sie die Leitprinzipien kennen, mit denen Sie die Flexibilität aufbauen können, die Ihre Codebasis benötigt, um mit möglichst geringem Aufwand auf neue Funktionen und Anforderungen reagieren zu können.

Folgendes würde ich empfehlen, um sofort zu lernen:

  • Konstruktionsprinzipien für Komponenten: Das Prinzip der stabilen Abstraktion, das Prinzip der stabilen Abhängigkeit und das Prinzip der azyklischen Abhängigkeit, um zu organisieren, wie Komponenten organisiert werden, wann sie gekoppelt werden müssen und welche Auswirkungen es hat, versehentlich Abhängigkeitszyklen zu erstellen und sich auf instabile Komponenten zu verlassen.
  • Richtlinie vs. Detail, um zu verstehen, wie Sie die Regeln Ihrer Anwendung von den Implementierungsdetails trennen.
  • Grenzen und wie Sie die Subdomains identifizieren, zu denen die Funktionen Ihrer Anwendung gehören.

Onkel Bob hat viele dieser Prinzipien entdeckt und ursprünglich dokumentiert. Die beste Quelle, um mehr darüber zu erfahren, ist "Clean Architecture".

Stufe 7: Architekturstile

In der Architektur geht es um das Wesentliche.

Es geht darum, zu identifizieren, was ein System benötigt, um erfolgreich zu sein, und dann die Erfolgsaussichten zu stapeln, indem die Architektur ausgewählt wird, die den Anforderungen am besten entspricht.

Beispielsweise würde ein System mit einer hohen Komplexität der Geschäftslogik von der Verwendung einer Schichtarchitektur profitieren , um diese Komplexität zu kapseln.

Ein System wie Uber muss in der Lage sein, viele Echtzeitereignisse gleichzeitig zu verarbeiten und die Standorte der Treiber zu aktualisieren, sodass die Architektur im Publish-Subscribe- Stil möglicherweise am effektivsten ist.

Ich werde mich hier wiederholen, weil es wichtig ist zu beachten, dass die 3 Kategorien von Architekturstilen den 3 Kategorien von Designmustern ähnlich sind, da Architekturstile Designmuster auf hoher Ebene sind .

Strukturell

Projekte mit unterschiedlichen Komponentenstufen und weitreichenden Funktionen werden entweder von einer strukturellen Architektur profitieren oder darunter leiden.

Hier einige Beispiele:

  • Komponentenbasierte Architekturen betonen die Trennung von Bedenken zwischen den einzelnen Komponenten innerhalb eines Systems. Denken Sie eine Sekunde lang an Google . Überlegen Sie, wie viele Anwendungen sich in Ihrem Unternehmen befinden (Google Text & Tabellen, Google Drive, Google Maps usw.). Bei Plattformen mit vielen Funktionen teilen komponentenbasierte Architekturen die Probleme in lose gekoppelte unabhängige Komponenten auf. Dies ist eine horizontale Trennung.
  • Monolithisch bedeutet, dass die Anwendung zu einer einzigen Plattform oder einem einzigen Programm zusammengefasst und insgesamt bereitgestellt wird. Hinweis: Sie können eine komponentenbasierte UND monolithische Architektur verwenden, wenn Sie Ihre Anwendungen ordnungsgemäß trennen und dennoch alles als ein Stück bereitstellen .
  • Schichtarchitekturen trennen die Probleme vertikal, indem sie Software in Infrastruktur-, Anwendungs- und Domänenebenen unterteilen.

Saubere Architektur

Ein Beispiel für das vertikale Schneiden der Probleme einer Anwendung mithilfe einer mehrschichtigen Architektur. Lesen Sie hier, um weitere Informationen dazu zu erhalten.

Messaging

Abhängig von Ihrem Projekt kann Messaging eine wirklich wichtige Komponente für den Erfolg des Systems sein. Bei Projekten wie diesem bauen nachrichtenbasierte Architekturen auf funktionalen Programmierprinzipien und Verhaltensentwurfsmustern wie dem Beobachtermuster auf.

Hier einige Beispiele für nachrichtenbasierte Architekturstile:

  • Ereignisgesteuerte Architekturen zeigen alle wesentlichen Statusänderungen als Ereignisse an. Beispielsweise kann sich innerhalb einer Vinyl-Handels-App der Status eines Angebots von "ausstehend" zu "angenommen" ändern, wenn beide Parteien den Handel vereinbaren.
  • Publish-Subscribe- Architekturen bauen auf dem Observer-Entwurfsmuster auf, indem sie es zur primären Kommunikationsmethode zwischen dem System selbst, Endbenutzern / Clients und anderen Systemen und Komponenten machen.

Verteilt

Eine verteilte Architektur bedeutet einfach, dass die Komponenten des Systems separat bereitgestellt werden und durch Kommunikation über ein Netzwerkprotokoll betrieben werden. Verteilte Systeme können sehr effektiv sein, um den Durchsatz zu skalieren, Teams zu skalieren und (möglicherweise teure Aufgaben oder) Verantwortung an andere Komponenten zu delegieren.

Einige Beispiele für verteilte Architekturstile sind:

  • Client-Server- Architektur. Eine der gängigsten Architekturen, bei der wir die zu erledigende Arbeit zwischen dem Client (Präsentation) und dem Server (Geschäftslogik) aufteilen.
  • Peer-to-Peer- Architekturen verteilen Aufgaben auf Anwendungsebene auf gleichberechtigte Teilnehmer und bilden ein Peer-to-Peer-Netzwerk.

Stufe 8: Architekturmuster

Architekturmuster erklären in größeren taktischen Detail , wie eigentlich eine jener architektonischen umzusetzen Stile .

Hier einige Beispiele für Architekturmuster und die Stile, von denen sie erben:

  • Domain-Driven Design ist ein Ansatz zur Softwareentwicklung gegen wirklich komplexe Problemdomänen. Damit DDD am erfolgreichsten ist, müssen wir eine mehrschichtige Architektur implementieren , um die Bedenken eines Domänenmodells von den infrastruralen Details zu trennen, die die Anwendung tatsächlich ausführen lassen, wie Datenbanken, Webserver, Caches usw.
  • Model-View Controller ist wahrscheinlich das bekannteste Architekturmuster für die Entwicklung benutzeroberflächenbasierter Anwendungen. Die App wird in drei Komponenten unterteilt: Modell, Ansicht und Controller. MVC ist unglaublich nützlich, wenn Sie zum ersten Mal anfangen, und es hilft Ihnen, sich auf andere Architekturen zu konzentrieren, aber irgendwann stellen wir fest, dass MVC für Probleme mit viel Geschäftslogik nicht ausreicht.
  • Event Sourcing ist ein funktionaler Ansatz, bei dem nur die Transaktionen und niemals der Status gespeichert werden. Wenn wir jemals den Staat brauchen, können wir alle Transaktionen von Anfang an anwenden.

Stufe 9: Unternehmensmuster

Jedes Architekturmuster, das Sie auswählen, führt eine Reihe von Konstrukten und Fachjargon ein, mit denen Sie sich vertraut machen und entscheiden können, ob sich die Verwendung lohnt oder nicht.

Anhand eines Beispiels, von dem viele von uns wissen, dass die Ansicht in MVC den gesamten Code der Präsentationsschicht enthält, übersetzt der Controller Befehle und Abfragen aus der Ansicht in Anforderungen, die vom Modell verarbeitet und vom Controller zurückgegeben werden .

Wo im Modell (M) behandeln wir diese Dinge?:

  • Validierungslogik
  • invariante Regeln
  • Domain-Ereignisse
  • Anwendungsfälle
  • komplexe Abfragen
  • und Geschäftslogik

Wenn wir einfach einen ORM (objektrelationalen Mapper) wie Sequelize oder TypeORM als Modell verwenden , bleibt all das Wichtige der Interpretation überlassen, wohin es gehen soll, und es befindet sich in einer nicht spezifizierten Schicht dazwischen (was sollte ein Rich sein) ) Modell und die Steuerung .

mvc-2

Entnommen aus "3.1 - Schlanke (logiklose) Modelle" in solidbook.io.

Wenn es etwas gibt, das ich bisher auf meiner Reise über MVC hinaus gelernt habe, dann ist es, dass es für alles ein Konstrukt gibt .

Für jedes dieser Dinge, die MVC nicht anspricht, gibt es andere Unternehmensmuster , um sie zu lösen. Zum Beispiel:

  • Entitäten beschreiben Modelle, die eine Identität haben.
  • Wertobjekte sind Modelle ohne Identität und können verwendet werden, um die Validierungslogik zu kapseln.
  • Domänenereignisse sind Ereignisse, die auf ein relevantes Geschäftsereignis hinweisen und von anderen Komponenten abonniert werden können.

Abhängig von dem von Ihnen gewählten Architekturstil müssen Sie eine Menge anderer Unternehmensmuster lernen, um dieses Muster in vollem Umfang umzusetzen.

Integrationsmuster

Sobald Ihre Anwendung betriebsbereit ist und immer mehr Benutzer verfügbar sind, können Leistungsprobleme auftreten. API-Aufrufe können lange dauern, Server können durch Überlastung mit Anforderungen usw. abstürzen. Um diese Probleme zu lösen, lesen Sie möglicherweise Informationen zur Integration von Nachrichtenwarteschlangen oder Caches , um die Leistung zu verbessern.

Dies ist wahrscheinlich die größte Herausforderung: Skalierung, Audits und Leistung .

Das Entwerfen eines Systems für die Skalierung kann eine unglaubliche Herausforderung sein. Es erfordert ein tiefes Verständnis der Einschränkungen der einzelnen Komponenten innerhalb der Architektur sowie einen Aktionsplan, wie Sie die Belastung Ihrer Architektur verringern und Anforderungen in Situationen mit hohem Datenverkehr weiterhin bearbeiten können.

Die Notwendigkeit, auch zu prüfen, was in Ihrer Anwendung vor sich geht. Große Unternehmen müssen in der Lage sein, Audits durchzuführen, um potenzielle Sicherheitsprobleme zu identifizieren, zu verstehen, wie Benutzer ihre Anwendungen verwenden, und ein Protokoll über alles zu haben, was jemals passiert ist.

Die Implementierung kann schwierig sein, aber gängige Architekturen sehen letztendlich ereignisbasiert aus und bauen auf einer Vielzahl von Software- und Systemdesignkonzepten, -prinzipien und -praktiken wie Event Storming, DDD, CQRS (Command Query Response Segregation) und Event Sourcing auf .

Ich hoffe das war nützlich für dich!

Lassen Sie mich wissen, wenn Sie Vorschläge oder Fragen haben.

Prost!

Fork es auf GitHub

Lesen Sie das Buch über Software-Design und -Architektur

Lesen Sie den Artikel

khalilstemmler.com - Ich unterrichte Advanced TypeScript & Node.js Best Practices für Großanwendungen und das Schreiben flexibler, wartbarer Software.