Wygląda na to, że pytasz o różnicę między modelem danych a modelem domeny - w tym drugim można znaleźć logikę biznesową i podmioty postrzegane przez użytkownika końcowego, w tym drugim miejscu faktycznie przechowujesz swoje dane.
Ponadto zinterpretowałem trzecią część twojego pytania jako: jak zauważyć brak oddzielenia tych modeli.
Są to dwie bardzo różne koncepcje i zawsze trudno jest je rozdzielić. Istnieją jednak pewne typowe wzorce i narzędzia, które można wykorzystać w tym celu.
O modelu domeny
Pierwszą rzeczą, którą musisz rozpoznać, jest to, że twój model domeny tak naprawdę nie dotyczy danych; chodzi o działania i pytania, takie jak „aktywuj tego użytkownika”, „dezaktywuj tego użytkownika”, „którzy użytkownicy są obecnie aktywowani?” i „jaka jest nazwa tego użytkownika?”. W klasycznym ujęciu: chodzi o zapytania i polecenia .
Myślenie w poleceniach
Zacznijmy od spojrzenia na polecenia w twoim przykładzie: „aktywuj tego użytkownika” i „dezaktywuj tego użytkownika”. Zaletą poleceń jest to, że można je łatwo wyrazić za pomocą małych scenariuszy:
podane nieaktywny użytkownik
, gdy administrator aktywuje ten użytkownik
następnie użytkownik staje się aktywny
i potwierdzenie e-mail zostanie wysłana do użytkownika
, a wpis zostanie dodany do dziennika systemowego
(itd. itd.)
Takie scenariusze są przydatne, aby zobaczyć, jak jedno polecenie może wpłynąć na różne części infrastruktury - w tym przypadku baza danych (jakiś rodzaj „aktywnej” flagi), serwer poczty, dziennik systemowy itp.
Taki scenariusz naprawdę pomaga w skonfigurowaniu środowiska programistycznego opartego na testach.
I wreszcie, myślenie w poleceniach naprawdę pomaga stworzyć aplikację zorientowaną na zadania. Twoi użytkownicy docenią to :-)
Wyrażanie poleceń
Django zapewnia dwa proste sposoby wyrażania poleceń; oba są poprawnymi opcjami i nie jest niczym niezwykłym połączenie obu podejść.
Warstwa usługi
Moduł serwisowy został już opisany przez @Hedde . Tutaj definiujesz oddzielny moduł, a każde polecenie jest reprezentowane jako funkcja.
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
Korzystanie z formularzy
Innym sposobem jest użycie formularza Django dla każdego polecenia. Wolę to podejście, ponieważ łączy w sobie wiele ściśle powiązanych aspektów:
- wykonanie polecenia (co on robi?)
- sprawdzanie poprawności parametrów polecenia (czy można to zrobić?)
- prezentacja polecenia (jak mogę to zrobić?)
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
Myślenie w zapytaniach
Twój przykład nie zawierał żadnych zapytań, więc skorzystałem z kilku przydatnych zapytań. Wolę używać terminu „pytanie”, ale zapytania to klasyczna terminologia. Interesujące zapytania to: „Jak nazywa się ten użytkownik?”, „Czy ten użytkownik może się zalogować?”, „Pokaż listę dezaktywowanych użytkowników” i „Jaki jest rozkład geograficzny dezaktywowanych użytkowników?”
Zanim zaczniesz odpowiadać na te zapytania, powinieneś zawsze zadać sobie dwa pytania: czy jest to zapytanie prezentacyjne tylko dla moich szablonów i / lub zapytanie logiki biznesowej związane z wykonywaniem moich poleceń i / lub zapytanie sprawozdawcze .
Zapytania prezentacyjne mają na celu jedynie ulepszenie interfejsu użytkownika. Odpowiedzi na zapytania dotyczące logiki biznesowej bezpośrednio wpływają na wykonywanie poleceń. Zapytania sprawozdawcze służą wyłącznie celom analitycznym i mają luźniejsze ograniczenia czasowe. Te kategorie nie wykluczają się wzajemnie.
Drugie pytanie brzmi: „czy mam pełną kontrolę nad odpowiedziami?” Na przykład podczas zapytania o nazwę użytkownika (w tym kontekście) nie mamy żadnej kontroli nad wynikiem, ponieważ polegamy na zewnętrznym interfejsie API.
Robienie zapytań
Najbardziej podstawowym zapytaniem w Django jest użycie obiektu Manager:
User.objects.filter(active=True)
Oczywiście działa to tylko wtedy, gdy dane są faktycznie reprezentowane w modelu danych. Nie zawsze tak jest. W takich przypadkach możesz rozważyć poniższe opcje.
Niestandardowe tagi i filtry
Pierwsza alternatywa jest przydatna w przypadku zapytań, które mają jedynie charakter prezentacyjny: tagi niestandardowe i filtry szablonów.
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)
Metody zapytań
Jeśli twoje zapytanie nie jest wyłącznie prezentacyjne, możesz dodać zapytania do services.py (jeśli go używasz) lub wprowadzić moduł 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
Modele proxy
Modele proxy są bardzo przydatne w kontekście logiki biznesowej i raportowania. Zasadniczo definiujesz ulepszony podzbiór swojego modelu. Można zastąpić podstawowy QuerySet menedżera, zastępując Manager.get_queryset()
metodę.
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
Modele zapytań
W przypadku zapytań, które są z natury złożone, ale są wykonywane dość często, istnieje możliwość modeli zapytań. Model zapytania jest formą denormalizacji, w której odpowiednie dane dla pojedynczego zapytania są przechowywane w osobnym modelu. Sztuczka polega oczywiście na utrzymaniu zsynchronizowanego modelu w synchronizacji z modelem podstawowym. Z modeli zapytań można korzystać tylko wtedy, gdy zmiany są całkowicie pod twoją kontrolą.
models.py
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
Pierwszą opcją jest aktualizacja tych modeli w twoich poleceniach. Jest to bardzo przydatne, jeśli modele te są zmieniane tylko za pomocą jednego lub dwóch poleceń.
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()
Lepszą opcją byłoby użycie niestandardowych sygnałów. Sygnały te są oczywiście emitowane przez twoje polecenia. Zaletą sygnałów jest to, że można zsynchronizować wiele modeli zapytań z modelem oryginalnym. Ponadto przetwarzanie sygnału można odciążyć do zadań w tle, używając Selera lub podobnych ram.
sygnały.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()
Utrzymanie w czystości
Stosując to podejście, niezwykle łatwo jest ustalić, czy kod pozostaje czysty. Postępuj zgodnie z tymi wskazówkami:
- Czy mój model zawiera metody, które robią więcej niż zarządzanie stanem bazy danych? Powinieneś wyodrębnić polecenie.
- Czy mój model zawiera właściwości, które nie są mapowane na pola bazy danych? Powinieneś wyodrębnić zapytanie.
- Czy mój model odwołuje się do infrastruktury, która nie jest moją bazą danych (np. Poczta)? Powinieneś wyodrębnić polecenie.
To samo dotyczy wyświetleń (ponieważ widoki często mają ten sam problem).
- Czy mój widok aktywnie zarządza modelami baz danych? Powinieneś wyodrębnić polecenie.
Niektóre referencje
Dokumentacja Django: modele proxy
Dokumentacja Django: sygnały
Architektura: Projektowanie oparte na domenie