Anruf:
cargo run
Aus dem Repository -Root und Sie sind bereit zu gehen.
Versuchen Sie auch, die API -Dokumente zu erkunden, um eine Vorstellung davon zu bekommen, wo alles lebt:
cargo doc --open
Dieses Repository enthält eine Beispielrostanwendung für einen Online -Shop. Ziel ist es, einige Entwurfsmuster zu erkunden, die die Rostsprache nutzen, um skalierbare und wartbare Anwendungen aufzubauen.
Es ist ein Spielplatz für verschiedene Ideen, einige von ihnen könnten in der Praxis möglicherweise nicht ausgehen. Wenn Sie hier Feedback zu irgendetwas haben, können Sie sich gerne ein Problem eröffnen!
Es ist schwierig, Software im Vakuum zu entwerfen. Wenn Sie keine echte Domain haben, um das Wichtige voranzutreiben, können sich Designentscheidungen willkürlich anfühlen. Ich habe mich bemüht, Entscheidungen und die Gründe dafür zu dokumentieren, aber Fragen wie sollten wir Bestellelemente von Bestellungen teilen? Oder sollten Abfragen in Bestellungen in der Lage sein, auf Datenbanktabellen für Produkte zugreifen zu können? Kann nicht aus rein technischer Sicht wirklich beantwortet werden. Sie erfordern auch die Perspektive auf die Ziele des Projekts. Für alle, die diesen Code lesen, würde ich Sie ermutigen, ihn auf der Grundlage dieser willkürlichen Entwurfsentscheidungen zu untersuchen. Überlegen Sie sich die Einschränkungen, die Sie in Ihrer eigenen Umgebung konfrontiert sind und wie diese Ihre eigenen Entscheidungen beim Aufbau von Rostanträgen beeinflussen könnten.
Es geht nicht um bestimmte Rust -Frameworks oder Bibliotheken oder um die Lösung von Problemen, die einer Online -Einkaufsanwendung inhärent sind.
In den folgenden Abschnitten werden Teile der Anwendung beschrieben und erklären, warum sie so zusammengesetzt sind, wie sie sind.
Das Projektlayout konzentriert sich auf Privatsphäre. Durch die Begrenzung des Umfangs bestimmter Elemente begrenzen Sie auch den Umfang des möglichen Bruchs. Durch die Begrenzung des Umfangs bestimmter Elemente begrenzen Sie auch den Umfang der Belastung der Aufrechterhaltung des Anwendungszustands. In Rost sind Artikel, die in einem Modul privat sind, für alle Kinder dieses Moduls sichtbar . Das mag nach einer schlechten Sache klingen, aber wir nutzen es, um zu verhindern, dass Domänen -APIs die Implementierungsdetails für externe Bedenken wie Serialisierung und Speicherung durchsetzen.
Jedes Kerngeschäftskonzept in der Anwendung wird in seinen eigenen (meistens) in sich geschlossenen Ordner wie products oder customers aufgeteilt. Jedes Modul verkauft alles, was es über eine bestimmte Reihe von Entitäten zu wissen gibt:
/store )/queries )/commands ) Entitäten können von einem anderen Modul auf Entitäten abhängen, wie eine Order , abhängig von einem Product beim Hinzufügen. In jedem Domänenmodul gibt es eine Datenschutzhierarchie:
from_data -Methodefrom_data zu hydratischen Einheiten abDiese Module sind etwas schwerwiegend, aber in einer ordnungsgemäßen Anwendung kann das Hinzufügen neuer Domänenmodule mithilfe von Makros vereinfacht werden. Ich habe in dieser Anwendung keine Makros verwendet, sodass der Code einfach zu befolgen bleibt.
Ein Problem mit einer perfekt gefertigten Modulhierarchie ist, dass sie alle auseinanderfallen kann, wenn Sie ein Konzept haben, das einfach nicht in das aktuelle Layout passt. Je häufiger dies geschieht, desto schwieriger wird es, sich dem zuvor existierenden Layout anzupassen, weil es unmöglich wird, zu sagen, was es sein sollte.
Wir möchten, dass diese Module ihr eigenes Schicksal verwalten, aber wir möchten nicht, dass sie in sich geschlossen sind, bis sie in separate Dienste aufgeteilt werden könnten. Dies soll die Dinge einfach halten. Wenn Sie dies tun wollten, würde ich vorschlagen, separate Kisten anstelle von separaten Modulen zu verwenden.
Die Anwendung folgt einem einfachen Befehlsabfrage -Verantwortungs -Segregationsdesign. Dies ist ein Ansatz, der für eine datengesteuerte Anwendung ohne viel komplexe Logik gut funktioniert. Die Befehle erfassen einige Domäneninteraktion und arbeiten direkt an Entitäten, während Abfragen völlig willkürlich sind. Diese Anwendung verwendet keine spezielle Infrastruktur für die Realisierung von CQRs. Sie sind nur einfache Merkmale, die mithilfe eines Abhängigkeitsinjektionsmusters implementiert werden. Im Wesentlichen:
Result<()> zurückResult<T> zurück&mut self Receiver&self ReceiverDer Unterschied in der Mutability bedeutet, dass Befehle Anfragen aufrufen können, aber Abfragen können keine Befehle aufrufen.
Die Entitäten sind das Herzstück der Anwendung. Trotz des Mangels an einem echten Geschäft habe ich mich bemüht, das Domain -Modell reich zu halten. Entitäten sind nicht nur Taschen von Cruddy State. Sie sind:
.to_data() anrufen. Wenn Sie sich eine Entität ansehen, können Sie das Änderungsverhalten nicht aufrufen. Dies gilt durch das Kreditaufnahmesystem von Rust. Ein Unternehmen kann Eigentümer in seine schreibgeschützten Daten mit .into_data() verschieben. Dies ist eine Einwegoperation, daher können alle Änderungen, die an Staat vorgenommen wurden, nicht in den Laden zurückgebracht werden.Das Ziel einer Entität ist es, die Invarianten eines wichtigen Domänenkonzepts zu verkapulieren. Die Entitäten hier sind mit einem Mock-In-Memory-Store oder einer externen Datenbank einfach zu bedienen. Wir sollten darauf achten, dass wir uns nicht auf staatliche Veränderungen verlassen, wobei sich eine Entität in einer anderen widerspiegelt, weil sie zufällig auf dieselbe Quelle hinweisen.
Entitäten müssen auch darauf achten, nicht von den Datentypen eines anderen Unternehmens abhängig zu sein, da keine Garantie dafür besteht, dass Daten tatsächlich gültig sind. Stattdessen hängen sie von einer Entität ab und konvertieren sie nach Bedarf in Daten, sodass sie immer wissen, dass der Staat gültig ist.
Wir verwenden die folgenden Rostfunktionen, um unseren Entitätszustand zu schützen:
Serialize oder Deserialize nicht. Dies kann die Strecke verändert werden, aber ich finde es einfacher, den serialisierbaren Zustand schnell und locker zu halten, um die Kompatibilität rückwärts zu locken.Entitäten verkapulieren einige Zustands oder Daten und stellen sicher, dass Änderungen an diesen Daten keine Invarianten brechen, die Daten erwarten. Anstatt Getter zu implementieren, stellen wir eine schreibgeschützte Ansicht der Daten als Struktur auf. Der Vorteil ist, dass Sie die netten Funktionen von Rust nicht für die Arbeit mit Datastrukturen aufgeben müssen, wie Sie es mit Getter -Methoden tun würden. Diese Ansicht ist nur schreibgeschützt , sodass Änderungen nicht direkt zur Struktur zurückgeschrieben werden können. Das Entität bietet dafür noch Setter -Methoden.
Sie könnten argumentieren, dass die Aufdeckung von Staat auf diese Weise Implementierungsdetails aussieht, wie die version , die keinen Wert hat, der öffentlich ist. Das ist wahrscheinlich wahr. Um es zu umgehen, können Sie die Lebensdauer der schreibgeschützten Sicht auf die Felder verschieben und eine potenziell unterschiedliche geliehene Sichtweise des Staates zusammenstellen und die vom Entität privat verwaltete Datenstruktur behalten.
Sie könnten auch argumentieren, dass das Halten von Invarianten an einer Struktur, die sie nicht speichert, spröde ist. Dies ist sinnvoll, wenn die Datenschutzgrenze für ein Feld auf Objektebene liegt, wie in C#. Rost ist allerdings ein bisschen anders. Die engste Datenschutzgrenze liegt im Modul und seinen Kindern . Die Last, die Invarianten eines bestimmten Feldes aufrechtzuerhalten, fällt also auf alle Elemente des Moduls, in dem es definiert ist, sowie alle Kinder dieses Moduls.
Dies mag nach einem schrecklichen Leck klingen, aber diese Anwendung nutzt, um einen schön abstrahierten Speicher zu erstellen. Anstatt Löcher in unserer API freizulegen, um ein ORM zu unterstützen, erstreckt sich die Aufrechterhaltung des Zustands der Invarianten einfach in den Modellgeschäft, ohne an die Öffentlichkeit zurückzukehren.
Die Id und Version haben beide einen generischen Phantom -Parameter. Dieser Parameter existiert nur, um IDs mit inkompatiblen Typen wie Id<ProductData> und Id<OrderData> auszudrücken, aber noch andere Implementierungsdetails zu teilen.
Es ist ein Muster, das leichter zu befolgen ist als ein Makro, um die Kesselplatte zu reduzieren, da in der Quelle immer eine Schwierigkeit besteht, zu der Sie zurückkehren können.
Jede anhaltende Einheit hat ein version . Dieses Feld ist eine nichtsequentielle Kennung, die dem Zustand der Entität zu einem bestimmten Zeitpunkt entspricht. Wenn eine Entität aus dem Geschäft abgeholt wird, werden wir seine Version hydratisiert, dies wird kurz vor dem Aktualisieren überprüft, und wenn sie nicht übereinstimmen, balken wir.
Die Versionscheckung funktioniert für den In-Memory-Store einwandfrei, da wir eine exklusive Sperre für die Daten haben (nur 1 Anrufer kann den Zustand gleichzeitig ändern), benötigt jedoch einen anderen Ansatz für eine ordnungsgemäße DB. Wir können wahrscheinlich aktualisieren, wo die ID und die Version übereinstimmen, die Anzahl der aktualisierten Datensätze auswählen, und wenn sie 0 ist (bedeutet, dass die Version nicht übereinstimmt oder nicht existiert).
Die Speicherschicht verwendet ein einfaches Transaktionsschema, mit dem unabhängige Datenspeicher an Transaktionen teilnehmen können. Ein zentrales Repository verfolgt aktive Transaktionen und wird konsultiert, wenn Daten aus Datengeschäften abgerufen werden, um sicherzustellen, dass sie zur Verwendung bereit sind. Die optimistische Parallelität der Daten stellt sicher, dass mehrere aktive Transaktionen nicht versuchen können, denselben Wert gleichzeitig festzulegen. Dies verstößt gegen die wahre Isolation, hält die Dinge jedoch einfach und lässt uns den für jeden gespeicherten Wert minimieren.
Die Abhängigkeitsinjektion ist als Praxis von Vorteil, sich beim Entwerfen von Anwendungen zu stützen. Sie können die Bedenken der Abhängigkeitsauflösung von der App -Logik trennen. Es gibt Ihnen auch eine offensichtliche Möglichkeit, eine Anwendung zu skalieren. Diese Anwendung nimmt ein einfaches Muster an, das uns diese Vorteile ohne viel Infrastruktur bietet.
Diese Anwendung verwendet keine Umkehrung des Steuercontainers, wie Sie es möglicherweise verwendet werden, wenn Sie .NET -Anwendungen schreiben. Dies liegt hauptsächlich daran, dass es für Rost gibt. Es ist ein schweres Problem. Es wird jedoch ein einfaches Abhängigkeitsinjektionsmuster zum Komponieren von Befehlen und Abfragen verwendet, auch ohne anspruchsvolle Behälter.
Das Hauptziel der Abhängigkeitsinjektion hier ist es nicht, das Verspotten zu unterstützen. Es soll die Komplexität verringern, indem periphere Bedenken weiter von der Logik einer einzelnen Komponente entfernt werden.
Injizierbare Komponenten leben in ihrem eigenen Modul. Das Modul enthält:
Resolver -Typ, der eine Methode enthält, die die Standardimplementierung zurückgibt, ohne dass ihre Abhängigkeiten erforderlich sind.impl Trait zurückgibt. Sie wissen nie, welchen konkreten Typ diese Standardimplementierung verwendet.Arc und Box implementiert ist. Der gemeinsame Resolver klingt ein bisschen Service-Lokator-y, und da die Abhängigkeitsauflösung in den Implblöcken des Resolver selbst vollständig enthalten ist, vermeiden wir das Problem von Magic Global State in unserer App-Logik.
Um die Kesselplatte zu reduzieren, implementieren wir für Komponenten mit nur einer einzelnen Methode sie auch für Fn -Merkmale. Auf diese Weise können Sie vermeiden, dass Sie eine Struktur für sie deklarieren, die über alle ihre Abhängigkeiten allgemein ist. Der Rost -Compiler kümmert sich um Sie.
Dieses Muster ist bei Prosa schwer zu beschreiben, Sie müssen es sehen. Schauen Sie sich das Modul der domain/products/commands/create_product oder das domain/products/model/store für Beispiele dieses Abhängigkeitsinjektionsmusters bei der Arbeit an.
Resolver nicht ein "Gott Objekt"? Ein Gott -Objekt " ist ein Objekt in Ihrer Anwendung, das die wichtige Logik bis zu einem Punkt sammelt, an dem Sie nicht mit Komponenten arbeiten können, ohne auch das Gott -Objekt zu durcharbeiten. Sie sind ein Problem, weil sie schwer zu konstruieren oder zu ändern können. Das Resolver -Muster hier ist ein Gott -Objekt, aber es ist nicht notwendig, einzelne Komponenten zu bauen. Der Resolver befasst sich nur mit dem Aufbau einer Komponenten, die die Abhängigkeiten an den Abhängigkeiten erstellen.