Pemisahan logika bisnis dan akses data di Django

484

Saya menulis proyek di Django dan saya melihat bahwa 80% dari kode ada di file models.py. Kode ini membingungkan dan, setelah waktu tertentu, saya berhenti memahami apa yang sebenarnya terjadi.

Inilah yang menggangguku:

  1. Saya merasa jelek bahwa level model saya (yang seharusnya hanya bertanggung jawab atas pekerjaan dengan data dari database) juga mengirim email, berjalan di API ke layanan lain, dll.
  2. Juga, saya merasa tidak dapat diterima untuk menempatkan logika bisnis dalam tampilan, karena cara ini menjadi sulit untuk dikendalikan. Sebagai contoh, dalam aplikasi saya setidaknya ada tiga cara untuk membuat contoh baru User, tetapi secara teknis harus membuatnya secara seragam.
  3. Saya tidak selalu memperhatikan ketika metode dan properti model saya menjadi non-deterministik dan ketika mereka mengembangkan efek samping.

Ini adalah contoh sederhana. Pada awalnya, Usermodelnya seperti ini:

class User(db.Models):

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

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

Seiring waktu, itu berubah menjadi ini:

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])

Yang saya inginkan adalah memisahkan entitas dalam kode saya:

  1. Entitas basis data saya, tingkat basis data: Apa yang mengandung aplikasi saya?
  2. Entitas aplikasi saya, tingkat logika bisnis: Apa yang dapat membuat aplikasi saya?

Apa praktik baik untuk menerapkan pendekatan seperti itu yang dapat diterapkan di Django?

defuz
sumber
14
Baca tentang sinyal
Konstant
1
baik Anda menghapus tag tetapi Anda bisa menggunakan DCI untuk menyelesaikan pemisahan apa yang dilakukan sistem (fungsionalitas) dan apa sistemnya (model data / domain)
Rune FS
2
Anda mengusulkan untuk menerapkan semua logika bisnis dalam panggilan balik sinyal? Sayangnya, tidak semua aplikasi saya dapat ditautkan ke acara di basis data.
defuz
Rune FS, saya mencoba menggunakan DCI, tetapi bagi saya sepertinya tidak perlu banyak untuk proyek saya: Konteks, definisi peran sebagai mixin ke objek, dll. Ada cara pemisahan yang lebih mudah "lakukan" dan " adalah"? Bisakah Anda memberi contoh minimal?
defuz

Jawaban:

635

Sepertinya Anda bertanya tentang perbedaan antara model data dan model domain - yang terakhir adalah di mana Anda dapat menemukan logika dan entitas bisnis seperti yang dirasakan oleh pengguna akhir Anda, yang pertama adalah tempat Anda benar-benar menyimpan data Anda.

Selain itu, saya telah menafsirkan bagian ke-3 dari pertanyaan Anda sebagai: bagaimana memperhatikan kegagalan untuk memisahkan model-model ini.

Ini adalah dua konsep yang sangat berbeda dan selalu sulit untuk memisahkannya. Namun, ada beberapa pola dan alat umum yang dapat digunakan untuk tujuan ini.

Tentang Model Domain

Hal pertama yang perlu Anda ketahui adalah bahwa model domain Anda tidak benar-benar tentang data; ini tentang tindakan dan pertanyaan seperti "aktifkan pengguna ini", "nonaktifkan pengguna ini", "pengguna mana yang saat ini diaktifkan?", dan "siapa nama pengguna ini?". Dalam istilah klasik: ini tentang kueri dan perintah .

Berpikir dalam Perintah

Mari kita mulai dengan melihat perintah dalam contoh Anda: "aktifkan pengguna ini" dan "nonaktifkan pengguna ini". Hal yang menyenangkan tentang perintah adalah bahwa mereka dapat dengan mudah diekspresikan oleh skenario yang diberikan saat kecil:

diberikan pengguna yang tidak aktif
ketika admin mengaktifkan pengguna ini
maka pengguna menjadi aktif
dan email konfirmasi dikirim ke pengguna
dan entri ditambahkan ke log sistem
(dll.)

