Ich schreibe ein Projekt in Django und sehe, dass 80% des Codes in der Datei enthalten sind models.py
. Dieser Code ist verwirrend und nach einer gewissen Zeit verstehe ich nicht mehr, was wirklich passiert.
Folgendes stört mich:
- Ich finde es hässlich, dass meine Modellebene (die eigentlich nur für die Arbeit mit Daten aus einer Datenbank verantwortlich sein sollte) auch E-Mails sendet, über API an andere Dienste geht usw.
- Außerdem finde ich es nicht akzeptabel, Geschäftslogik in die Ansicht aufzunehmen, da es auf diese Weise schwierig wird, sie zu kontrollieren. In meiner Anwendung gibt es beispielsweise mindestens drei Möglichkeiten, neue Instanzen von zu erstellen
User
, aber technisch sollte sie einheitlich erstellt werden. - Ich merke nicht immer, wann die Methoden und Eigenschaften meiner Modelle nicht deterministisch werden und wann sie Nebenwirkungen entwickeln.
Hier ist ein einfaches Beispiel. Das User
Modell war zunächst so:
class User(db.Models):
def get_present_name(self):
return self.name or 'Anonymous'
def activate(self):
self.status = 'activated'
self.save()
Im Laufe der Zeit wurde daraus:
class User(db.Models):
def get_present_name(self):
# property became non-deterministic in terms of database
# data is taken from another service by api
return remote_api.request_user_name(self.uid) or 'Anonymous'
def activate(self):
# method now has a side effect (send message to user)
self.status = 'activated'
self.save()
send_mail('Your account is activated!', '…', [self.email])
Ich möchte Entitäten in meinem Code trennen:
- Entitäten meiner Datenbank, Datenbankebene: Was enthält meine Anwendung?
- Entitäten meiner Anwendung, Geschäftslogikebene: Was kann meine Anwendung ausmachen?
Was sind die guten Praktiken, um einen solchen Ansatz umzusetzen, der in Django angewendet werden kann?
Antworten:
Anscheinend fragen Sie nach dem Unterschied zwischen dem Datenmodell und dem Domänenmodell. Bei letzterem finden Sie die Geschäftslogik und Entitäten, die von Ihrem Endbenutzer wahrgenommen werden. Bei ersteren speichern Sie Ihre Daten tatsächlich.
Darüber hinaus habe ich den dritten Teil Ihrer Frage wie folgt interpretiert: Wie kann man feststellen, dass diese Modelle nicht getrennt bleiben?
Dies sind zwei sehr unterschiedliche Konzepte, und es ist immer schwierig, sie getrennt zu halten. Es gibt jedoch einige gängige Muster und Werkzeuge, die für diesen Zweck verwendet werden können.
Informationen zum Domänenmodell
Das erste, was Sie erkennen müssen, ist, dass es in Ihrem Domain-Modell nicht wirklich um Daten geht. Es geht um Aktionen und Fragen wie "Diesen Benutzer aktivieren", "Diesen Benutzer deaktivieren", "Welche Benutzer sind derzeit aktiviert?" und "Wie heißt dieser Benutzer?". Klassisch ausgedrückt: Es geht um Abfragen und Befehle .
In Befehlen denken
Schauen wir uns zunächst die Befehle in Ihrem Beispiel an: "Diesen Benutzer aktivieren" und "Diesen Benutzer deaktivieren". Das Schöne an Befehlen ist, dass sie leicht durch kleine gegebene Wann-Dann-Szenarien ausgedrückt werden können:
Solche Szenarien sind nützlich, um zu sehen, wie verschiedene Teile Ihrer Infrastruktur von einem einzigen Befehl beeinflusst werden können - in diesem Fall Ihre Datenbank (eine Art 'aktives' Flag), Ihr Mailserver, Ihr Systemprotokoll usw.
Solche Szenarien helfen Ihnen auch beim Einrichten einer testgesteuerten Entwicklungsumgebung.
Und schließlich hilft Ihnen das Denken in Befehlen wirklich dabei, eine aufgabenorientierte Anwendung zu erstellen. Ihre Benutzer werden dies zu schätzen wissen :-)
Befehle ausdrücken
Django bietet zwei einfache Möglichkeiten, Befehle auszudrücken. Sie sind beide gültige Optionen und es ist nicht ungewöhnlich, die beiden Ansätze zu mischen.
Die Serviceschicht
Das Servicemodul wurde bereits von @Hedde beschrieben . Hier definieren Sie ein separates Modul und jeder Befehl wird als Funktion dargestellt.
services.py
Formulare verwenden
Die andere Möglichkeit besteht darin, für jeden Befehl ein Django-Formular zu verwenden. Ich bevorzuge diesen Ansatz, weil er mehrere eng verwandte Aspekte kombiniert:
forms.py
In Fragen denken
Ihr Beispiel enthielt keine Abfragen, daher habe ich mir erlaubt, einige nützliche Abfragen zu erstellen. Ich bevorzuge den Begriff "Frage", aber Abfragen sind die klassische Terminologie. Interessante Fragen sind: "Wie heißt dieser Benutzer?", "Kann sich dieser Benutzer anmelden?", "Liste der deaktivierten Benutzer anzeigen" und "Wie ist die geografische Verteilung der deaktivierten Benutzer?"
Bevor Sie mit der Beantwortung dieser Fragen beginnen, sollten Sie sich immer zwei Fragen stellen: Ist dies eine Präsentationsabfrage nur für meine Vorlagen und / oder eine Geschäftslogikabfrage, die an die Ausführung meiner Befehle gebunden ist, und / oder eine Berichtsabfrage .
Präsentationsabfragen dienen lediglich der Verbesserung der Benutzeroberfläche. Die Antworten auf Geschäftslogikabfragen wirken sich direkt auf die Ausführung Ihrer Befehle aus. Berichtsabfragen dienen lediglich analytischen Zwecken und unterliegen weniger zeitlichen Einschränkungen. Diese Kategorien schließen sich nicht gegenseitig aus.
Die andere Frage lautet: "Habe ich die vollständige Kontrolle über die Antworten?" Wenn wir beispielsweise den Benutzernamen abfragen (in diesem Zusammenhang), haben wir keine Kontrolle über das Ergebnis, da wir auf eine externe API angewiesen sind.
Abfragen stellen
Die grundlegendste Abfrage in Django ist die Verwendung des Manager-Objekts:
Dies funktioniert natürlich nur, wenn die Daten tatsächlich in Ihrem Datenmodell dargestellt werden. Dies ist nicht immer der Fall. In diesen Fällen können Sie die folgenden Optionen in Betracht ziehen.
Benutzerdefinierte Tags und Filter
Die erste Alternative ist nützlich für Abfragen, die nur zur Präsentation dienen: benutzerdefinierte Tags und Vorlagenfilter.
template.html
template_tags.py
Abfragemethoden
Wenn Ihre Abfrage nicht nur präsentativ ist, können Sie Ihrer services.py Abfragen hinzufügen (falls Sie diese verwenden) oder ein queries.py- Modul einführen :
queries.py
Proxy-Modelle
Proxy-Modelle sind im Kontext von Geschäftslogik und Berichterstellung sehr nützlich. Sie definieren grundsätzlich eine erweiterte Teilmenge Ihres Modells. Sie können das Basis-QuerySet eines Managers überschreiben, indem Sie die
Manager.get_queryset()
Methode überschreiben .models.py
Abfragemodelle
Für Abfragen, die von Natur aus komplex sind, aber häufig ausgeführt werden, besteht die Möglichkeit von Abfragemodellen. Ein Abfragemodell ist eine Form der Denormalisierung, bei der relevante Daten für eine einzelne Abfrage in einem separaten Modell gespeichert werden. Der Trick besteht natürlich darin, das denormalisierte Modell mit dem primären Modell synchron zu halten. Abfragemodelle können nur verwendet werden, wenn Änderungen vollständig unter Ihrer Kontrolle stehen.
models.py
Die erste Möglichkeit besteht darin, diese Modelle in Ihren Befehlen zu aktualisieren. Dies ist sehr nützlich, wenn diese Modelle nur durch einen oder zwei Befehle geändert werden.
forms.py
Eine bessere Option wäre die Verwendung von benutzerdefinierten Signalen. Diese Signale werden natürlich von Ihren Befehlen ausgegeben. Signale haben den Vorteil, dass Sie mehrere Abfragemodelle mit Ihrem ursprünglichen Modell synchronisieren können. Darüber hinaus kann die Signalverarbeitung mithilfe von Sellerie oder ähnlichen Frameworks auf Hintergrundaufgaben verlagert werden.
signale.py
forms.py
models.py
Halten Sie es sauber
Wenn Sie diesen Ansatz verwenden, wird es lächerlich einfach festzustellen, ob Ihr Code sauber bleibt. Befolgen Sie einfach diese Richtlinien:
Gleiches gilt für Ansichten (da Ansichten häufig unter demselben Problem leiden).
Einige Referenzen
Django-Dokumentation: Proxy-Modelle
Django-Dokumentation: Signale
Architektur: Domain Driven Design
quelle
User.objects.inactive_users()
. Aber das Proxy-Modell-Beispiel hier IMO führt zu einer falschen Semantik:u = InactiveUser.objects.all()[0]; u.active = True; u.save()
und dochisinstance(u, InactiveUser) == True
. Ich würde auch erwähnen, dass ein effektiver Weg, ein Abfragemodell in vielen Fällen zu pflegen, eine Datenbankansicht ist.Normalerweise implementiere ich eine Service-Schicht zwischen Ansichten und Modellen. Dies verhält sich wie die API Ihres Projekts und gibt Ihnen einen guten Überblick über die Vorgänge im Hubschrauber. Ich habe diese Praxis von einem Kollegen geerbt, der diese Überlagerungstechnik häufig bei Java-Projekten (JSF) verwendet, z.
models.py
services.py
views.py
quelle
Zunächst einmal, Sie wiederholen sich nicht .
Dann achten Sie bitte darauf, nicht zu überarbeiten, manchmal ist es nur Zeitverschwendung und lässt jemanden den Fokus auf das verlieren, was wichtig ist. Überprüfen Sie von Zeit zu Zeit den Zen von Python .
Schauen Sie sich aktive Projekte an
Das Fabric-Repository ist auch gut anzusehen.
yourapp/models/logicalgroup.py
User
,Group
und verwandte Modelle gehen unteryourapp/models/users.py
Poll
,Question
,Answer
... könnte gehen unteryourapp/models/polls.py
__all__
innerhalb vonyourapp/models/__init__.py
Mehr über MVC
request.GET
/request.POST
... usw. eingestellt werdentastypie
oderpiston
Nutzen Sie Middleware / Templatetags
Nutzen Sie Modellmanager
User
kann in einem gehenUserManager(models.Manager)
.models.Model
.queryset
könnten in a gehenmodels.Manager
.User
einzelnes erstellen , sodass Sie möglicherweise der Meinung sind, dass es auf dem Modell selbst basieren sollte. Beim Erstellen des Objekts verfügen Sie jedoch wahrscheinlich nicht über alle Details:Beispiel:
Verwenden Sie nach Möglichkeit Formulare
Wenn Sie Formulare haben, die einem Modell zugeordnet sind, kann viel Boilerplate-Code entfernt werden. Das
ModelForm documentation
ist ziemlich gut. Das Trennen von Code für Formulare vom Modellcode kann hilfreich sein, wenn Sie viele Anpassungen vornehmen (oder manchmal zyklische Importfehler für erweiterte Verwendungszwecke vermeiden).Verwenden Sie nach Möglichkeit Verwaltungsbefehle
yourapp/management/commands/createsuperuser.py
yourapp/management/commands/activateinbulk.py
Wenn Sie über Geschäftslogik verfügen, können Sie diese trennen
django.contrib.auth
verwendet Backends , genau wie db ein Backend hat ... etc.setting
für Ihre Business - Logik (zBAUTHENTICATION_BACKENDS
)django.contrib.auth.backends.RemoteUserBackend
yourapp.backends.remote_api.RemoteUserBackend
yourapp.backends.memcached.RemoteUserBackend
Backend-Beispiel:
könnte werden:
mehr über Designmuster
mehr über Schnittstellengrenzen
yourapp.models
yourapp.vendor
yourapp.libs
yourapp.libs.vendor
oderyourapp.vendor.libs
Kurz gesagt, Sie könnten haben
yourapp/core/backends.py
yourapp/core/models/__init__.py
yourapp/core/models/users.py
yourapp/core/models/questions.py
yourapp/core/backends.py
yourapp/core/forms.py
yourapp/core/handlers.py
yourapp/core/management/commands/__init__.py
yourapp/core/management/commands/closepolls.py
yourapp/core/management/commands/removeduplicates.py
yourapp/core/middleware.py
yourapp/core/signals.py
yourapp/core/templatetags/__init__.py
yourapp/core/templatetags/polls_extras.py
yourapp/core/views/__init__.py
yourapp/core/views/users.py
yourapp/core/views/questions.py
yourapp/core/signals.py
yourapp/lib/utils.py
yourapp/lib/textanalysis.py
yourapp/lib/ratings.py
yourapp/vendor/backends.py
yourapp/vendor/morebusinesslogic.py
yourapp/vendor/handlers.py
yourapp/vendor/middleware.py
yourapp/vendor/signals.py
yourapp/tests/test_polls.py
yourapp/tests/test_questions.py
yourapp/tests/test_duplicates.py
yourapp/tests/test_ratings.py
oder irgendetwas anderes, das dir hilft; Das Finden der benötigten Schnittstellen und Grenzen hilft Ihnen dabei.
quelle
Django verwendet eine leicht modifizierte Art von MVC. In Django gibt es kein Konzept für einen "Controller". Der nächstgelegene Proxy ist eine "Ansicht", die zu Verwechslungen mit MVC-Konvertierungen führt, da eine Ansicht in MVC eher der "Vorlage" von Django ähnelt.
In Django ist ein "Modell" nicht nur eine Datenbankabstraktion. In mancher Hinsicht teilt es die Pflicht mit der "Ansicht" des Django als Controller von MVC. Es enthält das gesamte Verhalten einer Instanz. Wenn diese Instanz als Teil ihres Verhaltens mit einer externen API interagieren muss, ist dies immer noch Modellcode. Tatsächlich müssen Modelle überhaupt nicht mit der Datenbank interagieren, sodass Sie sich Modelle vorstellen können, die vollständig als interaktive Ebene für eine externe API vorhanden sind. Es ist ein viel freieres Konzept eines "Modells".
quelle
In Django unterscheidet sich die MVC-Struktur, wie Chris Pratt sagte, von dem klassischen MVC-Modell, das in anderen Frameworks verwendet wird. Ich denke, der Hauptgrund dafür ist die Vermeidung einer zu strengen Anwendungsstruktur, wie dies in anderen MVC-Frameworks wie CakePHP der Fall ist.
In Django wurde MVC folgendermaßen implementiert:
Die Ansichtsebene ist zweigeteilt. Die Ansichten sollten nur zum Verwalten von HTTP-Anforderungen verwendet werden. Sie werden aufgerufen und antworten darauf. Ansichten kommunizieren mit dem Rest Ihrer Anwendung (Formulare, Modellformulare, benutzerdefinierte Klassen, in einfachen Fällen direkt mit Modellen). Um die Schnittstelle zu erstellen, verwenden wir Vorlagen. Vorlagen sind für Django stringartig, sie ordnen ihnen einen Kontext zu, und dieser Kontext wurde der Ansicht von der Anwendung mitgeteilt (wenn die Ansicht dies erfordert).
Die Modellebene bietet Kapselung, Abstraktion, Validierung und Intelligenz und macht Ihre Daten objektorientiert (sie sagen, dass DBMS dies eines Tages auch tun wird). Dies bedeutet nicht, dass Sie große models.py-Dateien erstellen sollten (in der Tat ist es ein sehr guter Rat, Ihre Modelle in verschiedene Dateien aufzuteilen, sie in einen Ordner namens 'models' zu legen und eine '__init__.py'-Datei darin zu erstellen Ordner, in den Sie alle Ihre Modelle importieren und schließlich das Attribut 'app_label' der models.Model-Klasse verwenden. Das Modell sollte Sie vom Umgang mit Daten abhalten, da dies Ihre Anwendung vereinfacht. Bei Bedarf sollten Sie auch externe Klassen wie "Werkzeuge" für Ihre Modelle erstellen. Sie können auch das Erbe in Modellen verwenden und das Attribut "abstrakt" der Meta-Klasse Ihres Modells auf "Wahr" setzen.
Wo ist der Rest? Nun, kleine Webanwendungen sind im Allgemeinen eine Art Schnittstelle zu Daten. In einigen kleinen Programmfällen würde es ausreichen, Ansichten zum Abfragen oder Einfügen von Daten zu verwenden. In häufigeren Fällen werden Formulare oder ModelForms verwendet, die eigentlich "Controller" sind. Dies ist nichts anderes als eine praktische und sehr schnelle Lösung für ein häufiges Problem. Es ist das, was eine Website verwendet.
Wenn Formulare für Sie nicht ausreichend sind, sollten Sie Ihre eigenen Klassen erstellen, um die Magie auszuführen. Ein sehr gutes Beispiel hierfür ist die Administratoranwendung: Sie können ModelAmin-Code lesen, dies funktioniert tatsächlich als Controller. Es gibt keine Standardstruktur. Ich empfehle Ihnen, vorhandene Django-Apps zu untersuchen. Dies hängt von jedem Fall ab. Dies ist, was Django-Entwickler beabsichtigten. Sie können eine XML-Parser-Klasse, eine API-Connector-Klasse, Sellerie für die Ausführung von Aufgaben hinzufügen, für eine reaktorbasierte Anwendung verdreht, nur das ORM verwenden, einen Webdienst erstellen, die Administratoranwendung ändern und vieles mehr. Es liegt in Ihrer Verantwortung, Code von guter Qualität zu erstellen, die MVC-Philosophie zu respektieren oder nicht, ihn modulbasiert zu gestalten und Ihre eigenen Abstraktionsschichten zu erstellen. Es ist sehr flexibel.
Mein Rat: Lies so viel Code wie möglich, es gibt viele Django-Anwendungen, aber nimm sie nicht so ernst. Jeder Fall ist anders, Muster und Theorie helfen, aber nicht immer, dies ist eine ungenaue Wissenschaft. Django bietet Ihnen nur gute Werkzeuge, mit denen Sie einige Probleme lindern können (wie Administrationsoberfläche, Validierung von Webformularen, i18n, Implementierung von Beobachtermustern usw.) die zuvor erwähnten und andere), aber gute Designs kommen von erfahrenen Designern.
PS.: Verwenden Sie die Klasse 'User' aus der Auth-Anwendung (aus dem Standard-Django). Sie können beispielsweise Benutzerprofile erstellen oder zumindest den Code lesen. Dies ist für Ihren Fall hilfreich.
quelle
Eine alte Frage, aber ich möchte trotzdem meine Lösung anbieten. Es basiert auf der Annahme, dass auch Modellobjekte zusätzliche Funktionen erfordern, während es schwierig ist, sie in der Datei models.py zu platzieren . Schwere Geschäftslogik kann je nach persönlichem Geschmack separat geschrieben werden, aber ich mag es zumindest, wenn das Modell alles macht, was mit sich selbst zu tun hat. Diese Lösung unterstützt auch diejenigen, die die gesamte Logik in den Modellen selbst platzieren möchten.
Aus diesem Grund habe ich einen Hack entwickelt , mit dem ich Logik von Modelldefinitionen trennen und trotzdem alle Hinweise von meiner IDE erhalten kann.
Die Vorteile sollten offensichtlich sein, aber dies listet einige auf, die ich beobachtet habe:
Ich habe dies mit Python 3.4 und höher und Django 1.8 und höher verwendet.
app / models.py
App / Logik / Benutzer.py
Das einzige, was ich nicht herausfinden kann, ist, wie ich meine IDE (in diesem Fall PyCharm) erkennen kann, dass UserLogic tatsächlich ein Benutzermodell ist. Aber da dies offensichtlich ein Hack ist, bin ich ziemlich glücklich, das kleine Ärgernis zu akzeptieren, immer Typ für
self
Parameter anzugeben .quelle
Ich würde dir zustimmen müssen. Es gibt viele Möglichkeiten in Django, aber der beste Ausgangspunkt ist die Überprüfung der Designphilosophie von Django .
Das Aufrufen einer API aus einer Modelleigenschaft wäre nicht ideal. Es scheint sinnvoller zu sein, in der Ansicht so etwas zu tun und möglicherweise eine Service-Schicht zu erstellen, um die Dinge trocken zu halten. Wenn der Aufruf der API nicht blockiert und der Aufruf teuer ist, kann es sinnvoll sein, die Anforderung an einen Servicemitarbeiter (einen Mitarbeiter, der aus einer Warteschlange verwendet) zu senden.
Gemäß der Designphilosophie von Django kapseln Modelle jeden Aspekt eines "Objekts". Daher sollte die gesamte Geschäftslogik, die sich auf dieses Objekt bezieht, dort leben:
Die von Ihnen beschriebenen Nebenwirkungen sind offensichtlich. Die Logik hier könnte besser in Querysets und Manager unterteilt werden. Hier ist ein Beispiel:
models.py
admin.py.
quelle
Ich stimme der gewählten Antwort größtenteils zu ( https://stackoverflow.com/a/12857584/871392) ), möchte jedoch eine Option im Abschnitt "Abfragen erstellen" hinzufügen.
Man kann QuerySet-Klassen für Modelle definieren, um Filterabfragen zu erstellen und so weiter. Danach können Sie diese Queryset-Klasse für den Manager des Modells als Proxy verwenden, wie dies bei den integrierten Manager- und QuerySet-Klassen der Fall ist.
Wenn Sie jedoch mehrere Datenmodelle abfragen mussten, um ein Domänenmodell zu erhalten, erscheint es mir vernünftiger, dies wie zuvor vorgeschlagen in ein separates Modul zu stellen.
quelle
Umfassendster Artikel zu den verschiedenen Optionen mit Vor- und Nachteilen:
Quelle: https://sunscrapers.com/blog/where-to-put-business-logic-django/
quelle
Django ist so konzipiert, dass es einfach zur Bereitstellung von Webseiten verwendet werden kann. Wenn Sie sich damit nicht wohl fühlen, sollten Sie vielleicht eine andere Lösung verwenden.
Ich schreibe die Root- oder allgemeinen Operationen auf dem Modell (um dieselbe Schnittstelle zu haben) und die anderen auf dem Controller des Modells. Wenn ich eine Operation von einem anderen Modell benötige, importiere ich dessen Controller.
Dieser Ansatz reicht mir und der Komplexität meiner Anwendungen.
Die Antwort von Hedde ist ein Beispiel, das die Flexibilität von Django und Python selbst zeigt.
Sehr interessante Frage trotzdem!
quelle