Saubere Architektur in Next.js
Dieses Repo ist ein Beispiel dafür, wie man eine saubere Architektur in Next.js. Es gibt ein Video -Tutorial, das dieses Projekt durchläuft. Klicken Sie auf das Bild, um es auf YouTube zu überprüfen:
Sie können das Projekt ausführen, indem Sie npm install und npm run dev ausführen.
Saubere Architektur

Notiz
? Ich habe diese vereinfachte Version des ursprünglichen Clean Architecture Diagramms gezeichnet. Ich habe es auf eine Weise vereinfacht, die für mich sinnvoller ist, und es ist einfacher zu verstehen. Ich hoffe es hilft dir auch.
Ich empfehle Ihnen dringend, den Originalartikel von Onkel Bob zu lesen, wenn dies Sie zum ersten Mal über saubere Architektur hören, aber ich werde versuchen, ihn unten für Sie zusammenzufassen.
Clean Architecture ist eine Reihe von Regeln , die uns helfen, unsere Anwendungen so zu strukturieren, dass sie leichter zu warten und zu testen sind, und ihre Codebasen sind vorhersehbar. Es ist wie eine gemeinsame Sprache, die Entwickler verstehen, unabhängig von ihren technischen Hintergründen und Programmiersprachenpräferenzen.
Saubere Architektur und ähnliche/abgeleitete Architekturen haben alle das gleiche Ziel - die Trennung von Bedenken . Sie führen Ebenen ein, die einen ähnlichen Code zusammenbündet. Das "Schichten" hilft uns, wichtige Aspekte in unserer Codebasis zu erreichen:
- Unabhängig von der UI - die Geschäftslogik ist nicht mit dem UI -Framework verbunden (in diesem Fall als nächstes.js). Das gleiche System kann in einer CLI -Anwendung verwendet werden, ohne die Geschäftslogik oder Regeln ändern zu müssen.
- Unabhängig von der Datenbank - die Datenbankimplementierung/-vorgänge sind in ihrer eigenen Ebene isoliert, sodass der Rest der App nicht wichtig ist, welche Datenbank verwendet wird, sondern mithilfe von Modellen kommuniziert.
- Unabhängig von Frameworks wissen die Geschäftsregeln und die Logik einfach nichts über die Außenwelt. Sie empfangen Daten, die mit Plain JavaScript definiert sind, und verwenden einfache JavaScript, Dienste und Repositorys, um ihre eigene Logik und Funktionalität zu definieren. Auf diese Weise können wir Frameworks als Tools verwenden, anstatt unser System in ihre Implementierungen und Einschränkungen zu "formen". Wenn wir Routenhandler in unserer App verwenden und einige von ihnen auf Serveraktionen neu umsetzen möchten, müssen wir nur die spezifischen Controller in einer Serveraktion anstelle eines Routenhandlers aufrufen, aber die Kerngeschäftslogik bleibt unverändert.
- Testbar - Die Geschäftslogik und die Regeln können problemlos getestet werden, da sie nicht vom UI -Framework, der Datenbank oder dem Webserver oder einem anderen externen Element abhängt, das unser System erstellt.
Clean Architecture erreicht dies durch die Definition einer Abhängigkeitshierarchie - Schichten hängen nur von Schichten unter ihnen ab, aber nicht oben.
Projektstruktur (nur die wichtigen Bits)
-
app - Frameworks & Trivers Layer - Grundsätzlich alles als Next.js (Seiten, Serveraktionen, Komponenten, Stile usw.) oder was auch immer "konsumiert" die Logik der App -
di - Abhängigkeitsinjektion - Ein Ordner, in dem wir den DI -Behälter und die Module einrichten -
drizzle - alles DB - Initialisierung des DB -Clients, Definieren von Schema, Migrationen -
src - die "Wurzel" des Systems-
application - Anwendungsschicht - Hält Anwendungsfälle und Schnittstellen für Repositorys und Dienste -
entities - Entitäten Ebene - enthält Modelle und benutzerdefinierte Fehler -
infrastructure - Infrastrukturschicht - enthält Implementierungen von Repositorys und Diensten und zieht die Schnittstellen aus application an -
interface-adapters - Schnittstellenadapterschicht - Hält Controller, die als Einstiegspunkt für das System dienen (verwendet in Frameworks & Drivers Layer, um mit dem System zu interagieren)
-
tests - Unit -Tests hier live - Die Struktur des unit entspricht src -
.eslintrc.json - wo das Plugin eslint-plugin-boundaries definiert ist - dies hält Sie daran, die Abhängigkeitsregel zu brechen -
vitest.config.ts - Beachten Sie, wie der @ alias definiert ist!
Schichten Erklärung
- Frameworks & Treiber : Hält alle UI -Framework -Funktionen und alles andere, was mit dem System interagiert (z. B. AWS Lambdas, Stripe Webhooks usw.). In diesem Szenario ist das nächste.
- Diese Ebene sollte nur Controller , Modelle und Fehler verwenden und dürfen keine Anwendungsfälle , Repositorys und Dienste verwenden.
- Schnittstellenadapter : Definiert Controller :
- Controller führen Authentifizierungsüberprüfungen und Eingabevalidierung durch, bevor die Eingabe an die spezifischen Anwendungsfälle weitergegeben werden.
- Controller orchestrieren Anwendungsfälle. Sie implementieren keine Logik, definieren jedoch die gesamten Operationen anhand von Anwendungsfällen.
- Fehler aus tieferen Schichten werden aufgeblasen und gehandhabt, wo Controller verwendet werden.
- Controller verwenden Moderatoren , um die Daten kurz vor der Rückgabe an den "Verbraucher" in ein UI-freundliches Format umzuwandeln. Dies hilft uns, weniger JavaScript an den Client zu versenden (Logik und Bibliotheken, um die Daten zu konvertieren), hilft zu verhindern, dass sensible Eigenschaften wie E -Mails oder Hash -Passwörter ausgelöst werden, und hilft uns auch, die Datenmenge, die wir an den Client zurücksenden, zu senken.
- Anwendung : Wo die Geschäftslogik lebt. Manchmal als Kern bezeichnet. Diese Schicht definiert die Anwendungsfälle und Schnittstellen für die Dienste und Repositorys.
- Anwendungsfälle :
- Stellen Sie einzelne Operationen dar, z. B. "Erstellen Sie Todo" oder "Anmelden in" oder "Toggle Todo".
- Akzeptieren Sie die vorvalidierte Eingabe (von Controllern) und führen Sie die Autorisierungsprüfungen durch .
- Verwenden Sie Repositorys und Dienste , um auf Datenquellen zuzugreifen und mit externen Systemen zu kommunizieren.
- Anwendungsfälle sollten keine anderen Anwendungsfälle verwenden . Das ist ein Codegeruch. Dies bedeutet, dass der Anwendungsfall mehrere Dinge erledigt und in mehrere Anwendungsfälle unterteilt werden sollte.
- Schnittstellen für Repositorys und Dienste:
- Diese werden in dieser Ebene definiert, da wir die Abhängigkeit ihrer Tools und Frameworks (Datenbanktreiber, E -Mail -Dienste usw.) ausbrechen möchten. Wir werden sie daher in der Infrastrukturschicht implementieren.
- Da die Schnittstellen in dieser Ebene leben, können Anwendungsfälle (und transitiv die oberen Schichten) durch Abhängigkeitsinjektion auf sie zugreifen.
- Die Abhängigkeitsinjektion ermöglicht es uns, die Definitionen (Schnittstellen) aus den Implementierungen (Klassen) aufzuteilen und sie in einer separaten Ebene (Infrastruktur) zu halten, aber dennoch deren Verwendung zulassen.
- Entitäten : Wo die Modelle und Fehler definiert werden.
- Modelle :
- Definieren Sie "Domänen" -Datenformen mit einfachem JavaScript, ohne "Datenbank" -Technologien zu verwenden.
- Modelle sind nicht immer an die Datenbank gebunden. Das Senden von E -Mails erfordern einen externen E -Mail -Dienst, keine Datenbank, aber wir müssen noch eine Datenform haben, die anderen Ebenen hilft, "eine E -Mail zu senden" zu kommunizieren.
- Modelle definieren auch ihre eigenen Validierungsregeln, die als "Unternehmensgeschäftsregeln" bezeichnet werden. Regeln, die sich normalerweise nicht ändern oder am wenigsten ändern, wenn sich extern ändert (Seitennavigation, Sicherheit usw.). Ein Beispiel ist ein
User , das ein Benutzernamefeld definiert, das mindestens 6 Zeichen lang sein muss und keine Sonderzeichen enthalten .
- Fehler :
- Wir wollen unsere eigenen Fehler, weil wir keine datenbankspezifischen Fehler oder irgendeine Art von Fehlern, die für eine Bibliothek oder ein Framework spezifisch sind, aufblasen möchten.
- Wir
catch Fehler auf, die aus anderen Bibliotheken stammen (z. B. Nieselregen), und konvertieren diese Fehler in unsere eigenen Fehler. - So können wir unseren Kern unabhängig von allen Frameworks, Bibliotheken und Technologien halten - einem der wichtigsten Aspekte der sauberen Architektur.
- Infrastruktur : Wo Repositorys und Dienste definiert werden.
- Diese Schicht zieht die Schnittstellen von Repositorys und Diensten aus der Anwendungsschicht an und implementiert sie in ihren eigenen Klassen.
- Repositories sind, wie wir die Datenbankvorgänge implementieren. Es handelt sich um Klassen, die Methoden enthüllen, die einen einzelnen Datenbankvorgang ausführen - wie
getTodo , createTodo oder updateTodo . Dies bedeutet, dass wir nur die Datenbankbibliothek / den Treiber in diesen Klassen verwenden. Sie führen keine Datenvalidierung durch, führen Sie einfach Abfragen und Mutationen gegen die Datenbank aus und werfen entweder unsere benutzerdefinierten definierten Fehler oder geben Sie die Ergebnisse zurück. - Dienste sind gemeinsam genutzte Dienste, die in der Anwendung verwendet werden - wie ein Authentifizierungsdienst oder E -Mail -Service oder implementieren externe Systeme wie Stripe (Erstellen Sie Zahlungen, validieren Sie Quittungen usw.). Diese Dienste nutzen auch andere Frameworks und Bibliotheken. Deshalb wird ihre Implementierung hier neben den Repositorys aufbewahrt.
- Da wir nicht möchten, dass eine Ebene von diesem abhängt (und transitiv von der Datenbank und allen Diensten abhängt), verwenden wir das Abhängigkeitsinversionsprinzip . Auf diese Weise können wir nur von den in der Anwendungsschicht definierten Schnittstellen anstelle der Implementierungen in der Infrastrukturschicht abhängen. Wir verwenden eine Inversion der Kontrollbibliothek wie Ioctopus, um die Implementierung hinter den Schnittstellen abstrahieren und sie "injizieren", wann immer wir sie brauchen. Wir erstellen die Abstraktion im
di -Verzeichnis. Wir "binden" die Repositorys, Dienste, Controller und Anwendungsfälle an Symbole und "lösen" sie anhand dieser Symbole, wenn wir die tatsächliche Implementierung benötigen. So können wir die Implementierung verwenden, ohne sie explizit davon abhängen zu müssen (importieren).
FAQ
Tipp
Wenn Sie eine Frage haben, die nicht von den FAQs abgedeckt ist, können Sie entweder ein Problem in diesem Repo eröffnen oder meinem Discord -Server beigetreten und dort eine Konversation starten.
Ist Clean Architecture / diese Implementierung serverlos-freundlich? Kann ich dies für Vercel bereitstellen?
Ja! Sie können es mit Page -Router, App -Router, Middleware, API -Handlern, Serveraktionen und allem verwenden! In der Regel wird die Erlangung der Abhängigkeitsinjektion in JavaScript -Projekten mit der Inversify.js -Bibliothek durchgeführt, die mit anderen Laufzeiten mit Ausnahme des Knotens nicht kompatibel ist. Dieses Projekt implementiert Ioctopus, einen einfachen IOC-Container, der nicht auf reflect-metadata beruht und auf allen Laufzeiten funktioniert.
Sollte ich sofort mit der Implementierung sauberer Architektur beginnen, wenn ich mein nächstes.js -Projekt erstelle?
Ich würde nein sagen. Wenn Sie ein brandneues Projekt starten, würde ich Ihnen raten, sich darauf zu konzentrieren, einen MVP -Status so schnell wie möglich zu erreichen (damit Sie Ihre Idee validieren / sehen können, ob es eine Zukunft für Ihr Projekt gibt). Wenn die Dinge ernst werden (weitere Funktionen werden implementiert, Ihre Benutzerbasis erlebt ein erhebliches Wachstum oder Sie haben andere Entwickler in Ihrem Projekt an Bord), dann möchten Sie einige Zeit in die Anpassung dieser Architektur (oder einer Architektur) investieren.
Wenn Sie bereits tief im Unkraut eines Projekts sind, können Sie (und Ihr Team) ab dem nächsten Sprint schrittweise refactoring planen. In diesem Fall haben Sie bereits den Code geschrieben, Sie müssen ihn nur ein wenig umstrukturieren, und Sie können diesen Teil per Teil, Routenhandler mit Routenhandler, Server -Aktion nach Serveraktionen durchführen. Übrigens sage ich es leicht: "Sie müssen es nur ein wenig neu organisieren" , aber es kann weit davon entfernt sein, so einfach so einfach zu sein. Berücksichtigen Sie immer "Dinge, die schief gehen", wenn Sie das Refactoring planen. Und etwas Zeit für das Schreiben von Tests!
Dies sieht aus wie überginewirtschaftlich und kompliziert die Entwicklung von Funktionen.
Wenn Sie nicht länger als 3 Minuten darüber nachdenken, dann sieht es nach übergineerahen aus. Aber wenn Sie dies tun, werden Sie diese Architektur = Disziplin erkennen. Die Architektur ist ein Vertrag zwischen den Entwicklern, der definiert, was wohin geht. Es vereinfacht tatsächlich die Funktionsentwicklung, da die Codebasis vorhersehbar ist und diese Entscheidungen für Sie trifft.
Sie können ein Projekt nicht nachhaltig anbauen, wenn jeder Entwickler, der daran arbeitet, Code schreibt, wo es am bequemsten ist. Die Codebasis wird zu einem Albtraum, mit dem Sie arbeiten können, und dann fühlen Sie sich einen wirklich komplizierten Feature -Entwicklungsprozess. Um dies zu bekämpfen, werden Sie irgendwann einige Regeln festlegen. Diese Regeln werden wachsen, wenn Ihr Team konfrontiert ist und neue Probleme löst. Legen Sie all diese Regeln in ein Dokument ein, und es gibt Ihre eigene Architekturdefinition. Sie implementieren immer noch eine Art Architektur, Sie haben diesen Punkt nur sehr langsam und schmerzhaft erreicht.
Clean Architecture bietet Ihnen eine Abkürzung und eine vordefinierte Architektur, die getestet wurde. Und ja, sicher, Sie müssen all dies lernen, aber Sie tun es einmal in Ihrem Leben und wenden dann nur die Prinzipien in jeder Sprache oder in jeder Framework an, die Sie in Zukunft verwenden.
Sollte ich in allen meinen Projekten saubere Architektur anwenden?
NEIN . Nicht, wenn Sie nicht erwarten, dass das Projekt sowohl in Bezug auf die Anzahl der Funktionen als auch die Anzahl der Benutzer oder die Anzahl der daran arbeiten.
Was sind andere ähnliche Architekturen wie Clean Architecture?
Wie im ursprünglichen Blog -Beitrag erwähnt, den ich oben in der Readme erwähnt habe, haben Sie:
- Hexagonalarchitektur (auch bekannt als Ports und Adapter) von Alistair Cockburn
- Zwiebelarchitektur von Jeffrey Palermo
- Schreiende Architektur von Onkel Bob (der gleiche Typ hinter Clean Architecture)
- Und ein paar weitere (siehe den ursprünglichen Blog -Beitrag)