Skenario seperti itu berguna untuk melihat bagaimana bagian-bagian berbeda dari infrastruktur Anda dapat dipengaruhi oleh satu perintah - dalam hal ini database Anda (semacam bendera 'aktif'), server mail Anda, log sistem Anda, dll.

Skenario seperti itu juga sangat membantu Anda dalam menyiapkan lingkungan Pengembangan Berbasis Tes.

Dan akhirnya, berpikir dalam perintah sangat membantu Anda membuat aplikasi berorientasi tugas. Pengguna Anda akan menghargai ini :-)

Mengekspresikan Perintah

Django menyediakan dua cara mudah untuk mengekspresikan perintah; keduanya adalah opsi yang valid dan bukan tidak biasa untuk menggabungkan kedua pendekatan.

Lapisan layanan

The modul layanan telah dijelaskan oleh @Hedde . Di sini Anda mendefinisikan modul terpisah dan setiap perintah direpresentasikan sebagai suatu fungsi.

services.py

def activate_user(user_id):
    user = User.objects.get(pk=user_id)

    # set active flag
    user.active = True
    user.save()

    # mail user
    send_mail(...)

    # etc etc

Menggunakan formulir

Cara lain adalah dengan menggunakan Formulir Django untuk setiap perintah. Saya lebih suka pendekatan ini, karena menggabungkan beberapa aspek yang terkait erat:

  • pelaksanaan perintah (apa fungsinya?)
  • validasi parameter perintah (dapatkah ini dilakukan?)
  • presentasi perintah (bagaimana saya bisa melakukan ini?)

forms.py

class ActivateUserForm(forms.Form):

    user_id = IntegerField(widget = UsernameSelectWidget, verbose_name="Select a user to activate")
    # the username select widget is not a standard Django widget, I just made it up

    def clean_user_id(self):
        user_id = self.cleaned_data['user_id']
        if User.objects.get(pk=user_id).active:
            raise ValidationError("This user cannot be activated")
        # you can also check authorizations etc. 
        return user_id

    def execute(self):
        """
        This is not a standard method in the forms API; it is intended to replace the 
        'extract-data-from-form-in-view-and-do-stuff' pattern by a more testable pattern. 
        """
        user_id = self.cleaned_data['user_id']

        user = User.objects.get(pk=user_id)

        # set active flag
        user.active = True
        user.save()

        # mail user
        send_mail(...)

        # etc etc

Berpikir dalam Pertanyaan

Contoh Anda tidak mengandung pertanyaan apa pun, jadi saya mengambil kebebasan untuk membuat beberapa pertanyaan yang bermanfaat. Saya lebih suka menggunakan istilah "pertanyaan", tetapi pertanyaan adalah terminologi klasik. Pertanyaan yang menarik adalah: "Apa nama pengguna ini?", "Bisakah pengguna ini login?", "Tunjukkan daftar pengguna yang dinonaktifkan", dan "Apa distribusi geografis dari pengguna yang dinonaktifkan?"

Sebelum memulai menjawab pertanyaan ini, Anda harus selalu bertanya pada diri sendiri dua pertanyaan: apakah ini permintaan presentasi hanya untuk template saya, dan / atau permintaan logika bisnis terkait dengan menjalankan perintah saya, dan / atau permintaan pelaporan .

Pertanyaan presentasi hanya dibuat untuk meningkatkan antarmuka pengguna. Jawaban untuk pertanyaan logika bisnis secara langsung mempengaruhi pelaksanaan perintah Anda. Pertanyaan pelaporan hanya untuk tujuan analitis dan memiliki batasan waktu yang lebih longgar. Kategori-kategori ini tidak saling eksklusif.

Pertanyaan lainnya adalah: "Apakah saya memiliki kendali penuh atas jawaban?" Misalnya, ketika menanyakan nama pengguna (dalam konteks ini) kami tidak memiliki kendali atas hasilnya, karena kami mengandalkan API eksternal.

