611 Stimmen

Trennung von Geschäftslogik und Datenzugriff in django

Ich schreibe ein Projekt in Django und ich sehe, dass 80% des Codes in der Datei ist models.py . Dieser Code ist verwirrend und nach einer gewissen Zeit verstehe ich nicht mehr, was wirklich passiert.

Folgendes stört mich:

  1. Ich finde es hässlich, dass meine Modellebene (die eigentlich nur für die Arbeit mit Daten aus einer Datenbank zuständig ist) auch E-Mails versendet, über API zu anderen Diensten geht usw.
  2. Außerdem halte ich es für inakzeptabel, Geschäftslogik in der Ansicht zu platzieren, weil denn auf diese Weise wird es schwierig, sie zu kontrollieren. Zum Beispiel gibt es in meiner Anwendung gibt es mindestens drei Möglichkeiten, neue Instanzen von User aber technisch gesehen sollte es sie einheitlich erstellen.
  3. Ich merke nicht immer, wenn die Methoden und Eigenschaften meiner Modelle nicht-deterministisch werden und wenn sie Nebeneffekte entwickeln.

Hier ist ein einfaches Beispiel. Zunächst wird die User Modell war so:

class User(db.Models):

    def get_present_name(self):
        return self.name or 'Anonymous'

    def activate(self):
        self.status = 'activated'
        self.save()

Mit der Zeit wurde daraus das hier:

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:

  1. Entitäten auf Datenbankebene, d. h. Logik auf Datenbankebene: Welche Art von Daten speichert meine Anwendung?
  2. Entitäten auf Anwendungsebene, d. h. Logik auf Geschäftsebene: Was macht meine Anwendung?

Was sind die bewährten Verfahren zur Umsetzung eines solchen Ansatzes, der in Django angewendet werden kann?

188voto

Hedde van der Heide Punkte 20395

Normalerweise implementiere ich eine Dienstschicht zwischen Ansichten und Modellen. Diese fungiert wie die API Ihres Projekts und gibt Ihnen einen guten Überblick darüber, was vor sich geht. Ich habe diese Praxis von einem Kollegen geerbt, der diese Schichtungstechnik häufig bei Java-Projekten (JSF) einsetzt, z. B:

models.py

class Book:
   author = models.ForeignKey(User)
   title = models.CharField(max_length=125)

   class Meta:
       app_label = "library"

dienste.py

from library.models import Book

def get_books(limit=None, **filters):
    """ simple service function for retrieving books can be widely extended """
    return Book.objects.filter(**filters)[:limit]  # list[:None] will return the entire list

views.py

from library.services import get_books

class BookListView(ListView):
    """ simple view, e.g. implement a _build and _apply filters function """
    queryset = get_books()

Allerdings nehme ich normalerweise Modelle, Ansichten und Dienste auf Modulebene und je nach Größe des Projekts sogar noch weiter trennen

84voto

dnozay Punkte 22949

Zuallererst, Wiederholen Sie sich nicht .

Achten Sie also darauf, dass Sie sich nicht übernehmen, denn manchmal ist das reine Zeitverschwendung und führt dazu, dass man den Blick für das Wesentliche verliert. Überprüfen Sie die zen von python von Zeit zu Zeit.

Werfen Sie einen Blick auf aktive Projekte

  • mehr Menschen = mehr Bedarf an guter Organisation
  • die django repository sie haben eine einfache Struktur.
  • die Pip-Repository sie haben eine einfache Verzeichnisstruktur.
  • die Gewebedepot ist auch eine gute Möglichkeit, einen Blick darauf zu werfen.

    • können Sie alle Ihre Modelle unter yourapp/models/logicalgroup.py
  • z.B. User , Group und verwandte Modelle können unter yourapp/models/users.py
  • z.B. Poll , Question , Answer ... könnte untergehen yourapp/models/polls.py
  • Laden Sie, was Sie brauchen, in __all__ innerhalb von yourapp/models/__init__.py

