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