Membuat Pertanyaan

Permintaan paling mendasar di Django adalah penggunaan objek Manajer:

User.objects.filter(active=True)

Tentu saja, ini hanya berfungsi jika data benar-benar terwakili dalam model data Anda. Ini tidak selalu terjadi. Dalam hal ini, Anda dapat mempertimbangkan opsi di bawah ini.

Tag dan filter khusus

Alternatif pertama berguna untuk kueri yang hanya bersifat presentasi: tag khusus dan filter template.

template.html

<h1>Welcome, {{ user|friendly_name }}</h1>

template_tags.py

@register.filter
def friendly_name(user):
    return remote_api.get_cached_name(user.id)

Metode kueri

Jika kueri Anda bukan hanya presentasi, Anda bisa menambahkan kueri ke services.py Anda (jika Anda menggunakannya), atau memperkenalkan modul queries.py :

queries.py

def inactive_users():
    return User.objects.filter(active=False)


def users_called_publysher():
    for user in User.objects.all():
        if remote_api.get_cached_name(user.id) == "publysher":
            yield user 

Model proksi

Model proxy sangat berguna dalam konteks logika bisnis dan pelaporan. Anda pada dasarnya mendefinisikan subset yang disempurnakan dari model Anda. Anda bisa mengganti QuerySet basis Manajer dengan mengganti Manager.get_queryset()metode.

models.py

class InactiveUserManager(models.Manager):
    def get_queryset(self):
        query_set = super(InactiveUserManager, self).get_queryset()
        return query_set.filter(active=False)

class InactiveUser(User):
    """
    >>> for user in InactiveUser.objects.all():
    …        assert user.active is False 
    """

    objects = InactiveUserManager()
    class Meta:
        proxy = True

Model permintaan

Untuk kueri yang secara inheren rumit, tetapi dieksekusi cukup sering, ada kemungkinan model kueri. Model kueri adalah bentuk denormalisasi di mana data yang relevan untuk satu kueri disimpan dalam model terpisah. Triknya tentu saja adalah menjaga agar model yang dinormalisasi tetap sinkron dengan model utama. Model kueri hanya dapat digunakan jika perubahan sepenuhnya di bawah kendali Anda.

models.py

class InactiveUserDistribution(models.Model):
    country = CharField(max_length=200)
    inactive_user_count = IntegerField(default=0)

Opsi pertama adalah memperbarui model-model ini dalam perintah Anda. Ini sangat berguna jika model ini hanya diubah oleh satu atau dua perintah.

forms.py

class ActivateUserForm(forms.Form):
    # see above

    def execute(self):
        # see above
        query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
        query_model.inactive_user_count -= 1
        query_model.save()

Pilihan yang lebih baik adalah menggunakan sinyal khusus. Sinyal-sinyal ini tentu saja dipancarkan oleh perintah Anda. Sinyal memiliki keuntungan bahwa Anda dapat tetap menyinkronkan beberapa model permintaan dengan model asli Anda. Selanjutnya, pemrosesan sinyal dapat diturunkan ke tugas-tugas latar belakang, menggunakan Seledri atau kerangka kerja serupa.

signal.py

user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])

forms.py

class ActivateUserForm(forms.Form):
    # see above

    def execute(self):
        # see above
        user_activated.send_robust(sender=self, user=user)

models.py

class InactiveUserDistribution(models.Model):
    # see above

@receiver(user_activated)
def on_user_activated(sender, **kwargs):
        user = kwargs['user']
        query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
        query_model.inactive_user_count -= 1
        query_model.save()

Menjaga kebersihannya

Saat menggunakan pendekatan ini, sangat mudah untuk menentukan apakah kode Anda tetap bersih. Cukup ikuti panduan ini:

  • Apakah model saya berisi metode yang lebih dari sekadar mengelola keadaan basis data? Anda harus mengekstrak perintah.
  • Apakah model saya mengandung properti yang tidak memetakan ke bidang basis data? Anda harus mengekstrak kueri.
  • Apakah infrastruktur referensi model saya yang bukan basis data saya (seperti surat)? Anda harus mengekstrak perintah.