Mehr über MVC

  • Modell sind Ihre Daten
    • dies umfasst Ihre aktuellen Daten
    • Dazu gehören auch Ihre Sitzungs-/Cookie-/Cache-/Festplatten-/Indexdaten.
  • der Benutzer interagiert mit dem Controller, um das Modell zu manipulieren
    • dies könnte eine API oder eine Ansicht sein, die Ihre Daten speichert/aktualisiert
    • dies kann abgestimmt werden mit request.GET / request.POST ...usw.
    • denken ausrufen o Filtrieren auch.
  • die Daten aktualisieren die Ansicht
    • die Vorlagen übernehmen die Daten und formatieren sie entsprechend
    • APIs, auch ohne Vorlagen, sind Teil der Ansicht; z. B. tastypie o piston
    • Dies sollte auch für die Middleware gelten.

Nutzen Sie die Vorteile Middleware / Vorlagentags

  • Wenn für jede Anfrage ein gewisser Aufwand betrieben werden muss, ist Middleware eine Möglichkeit.
    • z.B. Hinzufügen von Zeitstempeln
    • z.B. Aktualisierung der Metriken über Seitenaufrufe
    • z.B. Auffüllen eines Caches
  • Wenn Sie Codeschnipsel haben, die bei der Formatierung von Objekten immer wieder vorkommen, sind Templatetags gut.
    • z.B. aktiver Tab / Url-Breadcrumbs

Nutzen Sie die Vorteile Modell-Manager

  • Erstellung von User kann in einer UserManager(models.Manager) .
  • blutige Details für die Instanzen sollten auf der models.Model .
  • blutige Details für queryset könnte in einem models.Manager .
  • möchten Sie vielleicht eine User Sie denken vielleicht, dass sie im Modell selbst untergebracht werden sollte, aber wenn Sie das Objekt erstellen, haben Sie wahrscheinlich nicht alle Details:

Exemple :

class UserManager(models.Manager):
   def create_user(self, username, ...):
      # plain create
   def create_superuser(self, username, ...):
      # may set is_superuser field.
   def activate(self, username):
      # may use save() and send_mail()
   def activate_in_bulk(self, queryset):
      # may use queryset.update() instead of save()
      # may use send_mass_mail() instead of send_mail()

Verwenden Sie nach Möglichkeit Formulare

Wenn Sie Formulare haben, die einem Modell zugeordnet sind, können Sie auf eine Menge Standardcode verzichten. Die ModelForm documentation ist ziemlich gut. Die Trennung des Codes für Formulare vom Modellcode kann gut sein, wenn Sie viele Anpassungen vornehmen (oder manchmal zyklische Importfehler für fortgeschrittene Anwendungen vermeiden).

使用する Verwaltungsbefehle wenn möglich

  • z.B.. yourapp/management/commands/createsuperuser.py
  • z.B.. yourapp/management/commands/activateinbulk.py

Wenn Sie eine Geschäftslogik haben, können Sie diese herauslösen.

  • django.contrib.auth verwendet Backends so wie db ein Backend hat...usw.
  • einfügen setting für Ihre Geschäftslogik (z. B. AUTHENTICATION_BACKENDS )
  • könnten Sie verwenden django.contrib.auth.backends.RemoteUserBackend
  • könnten Sie verwenden yourapp.backends.remote_api.RemoteUserBackend
  • könnten Sie verwenden yourapp.backends.memcached.RemoteUserBackend
  • die schwierige Geschäftslogik an das Backend delegieren
  • Stellen Sie sicher, dass die Erwartung an den Eingang/Ausgang richtig eingestellt ist.
  • die Änderung der Geschäftslogik ist so einfach wie die Änderung einer Einstellung :)

Backend-Beispiel:

class User(db.Models):
    def get_present_name(self): 
        # property became not deterministic in terms of database
        # data is taken from another service by api
        return remote_api.request_user_name(self.uid) or 'Anonymous' 

werden könnte:

class User(db.Models):
   def get_present_name(self):
      for backend in get_backends():
         try:
            return backend.get_present_name(self)
         except: # make pylint happy.
            pass
      return None

mehr über Entwurfsmuster

mehr über Schnittstellengrenzen

  • Ist der Code, den Sie verwenden möchten, wirklich Teil der Modelle? -> yourapp.models
  • Ist der Code Teil der Geschäftslogik? -> yourapp.vendor
  • Ist der Code Teil allgemeiner Tools/Libs? -> yourapp.libs
  • Ist der Code Teil der Geschäftslogik-Libs? -> yourapp.libs.vendor o yourapp.vendor.libs
  • Hier ist eine gute Frage: Können Sie Ihren Code unabhängig testen?
    • ja, gut :)
    • nein, Sie haben möglicherweise ein Schnittstellenproblem
    • wenn es eine klare Trennung gibt, sollte Unittest ein Kinderspiel sein mit die Verwendung von Spott
  • Ist die Trennung logisch?
    • ja, gut :)
    • Nein, es kann schwierig sein, diese logischen Konzepte getrennt zu testen.
  • Glauben Sie, dass Sie refaktorisieren müssen, wenn Sie 10x mehr Code erhalten?
    • ja, nicht gut, no bueno, Refaktor könnte eine Menge Arbeit sein
    • Nein, das ist einfach fantastisch!

