Piszę projekt w Django i widzę, że 80% kodu znajduje się w pliku models.py
. Ten kod jest mylący i po pewnym czasie przestaję rozumieć, co się naprawdę dzieje.
Oto, co mnie niepokoi:
- Uważam za brzydkie, że mój poziom modelu (który miał być odpowiedzialny tylko za pracę z danymi z bazy danych) również wysyła wiadomości e-mail, chodzi po interfejsie API do innych usług itp.
- Ponadto uważam za niedopuszczalne umieszczanie logiki biznesowej w widoku, ponieważ w ten sposób trudno jest kontrolować. Na przykład w mojej aplikacji istnieją co najmniej trzy sposoby tworzenia nowych instancji
User
, ale technicznie powinno się je tworzyć jednolicie. - Nie zawsze zauważam, kiedy metody i właściwości moich modeli stają się niedeterministyczne i kiedy wywołują skutki uboczne.
Oto prosty przykład. Na początku User
model był taki:
class User(db.Models):
def get_present_name(self):
return self.name or 'Anonymous'
def activate(self):
self.status = 'activated'
self.save()
Z czasem zmieniło się to w:
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])
Chcę oddzielić jednostki w moim kodzie:
- Podmioty mojej bazy danych, poziom bazy danych: Co zawiera moją aplikację?
- Podmioty mojej aplikacji, poziom logiki biznesowej: Co może składać moją aplikację?
Jakie są dobre praktyki wdrażania takiego podejścia, które można zastosować w Django?
Odpowiedzi:
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:
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
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:
forms.py
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:
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
template_tags.py
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
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
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
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
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
forms.py
models.py
Utrzymanie w czystości
Stosując to podejście, niezwykle łatwo jest ustalić, czy kod pozostaje czysty. Postępuj zgodnie z tymi wskazówkami:
To samo dotyczy wyświetleń (ponieważ widoki często mają ten sam problem).
Niektóre referencje
Dokumentacja Django: modele proxy
Dokumentacja Django: sygnały
Architektura: Projektowanie oparte na domenie
źródło
User.objects.inactive_users()
. Ale przykładowy model proxy tutaj IMO prowadzi do niepoprawnej semantyki:u = InactiveUser.objects.all()[0]; u.active = True; u.save()
a jednakisinstance(u, InactiveUser) == True
. Wspomniałbym również, że skutecznym sposobem na utrzymanie modelu zapytań w wielu przypadkach jest widok db.Zwykle implementuję warstwę usług pomiędzy widokami i modelami. To działa jak interfejs API twojego projektu i daje ci dobry widok helikoptera na to, co się dzieje. Odziedziczyłem tę praktykę od mojego kolegi, który bardzo często stosuje tę technikę warstw w projektach Java (JSF), np .:
models.py
services.py
views.py
źródło
Przede wszystkim nie powtarzaj się .
Następnie uważaj, aby nie prześcignąć inżyniera, czasem jest to strata czasu i sprawia, że ktoś traci koncentrację na tym, co ważne. Przeglądu Zen pytona od czasu do czasu.
Zobacz aktywne projekty
repozytorium tkanina jest również jeden dobry patrzeć.
yourapp/models/logicalgroup.py
User
,Group
i związane z nimi modele mogą przejść podyourapp/models/users.py
Poll
,Question
,Answer
... może pójść podyourapp/models/polls.py
__all__
środkuyourapp/models/__init__.py
Więcej o MVC
request.GET
/request.POST
... itdtastypie
lubpiston
Skorzystaj z oprogramowania pośredniego / szablonów
Skorzystaj z menedżerów modeli
User
może przejść wUserManager(models.Manager)
.models.Model
.queryset
mogą iść wmodels.Manager
.User
jeden na raz, więc możesz pomyśleć, że powinien on żyć na samym modelu, ale podczas tworzenia obiektu prawdopodobnie nie masz wszystkich szczegółów:Przykład:
W miarę możliwości korzystaj z formularzy
Wiele kodów typu „płyta podstawowa” można wyeliminować, jeśli masz formularze odwzorowujące model. To
ModelForm documentation
jest całkiem dobre. Oddzielanie kodu formularzy od kodu modelu może być dobre, jeśli masz wiele możliwości dostosowywania (lub czasami unikniesz cyklicznych błędów importu w przypadku bardziej zaawansowanych zastosowań).Jeśli to możliwe, używaj poleceń zarządzania
yourapp/management/commands/createsuperuser.py
yourapp/management/commands/activateinbulk.py
jeśli masz logikę biznesową, możesz ją rozdzielić
django.contrib.auth
używa backendów , podobnie jak db ma backend ... itd.setting
logikę biznesową (np.AUTHENTICATION_BACKENDS
)django.contrib.auth.backends.RemoteUserBackend
yourapp.backends.remote_api.RemoteUserBackend
yourapp.backends.memcached.RemoteUserBackend
przykład backendu:
może stać się:
więcej o wzorach projektowych
więcej o granicach interfejsu
yourapp.models
yourapp.vendor
yourapp.libs
yourapp.libs.vendor
lubyourapp.vendor.libs
Krótko mówiąc, mógłbyś
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
lub cokolwiek innego, co ci pomoże; znalezienie potrzebnych interfejsów i granic pomoże.
źródło
Django stosuje nieco zmodyfikowany rodzaj MVC. W Django nie ma koncepcji „kontrolera”. Najbliższym serwerem proxy jest „widok”, który powoduje zamieszanie przy konwersji MVC, ponieważ w MVC widok bardziej przypomina „szablon” Django.
W Django „model” nie jest jedynie abstrakcją bazy danych. Pod pewnymi względami dzieli on obowiązek z „poglądem” Django na kontrolera MVC. Zawiera całość zachowań związanych z instancją. Jeśli to wystąpienie musi wchodzić w interakcję z zewnętrznym interfejsem API w ramach jego zachowania, to nadal jest to kod modelu. W rzeczywistości modele nie są w ogóle wymagane do interakcji z bazą danych, więc można sobie wyobrazić, że modele istnieją całkowicie jako warstwa interaktywna dla zewnętrznego interfejsu API. Jest to o wiele bardziej darmowa koncepcja „modelu”.
źródło
W Django struktura MVC jest, jak powiedział Chris Pratt, odmienna od klasycznego modelu MVC stosowanego w innych ramach, myślę, że głównym powodem tego jest unikanie zbyt ścisłej struktury aplikacji, jak ma to miejsce w innych ramach MVC, takich jak CakePHP.
W Django MVC zostało zaimplementowane w następujący sposób:
Warstwa widoku jest podzielona na dwie części. Widoki powinny być używane tylko do zarządzania żądaniami HTTP, są one wywoływane i odpowiadają na nie. Widoki komunikują się z resztą aplikacji (formularze, formularze modeli, klasy niestandardowe, w prostych przypadkach bezpośrednio z modelami). Do stworzenia interfejsu używamy szablonów. Szablony są podobne do napisów w Django, odwzorowuje w nich kontekst, a ten kontekst został przekazany do widoku przez aplikację (kiedy widok o to poprosi).
Warstwa modelu zapewnia enkapsulację, abstrakcję, sprawdzanie poprawności, inteligencję i sprawia, że twoje dane są zorientowane obiektowo (mówią, że pewnego dnia DBMS również to zrobi). Nie oznacza to, że powinieneś tworzyć ogromne pliki models.py (w rzeczywistości bardzo dobrą radą jest podzielenie modeli na różne pliki, umieszczenie ich w folderze zwanym „modelami”, utworzenie w tym pliku pliku „__init__.py” folder, w którym zaimportujesz wszystkie swoje modele i na koniec użyjesz atrybutu „app_label” modeli.Model). Model powinien oderwać Cię od pracy z danymi, dzięki czemu Twoja aplikacja będzie prostsza. W razie potrzeby powinieneś również utworzyć klasy zewnętrzne, takie jak „narzędzia” dla swoich modeli. Możesz także używać dziedzictwa w modelach, ustawiając atrybut „abstrakcyjny” klasy Meta swojego modelu na „True”.
Gdzie jest reszta? Cóż, małe aplikacje internetowe są na ogół swego rodzaju interfejsem do danych, w niektórych małych przypadkach programowych wystarczyłoby użyć widoków do zapytania lub wstawienia danych. Bardziej powszechne przypadki wykorzystują Formularze lub ModelFormy, które w rzeczywistości są „kontrolerami”. Nie jest to nic innego jak praktyczne rozwiązanie typowego problemu i bardzo szybki. Tego właśnie używa strona internetowa.
Jeśli formularze nie są dla ciebie enogh, powinieneś stworzyć własne klasy, aby wykonać magię, bardzo dobrym przykładem jest aplikacja administracyjna: możesz odczytać kod ModelAmin, to faktycznie działa jako kontroler. Nie ma standardowej struktury, proponuję zbadać istniejące aplikacje Django, zależy to od każdego przypadku. Tak zamierzali programiści Django: możesz dodać klasę parsera XML, klasę interfejsu API, dodać Selera do wykonywania zadań, przekręcać dla aplikacji opartej na reaktorach, używać tylko ORM, tworzyć serwis internetowy, modyfikować aplikację administracyjną i więcej. .. Twoim obowiązkiem jest tworzenie dobrej jakości kodu, szanowanie filozofii MVC lub nie, tworzenie bazujących na modułach i tworzenie własnych warstw abstrakcji. Jest bardzo elastyczny.
Moja rada: przeczytaj jak najwięcej kodu, istnieje wiele aplikacji django, ale nie bierz ich tak poważnie. Każdy przypadek jest inny, wzory i teoria pomagają, ale nie zawsze, jest to nieprecyzyjny przypadek, django zapewnia tylko dobre narzędzia, których możesz użyć, aby ożywić niektóre problemy (takie jak interfejs administratora, sprawdzanie poprawności formularza internetowego, i18n, implementacja wzorca obserwatora, wszystko wspomniane wcześniej i inne), ale dobre projekty pochodzą od doświadczonych projektantów.
PS .: użyj klasy „User” z aplikacji auth (ze standardowego django), możesz na przykład utworzyć profile użytkowników lub przynajmniej odczytać jego kod, będzie to przydatne w twoim przypadku.
źródło
Stare pytanie, ale i tak chciałbym zaoferować swoje rozwiązanie. Opiera się na akceptacji, że obiekty modelu również wymagają dodatkowej funkcjonalności, podczas gdy niewygodne jest umieszczanie ich w pliku models.py . Ciężka logika biznesowa może być napisana osobno w zależności od osobistych upodobań, ale przynajmniej podoba mi się ten model, aby robić wszystko, co związane z samym sobą. To rozwiązanie obsługuje również tych, którzy lubią mieć całą logikę w samych modelach.
Jako taki, wymyśliłem hack, który pozwala mi oddzielić logikę od definicji modelu i nadal uzyskiwać wszystkie wskazówki z mojego IDE.
Korzyści powinny być oczywiste, ale zawiera kilka zaobserwowanych przeze mnie:
Korzystałem z tego w Pythonie 3.4 i nowszych oraz Django 1.8 i nowszych.
app / models.py
app / logic / user.py
Jedyne, czego nie mogę zrozumieć, to jak sprawić, aby moje IDE (w tym przypadku PyCharm) rozpoznało, że UserLogic jest w rzeczywistości modelem użytkownika. Ale ponieważ jest to oczywiście włamanie, z przyjemnością akceptuję małe niedogodności związane z zawsze określaniem typu
self
parametru.źródło
Musiałbym się z tobą zgodzić. Istnieje wiele możliwości w Django, ale najlepiej zacząć od przeglądu filozofii projektowania Django .
Wywołanie interfejsu API z właściwości modelu nie byłoby idealne, wydaje się, że bardziej sensowne byłoby zrobienie czegoś takiego w widoku i ewentualnie stworzenie warstwy usług, aby utrzymać suchość. Jeśli wywołanie interfejsu API nie jest blokowane, a wywołanie jest kosztowne, sensowne może być wysłanie żądania do pracownika usługi (pracownika, który korzysta z kolejki).
Zgodnie z filozofią projektowania Django modele obejmują każdy aspekt „obiektu”. Dlatego cała logika biznesowa związana z tym obiektem powinna tam istnieć:
Opisane przez ciebie efekty uboczne są widoczne, logikę tutaj można lepiej podzielić na zestawy zapytań i menedżerów. Oto przykład:
models.py
admin.py
źródło
W większości zgadzam się z wybraną odpowiedzią ( https://stackoverflow.com/a/12857584/871392 ), ale chcę dodać opcję w sekcji Tworzenie zapytań.
Można zdefiniować klasy QuerySet dla modeli do tworzenia zapytań filtrów i tak dalej. Następnie możesz proxy tej klasy zestawów zapytań dla menedżera modelu, podobnie jak wbudowane klasy Menedżera i klasy QuerySet.
Chociaż, jeśli trzeba zapytać o kilka modeli danych, aby uzyskać jeden model domeny, wydaje mi się rozsądniejsze, aby umieścić to w osobnym module, jak sugerowano wcześniej.
źródło
Najbardziej obszerny artykuł na temat różnych opcji z zaletami i wadami:
Źródło: https://sunscrapers.com/blog/where-to-put-business-logic-django/
źródło
Django został zaprojektowany w taki sposób, aby można go było łatwo wykorzystywać do dostarczania stron internetowych. Jeśli nie czujesz się z tym komfortowo, być może powinieneś użyć innego rozwiązania.
Piszę root lub typowe operacje na modelu (aby mieć ten sam interfejs) i inne na kontrolerze modelu. Jeśli potrzebuję operacji z innego modelu, importuję jej kontroler.
To podejście wystarcza mi i złożoności moich aplikacji.
Odpowiedź Hedde jest przykładem pokazującym elastyczność samego django i samego pythona.
W każdym razie bardzo interesujące pytanie!
źródło