Hal yang sama berlaku untuk pandangan (karena pandangan sering menderita dari masalah yang sama).

  • Apakah pandangan saya secara aktif mengelola model basis data? Anda harus mengekstrak perintah.

Beberapa Referensi

Dokumentasi Django: model proxy

Dokumentasi Django: sinyal

Arsitektur: Desain Berbasis Domain

publysher
sumber
11
Sangat menyenangkan melihat jawaban yang memasukkan DDD ke pertanyaan terkait Django. Hanya karena Django mempekerjakan ActiveRecord untuk ketekunan, tidak berarti pemisahan kekhawatiran harus hilang. Jawaban yang bagus
Scott Coates
6
Jika saya ingin memvalidasi bahwa pengguna looged adalah pemilik objek sebelum menghapus objek itu, haruskah saya memeriksa itu dalam tampilan atau dalam modul formulir / layanan?
Ivan
6
@Van: keduanya. Itu harus dalam bentuk / modul layanan karena merupakan bagian dari kendala bisnis Anda. Ini harus juga berada dalam pandangan karena Anda seharusnya hanya tindakan ini bahwa pengguna dapat benar-benar melaksanakan.
publysher
4
Manajer kustom metode adalah cara yang baik untuk melaksanakan query: User.objects.inactive_users(). Tetapi contoh model proksi di sini IMO mengarah ke semantik yang salah: u = InactiveUser.objects.all()[0]; u.active = True; u.save()dan belum isinstance(u, InactiveUser) == True. Juga saya akan menyebutkan cara yang efektif untuk mempertahankan model permintaan dalam banyak kasus adalah dengan tampilan db.
Aryeh Leib Taurog
1
@adnanmuttaleb Ini benar. Perhatikan bahwa jawaban itu sendiri hanya menggunakan istilah "Model Domain". Saya telah menyertakan tautan ke DDD bukan karena jawaban saya adalah DDD, tetapi karena buku itu bekerja sangat baik dalam membantu Anda berpikir tentang model domain.
publysher
148

Saya biasanya menerapkan lapisan layanan di antara tampilan dan model. Ini bertindak seperti API proyek Anda dan memberi Anda pandangan helikopter yang baik tentang apa yang sedang terjadi. Saya mewarisi praktik ini dari seorang rekan saya yang banyak menggunakan teknik pelapisan ini dengan proyek Java (JSF), misalnya:

models.py

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

   class Meta:
       app_label = "library"

services.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()

Pikiran Anda, saya biasanya mengambil model, pandangan dan layanan ke tingkat modul dan terpisah lebih jauh tergantung pada ukuran proyek

Hedde van der Heide
sumber
8
Saya suka pendekatan umum meskipun dari pemahaman saya contoh spesifik Anda biasanya akan diterapkan sebagai manajer .
arie
9
@arie belum tentu, mungkin contoh yang lebih baik, untuk layanan webshop akan mencakup hal-hal seperti menghasilkan sesi keranjang, tugas asinkron seperti penghitungan peringkat produk, membuat dan mengirim email, dan sebagainya
Hedde van der Heide
4
Saya suka pendekatan ini juga, datang juga dari sana java. Saya baru mengenal python, Bagaimana Anda menguji views.py? Bagaimana Anda akan mengejek lapisan layanan (jika, misalnya, layanan membuat beberapa panggilan api jarak jauh)?
Teimuraz
71

Pertama-tama, Jangan ulangi diri Anda sendiri .

Maka, harap berhati-hati untuk tidak overengineer, kadang-kadang hanya buang-buang waktu, dan membuat seseorang kehilangan fokus pada apa yang penting. Tinjau zen python dari waktu ke waktu.