Kurz gesagt, Sie könnten

  • 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 Ihnen hilft; das Finden der Schnittstellen, die Sie benötigen und die Grenzen wird Ihnen helfen.

31voto

Chris Pratt Punkte 219403

Django verwendet eine leicht modifizierte Art von MVC. In Django gibt es kein Konzept für einen "Controller". Der nächstgelegene Proxy ist ein "View", was bei MVC-Konvertierungen zu Verwirrung führt, da ein View in MVC eher einer "Vorlage" von Django entspricht.

In Django ist ein "Modell" nicht nur eine Datenbankabstraktion. In gewisser Hinsicht teilt es die Aufgabe mit der "View" von Django als Controller von MVC. Es enthält die Gesamtheit des mit einer Instanz verbundenen Verhaltens. Wenn diese Instanz als Teil ihres Verhaltens mit einer externen API interagieren muss, dann ist das immer noch Modellcode. Tatsächlich müssen Modelle überhaupt nicht mit der Datenbank interagieren, so dass man sich Modelle vorstellen kann, die ausschließlich als interaktive Schicht für eine externe API existieren. Das ist ein viel freieres Konzept eines "Modells".

11voto

Nate Gentile Punkte 382

In Django ist die MVC-Struktur, wie Chris Pratt sagte, anders als das klassische MVC-Modell, das in anderen Frameworks verwendet wird. Ich denke, der Hauptgrund dafür ist, eine zu strenge Anwendungsstruktur zu vermeiden, wie sie in anderen MVC-Frameworks wie CakePHP vorkommt.

In Django wurde MVC auf die folgende Weise implementiert:

Die Ansichtsebene ist in zwei Teile geteilt. Die Views sollten nur zur Verwaltung von HTTP-Anfragen verwendet werden, sie werden aufgerufen und beantworten diese. Ansichten kommunizieren mit dem Rest Ihrer Anwendung (Formulare, Modellformulare, benutzerdefinierte Klassen, oder in einfachen Fällen direkt mit Modellen). Um die Schnittstelle zu erstellen, verwenden wir Templates. Templates sind für Django string-ähnlich, sie bilden einen Kontext ab, und dieser Kontext wird von der Anwendung an den View übermittelt (wenn der View fragt).

Die Modellschicht sorgt für Kapselung, Abstraktion, Validierung, Intelligenz und macht Ihre Daten objektorientiert (man sagt, dass dies eines Tages auch für DBMS gilt). Das bedeutet nicht, dass Sie riesige models.py Dateien erstellen sollten (tatsächlich ist ein sehr guter Ratschlag, Ihre Modelle in verschiedene Dateien aufzuteilen, sie in einen Ordner namens 'models' zu legen, eine '__init__.py' Datei in diesem Ordner zu erstellen, in die Sie alle Ihre Modelle importieren und schließlich das Attribut 'app_label' der models.Model Klasse verwenden). Model sollte Sie von der Arbeit mit Daten abstrahieren, das wird Ihre Anwendung einfacher machen. Bei Bedarf sollten Sie auch externe Klassen, wie z.B. "Tools" für Ihre Modelle erstellen. Sie können auch Erbe in Modellen verwenden, indem Sie das Attribut "abstract" der Meta-Klasse Ihres Modells auf "True" setzen.

Wo ist der Rest? Nun, kleine Web-Anwendungen sind in der Regel eine Art Schnittstelle zu Daten, in einigen kleinen Programm Fällen mit Ansichten zum Abfragen oder Einfügen von Daten würde genug sein. In häufigeren Fällen werden Formulare oder ModelForms verwendet, die eigentlich "Controller" sind. Dies ist nichts anderes als eine praktische Lösung für ein häufiges Problem, und zwar eine sehr schnelle. Das ist es, was eine Website zu tun pflegt.

