Mit der Weiterentwicklung der digitalen Landschaft nimmt auch die Komplexität moderner Websites zu. Angesichts der steigenden Nachfrage nach einer besseren Benutzererfahrung und erweiterten Funktionen stehen Frontend-Entwickler vor der Herausforderung, skalierbare, wartbare und effiziente Architekturen zu erstellen.
Unter den zahlreichen Artikeln und Ressourcen, die zum Thema Frontend-Architektur verfügbar sind, konzentriert sich ein erheblicher Teil auf Clean Architecture und deren Anpassung. Tatsächlich diskutieren mehr als 50 % der fast 70 befragten Artikel Clean Architecture im Kontext der Front-End-Entwicklung.
Trotz der Fülle an Informationen bleibt ein eklatantes Problem bestehen: Viele der vorgeschlagenen Architekturideen wurden möglicherweise nie in realen Produktionsumgebungen umgesetzt. Dies lässt Zweifel an ihrer Wirksamkeit und Anwendbarkeit in praktischen Szenarien aufkommen.
Angetrieben von dieser Sorge begab ich mich auf eine sechsmonatige Reise zur Implementierung von Clean Architecture im Frontend, die es mir ermöglichte, mich mit der Realität dieser Ideen auseinanderzusetzen und die Spreu vom Weizen zu trennen.
In diesem Artikel teile ich meine Erfahrungen und Erkenntnisse aus dieser Reise und biete einen umfassenden Leitfaden zur erfolgreichen Implementierung von Clean Architecture im Frontend.
Dieser Artikel beleuchtet die Herausforderungen, Best Practices und realen Lösungen und zielt darauf ab, Frontend-Entwicklern die Tools an die Hand zu geben, die sie benötigen, um sich in der sich ständig weiterentwickelnden Welt der Website-Entwicklung zurechtzufinden.
Im heutigen sich schnell entwickelnden digitalen Ökosystem haben Entwickler die Qual der Wahl, wenn es um Frontend-Frameworks geht. Diese Fülle an Möglichkeiten beseitigt zahlreiche Probleme und vereinfacht den Entwicklungsprozess.
Dies führt jedoch auch zu endlosen Debatten unter Entwicklern, von denen jeder behauptet, dass sein bevorzugtes Framework anderen überlegen sei. Die Wahrheit ist, dass in unserer schnelllebigen Welt täglich neue JavaScript-Bibliotheken entstehen und Frameworks fast monatlich eingeführt werden.
Um in einem solch dynamischen Umfeld Flexibilität und Anpassungsfähigkeit aufrechtzuerhalten, benötigen wir eine Architektur, die über bestimmte Frameworks und Technologien hinausgeht.
Dies ist besonders wichtig für Produktunternehmen oder langfristige Verträge, die Wartung beinhalten und bei denen sich ändernde Trends und technologische Fortschritte berücksichtigt werden müssen.
Durch die Unabhängigkeit von Details wie Frameworks können wir uns auf das Produkt konzentrieren, an dem wir arbeiten, und uns auf Änderungen vorbereiten, die während seines Lebenszyklus auftreten können.
Keine Angst; Dieser Artikel soll eine Antwort auf dieses Dilemma geben.
Bei meinem Bestreben, die Clean Architecture im Frontend zu implementieren, habe ich eng mit mehreren Fullstack- und Backend-Entwicklern zusammengearbeitet, um sicherzustellen, dass die Architektur auch für diejenigen mit minimaler Frontend-Erfahrung verständlich und wartbar ist.
Daher ist eine der Hauptanforderungen unserer Architektur die Zugänglichkeit für Backend-Entwickler, die sich möglicherweise nicht mit den Feinheiten des Frontends auskennen, sowie für Fullstack-Entwickler, die möglicherweise nicht über umfassende Frontend-Kenntnisse verfügen.
Durch die Förderung einer nahtlosen Zusammenarbeit zwischen Frontend- und Backend-Teams zielt die Architektur darauf ab, diese Lücke zu schließen und ein einheitliches Entwicklungserlebnis zu schaffen.
Um einige großartige Dinge zu bauen, müssen wir uns leider etwas Hintergrundwissen aneignen. Ein klares Verständnis der zugrunde liegenden Prinzipien erleichtert nicht nur den Implementierungsprozess, sondern stellt auch sicher, dass die Architektur den Best Practices der Softwareentwicklung entspricht.
In diesem Abschnitt stellen wir drei Schlüsselkonzepte vor, die die Grundlage unseres Architekturansatzes bilden: SOLID-Prinzipien , Clean Architecture (die eigentlich aus SOLID-Prinzipien hervorgeht) und Atomic Design . Wenn Ihnen diese Bereiche am Herzen liegen, können Sie diesen Abschnitt überspringen.
SOLID ist ein Akronym, das fünf Designprinzipien darstellt, die Entwickler bei der Erstellung skalierbarer, wartbarer und modularer Software unterstützen:
Wenn Sie sich intensiver mit diesem Thema befassen möchten, wozu ich Sie wärmstens ermutige, dann ist das kein Problem. Im Moment reicht das, was ich präsentiert habe, jedoch aus, um weiterzumachen.
Und was gibt uns SOLID in Bezug auf diesen Artikel?
Robert C. Martin schlug basierend auf den SOLID-Prinzipien und seiner umfangreichen Erfahrung in der Entwicklung verschiedener Anwendungen das Konzept der Clean Architecture vor. Bei der Erörterung dieses Konzepts wird häufig auf das folgende Diagramm verwiesen, um seine Struktur visuell darzustellen:
Clean Architecture ist also kein neues Konzept; Es wird häufig in verschiedenen Programmierparadigmen verwendet, einschließlich funktionaler Programmierung und Backend-Entwicklung.
Bibliotheken wie Lodash und zahlreiche Backend-Frameworks haben diesen Architekturansatz übernommen, der auf den SOLID-Prinzipien basiert.
Clean Architecture legt Wert auf die Trennung von Belangen und die Schaffung unabhängiger, testbarer Schichten innerhalb einer Anwendung, mit dem vorrangigen Ziel, das System leicht verständlich, wartungs- und modifizierbar zu machen.
Die Architektur ist in konzentrischen Kreisen oder Schichten organisiert; Jeder hat klare Grenzen, Abhängigkeiten und Verantwortlichkeiten:
Clean Architecture fördert den Fluss von Abhängigkeiten von den äußeren zu den inneren Schichten und stellt so sicher, dass die Kerngeschäftslogik unabhängig von den verwendeten spezifischen Technologien oder Frameworks bleibt.
Dies führt zu einer flexiblen, wartbaren und testbaren Codebasis, die sich problemlos an sich ändernde Anforderungen oder Technologie-Stacks anpassen lässt.
Atomic Design ist eine Methodik, die UI-Komponenten organisiert, indem sie Schnittstellen in ihre grundlegendsten Elemente zerlegt und sie dann wieder zu komplexeren Strukturen zusammensetzt. Brad Frost stellte das Konzept erstmals 2008 in einem Artikel mit dem Titel „Atomic Design Methodology“ vor.
Hier ist eine Grafik, die das Konzept von Atomic Design zeigt:
Es besteht aus fünf verschiedenen Ebenen:
Durch die Einführung von Atomic Design können Entwickler von mehreren Vorteilen profitieren, wie z. B. Modularität, Wiederverwendbarkeit und einer klaren Struktur für UI-Komponenten, da wir dazu den Design-System-Ansatz befolgen müssen. Dies ist jedoch nicht das Thema dieses Artikels. Machen Sie also weiter.
Um eine fundierte Perspektive auf Clean Architecture für die Frontend-Entwicklung zu entwickeln, begab ich mich auf die Reise, eine Anwendung zu erstellen. Über einen Zeitraum von sechs Monaten habe ich bei der Arbeit an diesem Projekt wertvolle Erkenntnisse und Erfahrungen gesammelt.
Daher basieren die in diesem Artikel bereitgestellten Beispiele auf meiner praktischen Erfahrung mit der Anwendung. Um die Transparenz zu wahren, sind alle Beispiele aus öffentlich zugänglichem Code abgeleitet.
Sie können das Endergebnis erkunden, indem Sie das Repository unter besuchen
Wie bereits erwähnt, sind zahlreiche Implementierungen von Clean Architecture online verfügbar. Bei diesen Implementierungen lassen sich jedoch einige gemeinsame Elemente identifizieren:
Wenn wir diese Gemeinsamkeiten verstehen, können wir die grundlegende Struktur von Clean Architecture verstehen und sie an unsere spezifischen Bedürfnisse anpassen.
Der Kernteil unserer Bewerbung enthält:
Anwendungsfälle : Anwendungsfälle beschreiben die Geschäftsregeln für verschiedene Vorgänge, z. B. das Speichern, Aktualisieren und Abrufen von Daten. Ein Anwendungsfall könnte beispielsweise darin bestehen, eine Liste mit Wörtern aus Notion abzurufen oder die tägliche Suche des Benutzers nach gelernten Wörtern zu erhöhen.
Im Wesentlichen behandeln Use Cases die Aufgaben und Prozesse der Anwendung aus betriebswirtschaftlicher Sicht und stellen so sicher, dass das System gemäß den gewünschten Zielen funktioniert.
Modelle : Modelle repräsentieren die Geschäftseinheiten innerhalb der Anwendung. Diese können mithilfe von TypeScript-Schnittstellen definiert werden, um sicherzustellen, dass sie den Bedürfnissen und Geschäftsanforderungen entsprechen.
Wenn ein Anwendungsfall beispielsweise das Abrufen einer Wortliste aus Notion umfasst, benötigen Sie ein Modell, um die Datenstruktur für diese Liste genau zu beschreiben und dabei die entsprechenden Geschäftsregeln und Einschränkungen einzuhalten.
Vorgänge : Manchmal ist es möglicherweise nicht möglich, bestimmte Aufgaben als Anwendungsfälle zu definieren, oder Sie möchten möglicherweise wiederverwendbare Funktionen erstellen, die in mehreren Teilen Ihrer Domäne eingesetzt werden können. Wenn Sie beispielsweise eine Funktion schreiben müssen, um anhand des Namens nach einem Notion-Wort zu suchen, sollten sich diese Operationen hier befinden.
Operationen sind nützlich, um domänenspezifische Logik zu kapseln, die in verschiedenen Kontexten innerhalb der Anwendung gemeinsam genutzt und genutzt werden kann.
Repository-Schnittstellen : Anwendungsfälle erfordern eine Möglichkeit, auf Daten zuzugreifen. Gemäß dem Prinzip der Abhängigkeitsinversion sollte die Domänenschicht nicht von einer anderen Schicht abhängen (während die anderen Schichten von ihr abhängen); Daher definiert diese Schicht die Schnittstellen für die Repositorys.
Es ist wichtig zu beachten, dass es die Schnittstellen und nicht die Implementierungsdetails angibt. Die Repositorys selbst nutzen das Repository-Muster, das unabhängig von der tatsächlichen Datenquelle ist und die Logik zum Abrufen oder Senden von Daten an und von diesen Quellen hervorhebt.
Es ist wichtig zu erwähnen, dass ein einzelnes Repository mehrere APIs implementieren kann und ein einzelner Anwendungsfall mehrere Repositorys nutzen kann.
Diese Schicht ist für den Datenzugriff verantwortlich und kann bei Bedarf mit verschiedenen Quellen kommunizieren. Da wir eine Frontend-Anwendung entwickeln, dient diese Ebene in erster Linie als Wrapper für Browser-APIs.
Dazu gehören APIs für REST, lokaler Speicher, IndexedDB, Sprachsynthese und mehr.
Es ist wichtig zu beachten, dass die API-Ebene der ideale Ort ist, wenn Sie OpenAPI-Typen und HTTP-Clients generieren möchten. Innerhalb dieser Schicht haben wir:
API-Adapter : Der API-Adapter ist ein spezieller Adapter für Browser-APIs, die in unserer Anwendung verwendet werden. Diese Komponente verwaltet REST-Aufrufe und die Kommunikation mit dem Speicher der App oder jeder anderen Datenquelle, die Sie verwenden möchten.
Bei Bedarf können Sie sogar Ihr eigenes Objektspeichersystem erstellen und implementieren. Durch einen dedizierten API-Adapter können Sie eine konsistente Schnittstelle für die Interaktion mit verschiedenen Datenquellen beibehalten und so die Aktualisierung oder Änderung bei Bedarf erleichtern.
Die Repository-Schicht spielt eine entscheidende Rolle in der Architektur der Anwendung, indem sie die Integration mehrerer APIs verwaltet, API-spezifische Typen Domänentypen zuordnet und Vorgänge zur Datentransformation integriert.
Wenn Sie beispielsweise die Sprachsynthese-API mit lokaler Speicherung kombinieren möchten, sind Sie hier genau richtig. Diese Ebene enthält:
Die Adapterschicht ist dafür verantwortlich, die Interaktionen zwischen diesen Schichten zu orchestrieren und sie miteinander zu verbinden. Diese Schicht enthält nur Module, die verantwortlich sind für:
Die Präsentationsschicht ist für die Darstellung der Benutzeroberfläche (UI) und die Abwicklung von Benutzerinteraktionen mit der Anwendung verantwortlich. Es nutzt die Adapter-, Domänen- und freigegebenen Ebenen, um eine funktionale und interaktive Benutzeroberfläche zu erstellen.
Die Präsentationsschicht nutzt die Atomic-Design-Methodik zur Organisation ihrer Komponenten, was zu einer skalierbaren und wartbaren Anwendung führt. Diese Ebene wird jedoch nicht der Hauptschwerpunkt dieses Artikels sein, da sie nicht das Hauptthema im Hinblick auf die Implementierung von Clean Architecture ist.
Für alle gemeinsamen Elemente, wie zentralisierte Dienstprogramme, Konfigurationen und gemeinsame Logik, ist ein bestimmter Ort erforderlich. Wir werden in diesem Artikel jedoch nicht zu tief auf diese Ebene eingehen.
Es lohnt sich, dies nur zu erwähnen, um ein Verständnis dafür zu vermitteln, wie gemeinsame Komponenten in der gesamten Anwendung verwaltet und gemeinsam genutzt werden.
Bevor wir uns nun mit dem Codieren befassen, ist es wichtig, das Testen zu besprechen. Es ist von entscheidender Bedeutung, die Zuverlässigkeit und Korrektheit Ihrer Anwendung sicherzustellen und eine robuste Teststrategie für jede Ebene der Architektur zu implementieren.
Durch die Implementierung einer umfassenden Teststrategie für jede Ebene der Architektur können Sie die Zuverlässigkeit, Korrektheit und Wartbarkeit Ihrer Anwendung sicherstellen und gleichzeitig die Wahrscheinlichkeit von Fehlern während der Entwicklung verringern.
Wenn Sie jedoch eine kleine Anwendung erstellen, sollten Integrationstests auf der Adapterebene ausreichen.
Okay, jetzt, da Sie ein solides Verständnis von Clean Architecture haben und sich vielleicht sogar eine eigene Meinung dazu gebildet haben, lassen Sie uns etwas tiefer eintauchen und echten Code erkunden.
Bedenken Sie, dass ich hier nur ein einfaches Beispiel präsentiere; Wenn Sie jedoch an detaillierteren Beispielen interessiert sind, können Sie gerne mein GitHub-Repository erkunden, das am Anfang dieses Artikels erwähnt wurde.
Im „echten Leben“ glänzt Clean Architecture in großen Anwendungen auf Unternehmensebene wirklich, während es für kleinere Projekte möglicherweise übertrieben ist. Nachdem dies gesagt ist, kommen wir zum Punkt.
Am Beispiel meiner Anwendung zeige ich, wie man einen API-Aufruf durchführt, um Wörterbuchvorschläge für ein bestimmtes Wort abzurufen. Dieser spezielle API-Endpunkt ruft durch Web-Scraping zweier Websites eine Liste mit Bedeutungen und Beispielen ab.
Aus geschäftlicher Sicht ist dieser Endpunkt von entscheidender Bedeutung für die Ansicht „Wort suchen“, die es Benutzern ermöglicht, nach einem bestimmten Wort zu suchen. Sobald der Benutzer das Wort gefunden und sich angemeldet hat, kann er die im Internet gespeicherten Informationen zu seiner Notion-Datenbank hinzufügen.
Zunächst müssen wir eine Ordnerstruktur einrichten, die die zuvor besprochenen Ebenen genau widerspiegelt. Der Aufbau sollte wie folgt aussehen:
client ├── adapter ├── api ├── domain ├── presentation ├── repository └── shared
Das Client-Verzeichnis dient in vielen Projekten einem ähnlichen Zweck wie der Ordner „src“. In diesem speziellen Next.js-Projekt habe ich die Konvention übernommen, den Frontend-Ordner als „Client“ und den Backend-Ordner als „Server“ zu benennen.
Dieser Ansatz ermöglicht eine klare Unterscheidung zwischen den beiden Hauptkomponenten der Anwendung.
Die Wahl der richtigen Ordnerstruktur für Ihr Projekt ist in der Tat eine entscheidende Entscheidung, die frühzeitig im Entwicklungsprozess getroffen werden sollte. Verschiedene Entwickler haben ihre eigenen Vorlieben und Ansätze, wenn es um die Organisation von Ressourcen geht.
Einige gruppieren Ressourcen möglicherweise nach Seitennamen, andere folgen möglicherweise den von OpenAPI generierten Namenskonventionen für Unterverzeichnisse und wieder andere glauben möglicherweise, dass ihre Anwendung zu klein ist, um eine dieser Lösungen zu rechtfertigen.
Der Schlüssel liegt darin, eine Struktur zu wählen, die den spezifischen Anforderungen und dem Umfang Ihres Projekts am besten entspricht und gleichzeitig eine klare und wartbare Ressourcenorganisation aufrechterhält.
Ich gehöre zur dritten Gruppe, daher sieht meine Struktur so aus:
client ├── adapter │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ └── supabase ├── api │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ └── supabase ├── domain │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ ├── supabase └── repository ├── local-storage ├── rest ├── speech-synthesis └── supabase
Ich habe beschlossen, die geteilten und Präsentationsebenen in diesem Artikel wegzulassen, da ich glaube, dass diejenigen, die tiefer eintauchen möchten, für weitere Informationen auf mein Repository verweisen können. Fahren wir nun mit einigen Codebeispielen fort, um zu veranschaulichen, wie Clean Architecture in einer Frontend-Anwendung angewendet werden kann.
Betrachten wir unsere Anforderungen. Als Nutzer möchte ich eine Liste mit Vorschlägen inklusive Bedeutung und Beispielen erhalten. Daher kann ein einzelner Wörterbuchvorschlag wie folgt modelliert werden:
interface DictionarySuggestion { example: string; meaning: string; }
Nachdem wir nun einen einzelnen Wörterbuchvorschlag beschrieben haben, ist es wichtig zu erwähnen, dass das durch Web Scraping erhaltene Wort manchmal von der Eingabe des Benutzers abweicht oder korrigiert wird. Um dies zu berücksichtigen, verwenden wir später in unserer App die korrigierte Version.
Daher müssen wir eine Schnittstelle definieren, die eine Liste mit Wörterbuchvorschlägen und Wortkorrekturen enthält. Die endgültige Schnittstelle sieht folgendermaßen aus:
export interface DictionarySuggestions { suggestions: DictionarySuggestion[]; word: string; }
Wir exportieren diese Schnittstelle, weshalb das Schlüsselwort export
enthalten ist.
Wir haben unser Modell und jetzt ist es an der Zeit, es in die Tat umzusetzen.
import { DictionarySuggestions } from './rest.models'; export interface RestRepository { getDictionarySuggestions: (word: string) => Promise<DictionarySuggestions | null>; }
An diesem Punkt sollte alles klar sein. Es ist wichtig zu beachten, dass wir hier überhaupt nicht auf die API eingehen! Die Struktur des Repositorys selbst ist recht einfach: nur ein Objekt mit einigen Methoden, wobei jede Methode asynchron Daten eines bestimmten Typs zurückgibt.
Bitte beachten Sie, dass das Repository Daten immer im Domänenmodellformat zurückgibt.
Definieren wir nun unsere Geschäftsregel als Anwendungsfall. Der Code sieht so aus:
export type GetDictionarySuggestionsUseCaseUseCase = UseCaseWithSingleParamAndPromiseResult< string, DictionarySuggestions | null >; export const getDictionarySuggestionsUseCase = ( restRepository: RestRepository, ): GetDictionarySuggestionsUseCaseUseCase => ({ execute: (word) => restRepository.getDictionarySuggestions(word), });
Als Erstes ist die Liste der gängigen Typen zu beachten, die zum Definieren von Anwendungsfällen verwendet werden. Um dies zu erreichen, habe ich eine Datei use-cases.types.ts
im Domänenverzeichnis erstellt:
domain ├── local-storage ├── rest ├── speech-synthesis ├── supabase └── use-cases.types.ts
Dadurch kann ich Typen für Anwendungsfälle problemlos zwischen meinen Unterverzeichnissen teilen. Die Definition von UseCaseWithSingleParamAndPromiseResult
sieht folgendermaßen aus:
export interface UseCaseWithSingleParamAndPromiseResult<TParam, TResult> { execute: (param: TParam) => Promise<TResult>; }
Dieser Ansatz trägt dazu bei, die Konsistenz und Wiederverwendbarkeit von Anwendungsfalltypen auf der gesamten Domänenebene aufrechtzuerhalten.
Sie fragen sich vielleicht, warum wir die execute
benötigen. Hier haben wir eine Fabrik, die den tatsächlichen Anwendungsfall zurückgibt.
Diese Designwahl ist auf die Tatsache zurückzuführen, dass wir die Repository-Implementierung nicht direkt im Anwendungsfallcode referenzieren möchten und auch nicht möchten, dass das Repo von einem Import verwendet wird. Dieser Ansatz ermöglicht es uns, die Abhängigkeitsinjektion später einfach anzuwenden.
Durch die Verwendung des Factory-Musters und der execute
können wir die Implementierungsdetails des Repositorys vom Anwendungsfallcode trennen, was die Modularität und Wartbarkeit der Anwendung verbessert.
Dieser Ansatz folgt dem Dependency Inversion-Prinzip, bei dem die Domänenschicht nicht von einer anderen Schicht abhängig ist und eine größere Flexibilität ermöglicht, wenn es darum geht, verschiedene Repository-Implementierungen auszutauschen oder die Architektur der Anwendung zu ändern.
Definieren wir zunächst unsere Schnittstelle:
export interface RestApi { getDictionarySuggestions: (word: string) => Promise<AxiosResponse<DictionarySuggestions>>; }
Wie Sie sehen, ähnelt die Definition dieser Funktion in der Schnittstelle stark der im Repository. Da der Domänentyp die Antwort bereits beschreibt, ist es nicht erforderlich, denselben Typ neu zu erstellen.
Es ist wichtig zu beachten, dass unsere API Rohdaten zurückgibt, weshalb wir den vollständigen AxiosResponse<DictionarySuggestions>
zurückgeben. Dadurch wahren wir eine klare Trennung zwischen der API- und der Domänenebene und ermöglichen so mehr Flexibilität bei der Datenverarbeitung und -transformation.
Die Implementierung dieser API sieht folgendermaßen aus:
export const getRestApi = (axiosInstance: AxiosInstance): RestApi => ({ getDictionarySuggestions: async (word: string) => { const encodedCurrentDate = encodeURIComponent(word); const response = await axiosInstance.get( `${RestEndpoints.GET_DICTIONARY_SUGGESTIONS}?word=${encodedCurrentDate}`, ); return response; } });
An diesem Punkt wird es interessanter. Der erste wichtige Aspekt, den es zu besprechen gilt, ist die Injektion unserer axiosInstance
. Dies macht unseren Code sehr flexibel und ermöglicht uns die einfache Erstellung solider Tests. Hier kümmern wir uns auch um die Codierung oder das Parsen von Abfrageparametern.
Sie können hier jedoch auch andere Aktionen ausführen, beispielsweise das Trimmen der Eingabezeichenfolge. Durch das Einfügen von axiosInstance
sorgen wir für eine klare Trennung der Belange und stellen sicher, dass die API-Implementierung an verschiedene Szenarien oder Änderungen in den externen Diensten anpassbar ist.
Da unsere Schnittstelle bereits durch die Domäne definiert ist, müssen wir nur noch unser Repository implementieren. Die endgültige Implementierung sieht also so aus:
export const getRestRepository = (restApi: RestApi): RestRepository => ({ getDictionarySuggestions: async (word) => { const { data } = await restApi.getDictionarySuggestions(word); if (!data?.suggestions?.length) { return null; } return formatDictionarySuggestions(data); } });
Ein wichtiger zu erwähnender Aspekt betrifft APIs. Unser getRestRepository
ermöglicht es uns, eine zuvor definierte restApi
zu übergeben. Dies ist von Vorteil, da es, wie bereits erwähnt, ein einfacheres Testen ermöglicht. Wir können formatDictionarySuggestions
kurz untersuchen:
export const formatDictionarySuggestions = ({ suggestions, word, }: DictionarySuggestions): DictionarySuggestions => { const cleanedWord = cleanUpString(word); const cleanedSuggestions = suggestions.map((_suggestion) => { const cleanedMeaning = cleanUpString(_suggestion.meaning); const cleanedExample = cleanUpString(_suggestion.example); return { meaning: cleanedMeaning, example: cleanedExample, }; }); return { word: cleanedWord, suggestions: cleanedSuggestions, }; };
Dieser Vorgang verwendet unser Domänen- DictionarySuggestions
Modell als Argument und führt eine String-Bereinigung durch, was bedeutet, dass unnötige Leerzeichen, Zeilenumbrüche, Tabulatoren und Groß-/Kleinschreibung entfernt werden. Es ist ziemlich einfach, ohne versteckte Komplexität.
Es ist wichtig zu beachten, dass Sie sich an dieser Stelle keine Gedanken über Ihre API-Implementierung machen müssen. Zur Erinnerung: Das Repository gibt Daten immer im Domänenmodell zurück! Es kann nicht anders sein, da dies das Prinzip der Abhängigkeitsumkehr verletzen würde.
Und im Moment ist unsere Domänenschicht nicht von irgendetwas außerhalb ihr abhängig.
Zu diesem Zeitpunkt sollte alles implementiert und für die Abhängigkeitsinjektion bereit sein. Hier ist die endgültige Implementierung des restlichen Moduls:
import { getRestRepository } from '@repository/rest/rest.repository'; import { getRestApi } from '@api/rest/rest.api'; import { getDictionarySuggestionsUseCase } from '@domain/rest/rest.use-cases'; import { axiosInstance } from '@shared/axios.instance'; const restApi = getRestApi(axiosInstance); const restRepository = getRestRepository(restApi); export const restModule = { getDictionarySuggestions: getDictionarySuggestionsUseCase(restRepository).execute, };
Das ist richtig! Wir haben den Prozess der Implementierung von Clean Architecture-Prinzipien durchlaufen, ohne an ein bestimmtes Framework gebunden zu sein. Dieser Ansatz stellt sicher, dass unser Code anpassbar ist, sodass bei Bedarf problemlos zwischen Frameworks oder Bibliotheken gewechselt werden kann.
Wenn es um Tests geht, ist das Auschecken des Repositorys eine gute Möglichkeit, zu verstehen, wie Tests in dieser Architektur implementiert und organisiert werden.
Mit einer soliden Grundlage in Clean Architecture können Sie umfassende Tests schreiben, die verschiedene Szenarien abdecken und so Ihre Anwendung robuster und zuverlässiger machen.
Wie gezeigt, führt die Befolgung der Clean Architecture-Prinzipien und die Trennung von Bedenken zu einer wartbaren, skalierbaren und testbaren Anwendungsstruktur.
Dieser Ansatz erleichtert letztendlich das Hinzufügen neuer Funktionen, die Umgestaltung von Code und die Zusammenarbeit mit einem Team an einem Projekt und stellt so den langfristigen Erfolg Ihrer Anwendung sicher.
In der Beispielanwendung wird React für die Präsentationsschicht verwendet. Im Adapterverzeichnis gibt es eine zusätzliche Datei namens hooks.ts
, die die Interaktion mit dem restlichen Modul abwickelt. Der Inhalt dieser Datei ist wie folgt:
import { restModule } from '@adapter/rest/rest.module'; import { useAxios } from '@shared/hooks'; export const useDictionarySuggestions = () => { const { data, error, isLoading, mutate } = useAxios(restModule.getDictionarySuggestions); return { dictionarySuggestions: data, getDictionarySuggestions: mutate, dictionarySuggestionsError: error, isDictionarySuggestionsLoading: isLoading, }; };
Diese Implementierung macht die Arbeit mit der Präsentationsschicht unglaublich einfach. Durch die Verwendung des useDictionarySuggestions
Hooks muss sich die Präsentationsschicht nicht um die Verwaltung von Datenzuordnungen oder anderen Verantwortlichkeiten kümmern, die nichts mit ihrer primären Funktion zu tun haben.
Diese Trennung der Belange trägt dazu bei, die Prinzipien der Clean Architecture beizubehalten und führt zu besser verwaltbarem und wartbarem Code.
Zuallererst empfehle ich Ihnen, in den Code des bereitgestellten GitHub-Repos einzutauchen und seine Struktur zu erkunden.
Was kannst du noch tun? Der Himmel ist das Limit! Es hängt alles von Ihren spezifischen Designanforderungen ab. Beispielsweise könnten Sie erwägen, die Datenschicht durch die Einbindung eines Datenspeichers zu implementieren (Redux, MobX oder sogar etwas Benutzerdefiniertes – das spielt keine Rolle).
Alternativ könnten Sie mit verschiedenen Kommunikationsmethoden zwischen den Schichten experimentieren, wie zum Beispiel die Verwendung von RxJS zur Abwicklung der asynchronen Kommunikation mit dem Backend, was Abfragen, Push-Benachrichtigungen oder Sockets umfassen könnte (im Wesentlichen die Vorbereitung auf jede Datenquelle).
Grundsätzlich können Sie nach Belieben erkunden und experimentieren, solange Sie die geschichtete Architektur beibehalten und sich an das Prinzip der umgekehrten Abhängigkeit halten. Stellen Sie immer sicher, dass die Domäne im Mittelpunkt Ihres Designs steht.
Auf diese Weise erstellen Sie eine flexible und wartbare Anwendungsstruktur, die sich an verschiedene Szenarien und Anforderungen anpassen lässt.
In diesem Artikel haben wir uns mit dem Konzept der Clean Architecture im Kontext einer mit React erstellten Sprachlernanwendung befasst.
Wir haben die Bedeutung der Aufrechterhaltung einer mehrschichtigen Architektur und die Einhaltung des Prinzips der umgekehrten Abhängigkeit sowie die Vorteile der Trennung von Belangen hervorgehoben.
Ein wesentlicher Vorteil von Clean Architecture besteht darin, dass Sie sich auf den technischen Aspekt Ihrer Anwendung konzentrieren können, ohne an ein bestimmtes Framework gebunden zu sein. Diese Flexibilität ermöglicht es Ihnen, Ihre Anwendung an verschiedene Szenarien und Anforderungen anzupassen.
Dieser Ansatz weist jedoch einige Nachteile auf. In manchen Fällen kann die Einhaltung eines strengen Architekturmusters zu mehr Boilerplate-Code oder zusätzlicher Komplexität in der Projektstruktur führen.
Darüber hinaus kann es sowohl ein Vor- als auch ein Nachteil sein, sich weniger auf die Dokumentation zu verlassen – es ermöglicht zwar mehr Freiheit und Kreativität, kann aber auch zu Verwirrung oder Missverständnissen zwischen den Teammitgliedern führen.
Trotz dieser potenziellen Herausforderungen kann die Implementierung von Clean Architecture äußerst vorteilhaft sein, insbesondere im Kontext von React, wo es kein allgemein akzeptiertes Architekturmuster gibt.
Es ist wichtig, Ihre Architektur zu Beginn eines Projekts zu berücksichtigen, anstatt sie erst nach Jahren des Ringens in Angriff zu nehmen.
Um ein reales Beispiel von Clean Architecture in Aktion zu sehen, schauen Sie sich gerne mein Repository unter an
Wow, das ist wahrscheinlich der längste Artikel, den ich je geschrieben habe. Es fühlt sich unglaublich an!