Lihatlah proyek aktif

  • lebih banyak orang = lebih banyak kebutuhan untuk berorganisasi dengan baik
  • yang Django repositori mereka memiliki struktur sederhana.
  • yang repositori pip mereka memiliki struktur direktori straigtforward.
  • yang repositori kain juga satu yang baik untuk melihat.

    • Anda dapat menempatkan semua model Anda di bawah yourapp/models/logicalgroup.py
  • misalnya User, Groupdan model terkait bisa gagalyourapp/models/users.py
  • misalnya Poll, Question, Answer... bisa pergi di bawahyourapp/models/polls.py
  • memuat apa yang Anda butuhkan di __all__dalamyourapp/models/__init__.py

Lebih lanjut tentang MVC

  • model adalah data Anda
    • ini termasuk data aktual Anda
    • ini juga termasuk data sesi / cookie / cache / fs / indeks Anda
  • pengguna berinteraksi dengan pengontrol untuk memanipulasi model
    • ini bisa berupa API, atau tampilan yang menyimpan / memperbarui data Anda
    • ini bisa disetel dengan request.GET/request.POST ... dll
    • pikirkan juga paging atau filtering .
  • data memperbarui tampilan
    • templat mengambil data dan memformatnya sesuai
    • API bahkan tanpa template adalah bagian dari tampilan; misalnya tastypieataupiston
    • ini juga harus menjelaskan middleware.

Manfaatkan middleware / templatetags

  • Jika Anda perlu melakukan beberapa pekerjaan untuk setiap permintaan, middleware adalah salah satu cara untuk melakukannya.
    • misal menambahkan cap waktu
    • mis. memperbarui metrik tentang klik halaman
    • mis. mengisi cache
  • Jika Anda memiliki potongan kode yang selalu terulang untuk memformat objek, templatetag bagus.
    • mis., remah tab / url roti aktif

Manfaatkan manajer model

  • menciptakan Userbisa masuk a UserManager(models.Manager).
  • detail berdarah untuk contoh harus pergi pada models.Model.
  • detail berdarah untuk querysetbisa masuk a models.Manager.
  • Anda mungkin ingin membuat Usersatu per satu, jadi Anda mungkin berpikir bahwa itu harus hidup pada model itu sendiri, tetapi saat membuat objek, Anda mungkin tidak memiliki semua detail:

Contoh:

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()

Manfaatkan formulir jika memungkinkan

Banyak kode boilerplate dapat dihilangkan jika Anda memiliki formulir yang memetakan ke model. The ModelForm documentationcukup bagus. Memisahkan kode untuk formulir dari kode model bisa baik jika Anda memiliki banyak penyesuaian (atau terkadang menghindari kesalahan impor siklik untuk penggunaan yang lebih lanjut).

Gunakan perintah manajemen bila memungkinkan

  • misalnya yourapp/management/commands/createsuperuser.py
  • misalnya yourapp/management/commands/activateinbulk.py

jika Anda memiliki logika bisnis, Anda dapat memisahkannya

  • django.contrib.auth menggunakan backend , sama seperti db memiliki backend ... dll.
  • tambahkan a settinguntuk logika bisnis Anda (mis. AUTHENTICATION_BACKENDS)
  • Anda bisa menggunakannya django.contrib.auth.backends.RemoteUserBackend
  • Anda bisa menggunakannya yourapp.backends.remote_api.RemoteUserBackend
  • Anda bisa menggunakannya yourapp.backends.memcached.RemoteUserBackend
  • mendelegasikan logika bisnis yang sulit ke backend
  • pastikan untuk mengatur harapan tepat pada input / output.
  • mengubah logika bisnis semudah mengubah pengaturan :)

contoh backend:

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' 

bisa menjadi:

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

lebih lanjut tentang pola desain

