Django iş mantığı ve veri erişim ayırma
Django bir proje yazıyorum ve kod €'i dosyası models.py
olduğunu görüyorum. Bu kod kafa karıştırıcı ve belli bir zaman sonra, gerçekten ne olup bittiğini anlamıyor.
Canımı sıkan da bu işte:
- Benim seviyesi bir model bu çirkin buluyorum, olması gerekiyordu ( bir veritabanı) veri ile sadece bu iş için sorumlu olduğunu da bazı e-posta gönderme, diğer hizmetler, vb apı üzerinde yürür.
- Ayrıca, çünkü görünümünde kabul edilemez yerleştirme iş mantığı buluyorum,
bu şekilde kontrol edilmesi zor olur. Benim örneğin,
uygulama hangi bir yeni oluşturmak için en az üç yolu vardır
model
User
, ama teknik olarak düzgün bir yol oluşturmanız gerekir. - Her zaman bu yöntemleri ne zaman fark yoktur, ve benim modellerinin özelliklerini belirleyici olur ve var olduğunda yan etkileri.
Basit bir örnek. İlk başta şöyle oldu:
class User(db.Models):
def get_present_name(self):
return self.name or 'Anonymous'
def activate(self):
self.status = 'activated'
self.save()
Zamanla, bu dönüştü:
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'
def activate(self):
# method was to have a side effect (send message to user)
self.status = 'activated'
self.save()
send_mail('Your account is activated!', '…', [self.email])
Benim istediğim kod varlıkları ayırmak için:
- Benim uygulama içerir? veritabanı benim seviye varlıklar:
- Başvurum Ne? benim uygulama, iş mantığı seviye varlıklar:
Django uygulanabilecek böyle bir yaklaşım uygulamak için iyi uygulamalar nelerdir?benim sorum: Teşekkür ederim!
CEVAP
Arasındaki fark hakkında soruyorsun gibi görünüyorveri modeliveetki alanı modeli– ikincisi aslında veri depolamak nerede son kullanıcı tarafından algılanan, eski olduğu gibi iş mantığı ve kuruluşlar bulabilirsiniz.
Ayrıca, 3. sorunuza parçası yorumladım: bu modelleri ayrı tutmak, arıza bildirimi için.
Bu çok farklı iki kavram olup, her zaman sabit onları ayrı tutmak. Ancak, bazı ortak kalıpları ve bu amaç için kullanılabilecek araçlar vardır.
Hakkında Etki alanı Modeli
Anlaman gereken ilk şey bir etki alanı modeli çok veri ile ilgili değildir; hakkındaeylemlerivesoru"bu kullanıcı grubuna", "bu kullanıcı devre dışı bırakma", "hangi kullanıcılar şu anda etkin?", gibi ve "bu kullanıcı adı nedir?". Klasik terimlerle ilgilisorgularvekomutlar.
Komutları düşünme
Hadi örnek komutları Aramaya Başla: "bu kullanıcıyı etkinleştir" ve "bu kullanıcı devre dışı bırak". Komutları hakkında güzel bir şey, kolayca küçük-ne zaman-o zaman senaryo ile ifade edilebilir
verildietkin olmayan bir kullanıcı^ br . ne zamanadmin bu kullanıcı etkinleştirir^ br . sonrakullanıcı etkin hale gelir^ br . vebir onay e-posta kullanıcı için gönderilir^ br . vegiriş sistem günlüğüne eklenir^ br . (vb. vb.)
Böyle bir senaryo bu yararlı görmek nasıl farklı parçaların altyapı etkilenebilecek bir tek komut – bu durumda veritabanı (bir çeşit 'aktif' bayrak), posta sunucusu, sistem günlüğü, vb.
Böyle bir senaryo da gerçekten Test Odaklı Geliştirme ortamı kurma konusunda size yardımcı oluyor.
Ve son olarak, komutları düşünme çok görev odaklı bir uygulama oluşturmanıza yardımcı olur. Kullanıcılarınız bu takdir edecektir :-)
Komutları İfade
Django ifade komutları iki kolay yol sağlar; Her ikisi de geçerli seçenekler var ve bu durum iki yaklaşım karıştırın.
Servis katmanı
servis modülüzaten described by @Hedde. Burada ayrı bir modül tanımlamak ve her bir komut bir fonksiyonu olarak temsil edilir.
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
Formları kullanma
Diğer taraftan, her komut için Django bir Form kullanmaktır. Yakından ilgili birden çok yönlerini birleştirir, çünkü bu yaklaşım tercih ederim:
- komut yürütme (?)
- komut parametreleri doÄŸrulama (bunu yapabilir mi?)
- komut sunum (bunu nasıl yapabilirim?)
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
Sorgular düşünme
Örnek herhangi bir sorunuz içermiyordu, bir kaç yararlı sorgu yapma cüretinde bulundum. Terimini kullanmayı tercih ederim "soru", ama sorgu klasik terminoloji. İlginç sorgular: "bu kullanıcı adı Nedir?", "Bu kullanıcı giriş yapabilir mi?", "Beni devre dışı kullanıcıların bir listesini göstermek" ve "aktif kullanıcılar coğrafi dağılımı Nedir?"
Bu sorgular cevaplamaya başlamadan önce, her zaman kendinize iki soru sormalısınız: birgörselsadece benim için sorgu şablonları, ve/veya biriş mantığıbenim sorgu komutları yürütmek için bağlı, ve/veya birraporlamasorgu.
Görsel sorgu sadece kullanıcı arayüzü geliştirmek için yapılır. İş mantığı sorguları için cevaplar doğrudan komutlar yürütme etkiler. Raporlama sorgularını sadece analitik amaçlarla ve gevşek zaman kısıtlamaları var. Bu kategoriler birbirini dışlayan değil.
Diğer soru: "cevapları üzerinde tam bir kontrolü var mı?" Kullanıcı adı (bu bağlamda) sorgularken örneğin, harici API güveniyoruz çünkü sonuç üzerinde herhangi bir kontrol yok.
Sorgular, Yapma
Django en temel sorgu Yöneticisi nesnesi kullanımı
User.objects.filter(active=True)
Tabii ki, bu verileri aslında veri modeli temsil ise sadece çalışır. Bu her zaman böyle değildir. Bu durumda, aşağıdaki seçenekleri göz önünde bulundurun.
Özel etiketler ve filtreler
İlk alternatif sadece görsel sorguları için yararlıdır: özel şablon etiketleri ve filtreleri.
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)
Sorgu yöntemleri
Eğer sorgu sadece görsel değil, senin için sorgular ekleyebilirsinizservices.pyeğer kullanıyorsanız (), ya da bir takdimqueries.pymodül:
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
Proxy modeller
Proxy modeller iş mantığı ve raporlama bağlamında çok yararlıdır. Temelde model geliştirilmiş bir alt kümesi tanımlayın.
models.py
class InactiveUserManager(models.Manager):
def get_query_set(self):
query_set = super(InactiveUserManager, self).get_query_set()
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
Sorgu modeller
Doğası gereği karmaşık, ancak oldukça sık çalıştırılan sorgular için sorgu modeller olasılığı vardır. Sorgu bir model tek bir sorgu için ilgili verileri ayrı bir model olarak depolandığı denormalization şeklidir. Tabii hile birincil model ile senkronize normal dışı modeli tutmaktır. Sorgu modeller yalnızca değişiklikleri tamamen sizin kontrolünüz altında ise kullanılabilir.
models.py
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
İlk seçenek komutlarınızı bu modeller güncellemek için. Bu eğer bu modeller sadece bir ya da iki komut ile değiştirilirse çok yararlıdır.
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()
Daha iyi bir seçenek özel sinyaller kullanmak olacaktır. Bu sinyaller elbette komutları tarafından üretilir. Sinyalleri senkronize birden fazla sorgu modeller orijinal modeli ile tutabilir avantajı var. Ayrıca, sinyal işleme arka plan görevleri, Kereviz kullanarak ya da benzer çerçeveler için boşaltılabilir.
signals.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()
Onu temiz tutmak
Bu yaklaşımı kullanarak, gülünç kodunuz temiz kalır eğer belirlemek için kolay olur. Sadece aşağıdaki yönergeleri izleyin:
- Benim model veritabanı devleti yönetmek daha mı yöntemlerini içeriyor mu? Bir komut ayıklamak gerekir.
- Benim model veritabanı alanları eşleme özelliklerini içeriyor mu? Bir sorgu ayıklamak gerekir.
- Benim veritabanı (mail gibi) olmayan modeli referans benim altyapı mu? Bir komut ayıklamak gerekir.
Aynı görüş için görüşlerini sık sık aynı sorundan muzdarip çünkü) gider.
- Benim görüşüme aktif veritabanı modelleri başarıyor? Bir komut ayıklamak gerekir.
Django ÅŸablonlar dan settings.py eriÅŸi...
lensler, fclabels, yapı erişim ve muta...
Neden=hata AYIKLAMA Yanlış ayar benim ...
Veri Eşleyici, Tablo Veri Ağ Geçidi (G...
Neden erişim adb kullanırken veri klas...