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?

1voto

radtek Punkte 30114

Ich muss Ihnen zustimmen. Es gibt eine Menge Möglichkeiten in Django, aber der beste Ort, um zu beginnen, ist die Überprüfung der Die Design-Philosophie von Django .

  1. Der Aufruf einer API aus einer Modelleigenschaft wäre nicht ideal, es scheint sinnvoller zu sein, so etwas in der Ansicht zu tun und möglicherweise eine Dienstschicht zu erstellen, um die Dinge trocken zu halten. Wenn der Aufruf der API nicht blockiert und der Aufruf teuer ist, könnte es sinnvoll sein, die Anforderung an einen Service Worker zu senden (einen Worker, der aus einer Warteschlange konsumiert).

  2. Gemäß der Designphilosophie von Django kapseln Modelle jeden Aspekt eines "Objekts". Daher sollte die gesamte Geschäftslogik in Bezug auf dieses Objekt dort untergebracht werden:

Einbeziehung aller relevanten Fachlogik

Modelle sollten jeden Aspekt eines "Objekts" kapseln, in Anlehnung an das Active Record Design Pattern von Martin Fowler.

  1. Die von Ihnen beschriebenen Nebeneffekte sind offensichtlich, die Logik könnte hier besser in Querysets und Manager aufgeteilt werden. Hier ist ein Beispiel:

    models.py

    import datetime
    
    from djongo import models
    from django.db.models.query import QuerySet
    from django.contrib import admin
    from django.db import transaction
    
    class MyUser(models.Model):
    
        present_name = models.TextField(null=False, blank=True)
        status = models.TextField(null=False, blank=True)
        last_active = models.DateTimeField(auto_now=True, editable=False)
    
        # As mentioned you could put this in a template tag to pull it
        # from cache there. Depending on how it is used, it could be
        # retrieved from within the admin view or from a custom view
        # if that is the only place you will use it.
        #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'
    
        # Moved to admin as an action
        # def activate(self):
        #     # method now has a side effect (send message to user)
        #     self.status = 'activated'
        #     self.save()
        #     # send email via email service
        #     #send_mail('Your account is activated!', '…', [self.email])
    
        class Meta:
            ordering = ['-id']  # Needed for DRF pagination
    
        def __unicode__(self):
            return '{}'.format(self.pk)
    
    class MyUserRegistrationQuerySet(QuerySet):
    
        def for_inactive_users(self):
            new_date = datetime.datetime.now() - datetime.timedelta(days=3*365)  # 3 Years ago
            return self.filter(last_active__lte=new_date.year)
    
        def by_user_id(self, user_ids):
            return self.filter(id__in=user_ids)
    
    class MyUserRegistrationManager(models.Manager):
    
        def get_query_set(self):
            return MyUserRegistrationQuerySet(self.model, using=self._db)
    
        def with_no_activity(self):
            return self.get_query_set().for_inactive_users()

    admin.py

    # Then in model admin
    
    class MyUserRegistrationAdmin(admin.ModelAdmin):
        actions = (
            'send_welcome_emails',
        )
    
        def send_activate_emails(self, request, queryset):
            rows_affected = 0
            for obj in queryset:
                with transaction.commit_on_success():
                    # send_email('welcome_email', request, obj) # send email via email service
                    obj.status = 'activated'
                    obj.save()
                    rows_affected += 1
    
            self.message_user(request, 'sent %d' % rows_affected)
    
    admin.site.register(MyUser, MyUserRegistrationAdmin)

0voto

l0ki Punkte 111

Ich bin mit der gewählten Antwort weitgehend einverstanden ( https://stackoverflow.com/a/12857584/871392 ), möchte aber eine Option im Abschnitt "Abfragen" hinzufügen.

Man kann QuerySet-Klassen für Modelle definieren, um Filterabfragen zu machen und so weiter. Danach kann man diese QuerySet-Klasse als Proxy für den Manager des Modells verwenden, wie es die eingebauten Manager- und QuerySet-Klassen tun.

Wenn man allerdings mehrere Datenmodelle abfragen muss, um ein Domänenmodell zu erhalten, erscheint es mir sinnvoller, dies in ein separates Modul zu packen, wie zuvor vorgeschlagen.

0voto

FSE Punkte 36

Umfassender Artikel über die verschiedenen Optionen mit Vor- und Nachteilen:

  1. Idee Nr. 1: Fette Modelle
  2. Idee Nr. 2: Geschäftslogik in Ansichten/Formulare einbauen
  3. Idee #3: Dienstleistungen
  4. Idee Nr. 4: QuerySets/Manager
  5. Schlussfolgerung

Quelle: https://sunscrapers.com/blog/where-to-put-business-logic-django/

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