lebih lanjut tentang batas antarmuka

  • Apakah kode yang ingin Anda gunakan benar-benar bagian dari model? ->yourapp.models
  • Apakah kode bagian dari logika bisnis? ->yourapp.vendor
  • Apakah kode bagian dari alat generik / lib? ->yourapp.libs
  • Apakah kode bagian dari lib logika bisnis? -> yourapp.libs.vendoratauyourapp.vendor.libs
  • Ini yang bagus: dapatkah Anda menguji kode Anda secara independen?
    • ya baik :)
    • tidak, Anda mungkin memiliki masalah antarmuka
    • ketika ada pemisahan yang jelas, unittest harus mudah dengan penggunaan ejekan
  • Apakah pemisahan itu logis?
    • ya baik :)
    • tidak, Anda mungkin kesulitan menguji konsep-konsep logis secara terpisah.
  • Apakah Anda pikir Anda perlu melakukan refactor ketika Anda mendapatkan kode 10x lebih banyak?
    • ya, tidak bagus, tidak bueno, refactor bisa jadi banyak pekerjaan
    • tidak, itu luar biasa!

Singkatnya, Anda bisa melakukannya

  • 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

atau apa pun yang membantu Anda; menemukan antarmuka yang Anda butuhkan dan batas - batas akan membantu Anda.

tidak tahu
sumber
27

Django menggunakan jenis MVC yang sedikit dimodifikasi. Tidak ada konsep "pengontrol" di Django. Proxy terdekat adalah "tampilan", yang cenderung menyebabkan kebingungan dengan konversi MVC karena dalam MVC tampilan lebih mirip "templat" Django.

Di Django, "model" bukan sekadar abstraksi basis data. Dalam beberapa hal, ia berbagi tugas dengan "tampilan" Django sebagai pengontrol MVC. Itu memegang keseluruhan perilaku yang terkait dengan sebuah instance. Jika instance itu perlu berinteraksi dengan API eksternal sebagai bagian dari perilakunya, maka itu masih kode model. Faktanya, model tidak diharuskan untuk berinteraksi dengan database sama sekali, sehingga Anda dapat memiliki model yang sepenuhnya ada sebagai lapisan interaktif ke API eksternal. Ini adalah konsep "model" yang jauh lebih bebas.

Chris Pratt
sumber
7

Dalam Django, struktur MVC adalah seperti yang dikatakan Chris Pratt, berbeda dari model MVC klasik yang digunakan dalam kerangka kerja lain, saya pikir alasan utama untuk melakukan ini adalah menghindari struktur aplikasi yang terlalu ketat, seperti yang terjadi pada kerangka kerja MVC lainnya seperti CakePHP.

Di Django, MVC dilaksanakan dengan cara berikut:

Lapisan tampilan dibagi menjadi dua. Pandangan harus digunakan hanya untuk mengelola permintaan HTTP, mereka dipanggil dan menanggapinya. Tampilan berkomunikasi dengan seluruh aplikasi Anda (formulir, formulir model, kelas khusus, dalam kasus sederhana langsung dengan model). Untuk membuat antarmuka, kami menggunakan Template. Template mirip-string ke Django, memetakan konteks ke dalamnya, dan konteks ini dikomunikasikan ke tampilan oleh aplikasi (ketika tampilan bertanya).

Lapisan Model memberikan enkapsulasi, abstraksi, validasi, intelijen, dan membuat data Anda berorientasi objek (kata mereka suatu hari nanti DBMS juga akan). Ini tidak berarti bahwa Anda harus membuat file models.py besar (sebenarnya saran yang sangat bagus adalah untuk membagi model Anda menjadi file yang berbeda, masukkan ke dalam folder yang disebut 'model', buat file '__init__.py' ke dalam file ini. folder di mana Anda mengimpor semua model Anda dan akhirnya menggunakan atribut 'app_label' dari models.Model kelas). Model harus abstrak Anda dari operasi dengan data, itu akan membuat aplikasi Anda lebih sederhana. Anda juga harus, jika diperlukan, membuat kelas eksternal, seperti "alat" untuk model Anda. Anda juga dapat menggunakan warisan dalam model, mengatur atribut 'abstrak' dari kelas Meta model Anda ke 'Benar'.