Wenn Forms nicht genug für Sie sind, dann sollten Sie Ihre eigenen Klassen erstellen, um die Magie zu tun, ein sehr gutes Beispiel dafür ist Admin-Anwendung: Sie können ModelAmin Code lesen, das tatsächlich als Controller funktioniert. Es gibt keine Standardstruktur, ich schlage vor, dass Sie sich bestehende Django-Anwendungen ansehen, es kommt auf den jeweiligen Fall an. Das ist das, was die Django-Entwickler beabsichtigt haben. Sie können eine XML-Parser-Klasse hinzufügen, eine API-Connector-Klasse, Celery für die Ausführung von Aufgaben hinzufügen, sich für eine Reaktor-basierte Anwendung entscheiden, nur den ORM verwenden, einen Web-Service erstellen, die Admin-Anwendung modifizieren und vieles mehr... Es liegt in Ihrer Verantwortung, qualitativ hochwertigen Code zu erstellen, die MVC-Philosophie zu respektieren oder nicht, es modulbasiert zu machen und Ihre eigenen Abstraktionsschichten zu erstellen. Es ist sehr flexibel.

Mein Rat: Lesen Sie so viel Code wie möglich, es gibt viele Django-Anwendungen, aber nehmen Sie sie nicht so ernst. Jeder Fall ist anders, Muster und Theorie hilft, aber nicht immer, dies ist eine ungenaue Wissenschaft, django nur bieten Ihnen gute Werkzeuge, die Sie verwenden können, um einige Schmerzen zu lindern (wie Admin-Interface, Web-Formular-Validierung, i18n, Beobachter-Muster-Implementierung, alle zuvor genannten und andere), aber gute Designs kommen von erfahrenen Designern.

PS: verwenden Sie 'User' Klasse von Auth-Anwendung (von Standard-Django), können Sie zum Beispiel Benutzerprofile zu machen, oder zumindest lesen Sie seinen Code, es wird für Ihren Fall nützlich sein.

1voto

velis Punkte 7083

Eine alte Frage, aber ich möchte trotzdem meine Lösung anbieten. Sie basiert auf der Annahme, dass auch Modellobjekte einige zusätzliche Funktionen benötigen, während es umständlich ist, diese innerhalb der models.py . Umfangreiche Geschäftslogik kann je nach persönlichem Geschmack separat geschrieben werden, aber ich möchte zumindest, dass das Modell alles, was mit ihm zusammenhängt, selbst erledigt. Diese Lösung unterstützt auch diejenigen, die die gesamte Logik in den Modellen selbst unterbringen möchten.

Als solches habe ich Folgendes entwickelt ein Hacken die es mir ermöglicht, die Logik von den Modelldefinitionen zu trennen und trotzdem alle Hinweise von meiner IDE zu erhalten.

Die Vorteile sollten auf der Hand liegen, aber hier sind einige, die ich beobachtet habe:

  • DB-Definitionen bleiben genau das - kein logischer "Müll" angehängt
  • Die modellbezogene Logik ist übersichtlich an einem Ort platziert
  • Alle Dienste (Formulare, REST, Ansichten) haben einen einzigen Zugangspunkt zur Logik
  • Das Beste von allem: Ich musste keinen Code neu schreiben, als ich erkannte, dass meine models.py zu unübersichtlich wurde und die Logik abgetrennt werden musste. Die Trennung ist reibungslos und iterativ: Ich konnte eine Funktion nach der anderen oder eine ganze Klasse oder die gesamte models.py bearbeiten.

Ich habe dies mit Python 3.4 und höher und Django 1.8 und höher verwendet.

app/models.py

....
from app.logic.user import UserLogic

class User(models.Model, UserLogic):
    field1 = models.AnyField(....)
    ... field definitions ...

app/logic/user.py

if False:
    # This allows the IDE to know about the User model and its member fields
    from main.models import User

class UserLogic(object):
    def logic_function(self: 'User'):
        ... code with hinting working normally ...

Das einzige, was ich nicht herausfinden kann, ist, wie meine IDE (PyCharm in diesem Fall) zu erkennen, dass UserLogic tatsächlich User-Modell ist. Aber da dies offensichtlich ein Hack ist, bin ich ganz glücklich, das kleine Ärgernis zu akzeptieren, immer den Typ für self Parameter.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X