Di mana sisanya? Nah, aplikasi web kecil umumnya adalah semacam antarmuka untuk data, dalam beberapa kasus program kecil menggunakan tampilan untuk permintaan atau memasukkan data sudah cukup. Kasus yang lebih umum akan menggunakan Formulir atau ModelForms, yang sebenarnya "pengendali". Ini tidak lain dari solusi praktis untuk masalah umum, dan sangat cepat. Itulah yang digunakan situs web untuk dilakukan.

Jika Formulir tidak enogh untuk Anda, maka Anda harus membuat kelas Anda sendiri untuk melakukan keajaiban, contoh yang sangat bagus dari ini adalah aplikasi admin: Anda dapat membaca kode ModelAmin, ini sebenarnya berfungsi sebagai pengontrol. Tidak ada struktur standar, saya sarankan Anda untuk memeriksa aplikasi Django yang ada, itu tergantung pada setiap kasus. Inilah yang dimaksudkan pengembang Django, Anda dapat menambahkan kelas parser xml, kelas konektor API, menambahkan Seledri untuk melakukan tugas, memutar untuk aplikasi berbasis reaktor, hanya menggunakan ORM, membuat layanan web, memodifikasi aplikasi admin dan banyak lagi. .. Ini adalah tanggung jawab Anda untuk membuat kode kualitas yang baik, menghormati filosofi MVC atau tidak, membuatnya berdasarkan modul dan membuat lapisan abstraksi Anda sendiri. Ini sangat fleksibel.

Saran saya: baca kode sebanyak yang Anda bisa, ada banyak aplikasi Django, tetapi jangan menganggapnya serius. Setiap kasus berbeda, pola dan teori membantu, tetapi tidak selalu, ini merupakan pengalaman yang tidak tepat, Django hanya memberikan Anda alat yang baik yang dapat Anda gunakan untuk menghilangkan beberapa rasa sakit (seperti antarmuka admin, validasi formulir web, i18n, penerapan pola pengamat, semua yang disebutkan sebelumnya dan lainnya), tetapi desain yang baik datang dari desainer berpengalaman.

PS:. Gunakan kelas 'Pengguna' dari aplikasi auth (dari standar Django), Anda dapat membuat misalnya profil pengguna, atau setidaknya membaca kode, itu akan berguna untuk kasus Anda.

Nate Gentile
sumber
1

Sebuah pertanyaan lama, tapi saya tetap ingin menawarkan solusi saya. Ini didasarkan pada penerimaan bahwa objek model juga memerlukan beberapa fungsionalitas tambahan sementara itu canggung untuk menempatkannya di dalam models.py . Logika bisnis yang berat dapat ditulis secara terpisah tergantung pada selera pribadi, tetapi saya setidaknya suka model untuk melakukan segala sesuatu yang berkaitan dengan dirinya sendiri. Solusi ini juga mendukung mereka yang suka menempatkan semua logika di dalam model itu sendiri.

Karena itu, saya merancang hack yang memungkinkan saya untuk memisahkan logika dari definisi model dan masih mendapatkan semua petunjuk dari IDE saya.

Keuntungannya harus jelas, tetapi ini mencantumkan beberapa yang telah saya amati:

  • Definisi DB tetap saja - tidak ada logika "sampah" yang terpasang
  • Logika terkait model semuanya ditempatkan dengan rapi di satu tempat
  • Semua layanan (formulir, REST, tampilan) memiliki satu titik akses ke logika
  • Terbaik dari semua: Saya tidak perlu menulis ulang kode apa pun setelah saya menyadari bahwa models.py saya menjadi terlalu berantakan dan harus memisahkan logika. Pemisahannya halus dan berulang: saya bisa melakukan suatu fungsi pada suatu waktu atau seluruh kelas atau seluruh model.py.

Saya telah menggunakan ini dengan Python 3.4 dan lebih besar dan Django 1.8 dan lebih besar.

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 ...

Satu-satunya hal yang saya tidak tahu adalah bagaimana membuat IDE saya (PyCharm dalam kasus ini) mengenali bahwa UserLogic sebenarnya adalah model Pengguna. Tapi karena ini jelas hack, saya cukup senang menerima sedikit gangguan dari selalu menentukan tipe untuk selfparameter.

velis
sumber
Sebenarnya saya melihatnya sebagai pendekatan yang mudah digunakan. Tetapi saya akan memindahkan model akhir ke file lain, dan tidak mewarisi dalam models.py. Ini akan seperti service.py adalah model clash userlogic +
Maks
1

Saya harus setuju dengan Anda. Ada banyak kemungkinan dalam Django tetapi tempat terbaik untuk memulai adalah meninjau filosofi desain Django .

  1. Memanggil API dari properti model tidak akan ideal, sepertinya akan lebih masuk akal untuk melakukan sesuatu seperti ini dalam tampilan dan mungkin membuat lapisan layanan untuk menjaga hal-hal tetap kering. Jika panggilan ke API non-blocking dan panggilan itu mahal, mengirimkan permintaan ke pekerja layanan (pekerja yang mengkonsumsi dari antrian) mungkin masuk akal.

  2. Sesuai model filosofi desain Django merangkum setiap aspek dari "objek". Jadi semua logika bisnis yang terkait dengan objek itu harus tinggal di sana:

Sertakan semua logika domain yang relevan

Model harus merangkum setiap aspek dari "objek," mengikuti pola desain Rekaman Aktif Martin Fowler.

  1. Efek samping yang Anda jelaskan jelas, logika di sini bisa lebih baik dipecah menjadi Querysets dan manajer. Berikut ini sebuah contoh:

    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)
radtek
sumber
0

Saya sebagian besar setuju dengan jawaban yang dipilih ( https://stackoverflow.com/a/12857584/871392 ), tetapi ingin menambahkan opsi di bagian Making Queries.

Satu dapat mendefinisikan kelas QuerySet untuk model untuk membuat permintaan filter dan sebagainya. Setelah itu Anda dapat mem-proxy kelas queryset ini untuk manajer model, seperti yang dilakukan oleh manajer dan kelas QuerySet.

Meskipun, jika Anda harus meminta beberapa model data untuk mendapatkan satu model domain, tampaknya lebih masuk akal bagi saya untuk menempatkan ini dalam modul terpisah seperti yang disarankan sebelumnya.

lki
sumber
0

Artikel paling komprehensif tentang berbagai opsi dengan pro dan kontra:

  1. Gagasan # 1: Model Gemuk
  2. Gagasan # 2: Menempatkan Logika Bisnis dalam Tampilan / Formulir
  3. Gagasan # 3: Layanan
  4. Gagasan # 4: QuerySets / Manajer
  5. Kesimpulan

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

FSE
sumber
Anda harus menambahkan beberapa penjelasan.
m02ph3u5
-6

Django dirancang agar mudah digunakan untuk mengirimkan halaman web. Jika Anda tidak nyaman dengan ini mungkin Anda harus menggunakan solusi lain.

Saya sedang mengerjakan root atau operasi umum pada model (untuk memiliki antarmuka yang sama) dan yang lain pada pengontrol model. Jika saya memerlukan operasi dari model lain saya mengimpor controller-nya.

Pendekatan ini cukup bagi saya dan kompleksitas aplikasi saya.

Tanggapan Hedde adalah contoh yang menunjukkan fleksibilitas django dan python itu sendiri.

Pertanyaan yang sangat menarik!

pvilas
sumber
9
Bagaimana mengatakan bahwa itu cukup baik bagi Anda membantu pemahaman saya tentang pertanyaan Anda?
Chris Wesseling
1
Django memang memiliki lebih banyak untuk ditawarkan selain dari django.db.models, tetapi sebagian besar ekosistem sangat bergantung pada model Anda menggunakan model django.
andho
1
Pola desain yang digunakan untuk mengembangkan perangkat lunak. Dan Django dirancang agar mudah digunakan untuk mengirimkan perangkat lunak dalam skala menengah atau besar, bukan hanya halaman web!
Mohammad Torkashvand