Tak więc często spotykam się z sytuacją, w której moje modele zaczynają:
- Wyhoduj potwory z mnóstwem metod
LUB
- Pozwalają ci przekazywać im fragmenty SQL, dzięki czemu są wystarczająco elastyczne, aby nie wymagały miliona różnych metod
Załóżmy na przykład, że mamy model „widżetu”. Zaczynamy od kilku podstawowych metod:
- get ($ id)
- wstaw ($ record)
- aktualizacja ($ id, $ record)
- usuń ($ id)
- getList () // pobierz listę widżetów
Wszystko w porządku i elegancko, ale potrzebujemy raportów:
- listCreatedBetween ($ data_początkowa, $ data_końcowa)
- listPurchasedBetween ($ data_początkowa, $ data_końcowa)
- listOfPending ()
A potem raportowanie zaczyna się komplikować:
- listPendingCreatedBetween ($ data_początkowa, $ data_końcowa)
- listForCustomer ($ identyfikator_użytkownika)
- listPendingCreatedBetweenForCustomer ($ identyfikator_użytkownika, $ data_początkowa, $ data_końcowa)
Możesz zobaczyć, gdzie to rośnie ... w końcu mamy tak wiele specyficznych wymagań dotyczących zapytań, że albo muszę wdrożyć mnóstwo metod, albo jakiś obiekt „zapytania”, który mogę przekazać do pojedynczego -> zapytania (zapytania $ zapytanie) metoda ...
... lub po prostu ugryź kulę i zacznij robić coś takiego:
- list = MyModel-> zapytanie („data_początkowa> X I data_końcowa <T ORAZ w toku = 1 ORAZ identyfikator_użytkownika = Z”)
Istnieje pewna apelacja, aby mieć tylko jedną taką metodę zamiast 50 milionów innych bardziej szczegółowych metod ... ale czasami wydaje się „niewłaściwe” wrzucanie do kontrolera stosu tego, co w zasadzie jest SQL.
Czy istnieje „właściwy” sposób radzenia sobie z takimi sytuacjami? Czy wydaje się akceptowalne umieszczanie takich zapytań w ogólnej -> query () metodzie?
Czy są lepsze strategie?
źródło
Odpowiedzi:
Wzorce architektury aplikacji korporacyjnych Martina Fowlera opisują szereg szablonów związanych z ORM, w tym użycie obiektu zapytania, co sugeruję.
Obiekty zapytania umożliwiają przestrzeganie zasady pojedynczej odpowiedzialności poprzez rozdzielenie logiki dla każdego zapytania na indywidualnie zarządzane i utrzymywane obiekty strategii. Albo kontroler może bezpośrednio zarządzać ich użyciem, lub przekazać to drugiemu kontrolerowi lub obiektowi pomocniczemu.
Czy będzie ich dużo? Na pewno. Czy niektóre można pogrupować w ogólne zapytania? Tak ponownie.
Czy można użyć wstrzykiwania zależności do tworzenia obiektów z metadanych? Tak właśnie działa większość narzędzi ORM.
źródło
Nie ma właściwego sposobu, aby to zrobić. Wiele osób korzysta z ORM, aby pozbyć się całej złożoności. Niektóre bardziej zaawansowane ORM tłumaczą wyrażenia kodu na skomplikowane instrukcje SQL. ORM mają również swoje wady, jednak w przypadku wielu zastosowań korzyści przewyższają koszty.
Jeśli nie pracujesz z ogromnym zestawem danych, najprostszą rzeczą jest wybranie całej tabeli do pamięci i przefiltrowanie kodu.
W przypadku wewnętrznych aplikacji raportujących takie podejście jest prawdopodobnie w porządku. Jeśli zestaw danych jest naprawdę duży, zaczniesz potrzebować wielu niestandardowych metod, a także odpowiednich indeksów w tabeli.
źródło
Niektóre ORM pozwalają konstruować złożone zapytania, zaczynając od podstawowych metod. Na przykład
jest doskonale poprawnym zapytaniem w Django ORM .
Chodzi o to, że masz jakiegoś konstruktora zapytań (w tym przypadku
Purchase.objects
), którego wewnętrzny status reprezentuje informacje o zapytaniu. Metody takie jakget
,filter
,exclude
,order_by
są ważne i powrócić nowy kreator zapytań ze zaktualizowaną statusu. Obiekty te implementują interfejs iterowalny, dzięki czemu po wykonaniu iteracji zapytanie jest wykonywane, a wyniki zapytania są konstruowane do tej pory. Chociaż ten przykład pochodzi z Django, zobaczysz tę samą strukturę w wielu innych ORM.źródło
Istnieje trzecie podejście.
Twój konkretny przykład wykazuje wykładniczy wzrost liczby potrzebnych metod wraz ze wzrostem liczby wymaganych funkcji: chcemy możliwości oferowania zaawansowanych zapytań, łącząc każdą funkcję zapytania ... jeśli to zrobimy przez dodanie metod, mamy jedną metodę dla podstawowe zapytanie, dwa jeśli dodamy jedną opcjonalną funkcję, cztery jeśli dodamy dwie, osiem jeśli dodamy trzy, 2 ^ n jeśli dodamy n funkcji.
Jest to oczywiście niemożliwe do utrzymania poza trzema lub czterema funkcjami, i jest nieprzyjemny zapach wielu blisko powiązanych kodów, które są prawie wklejone między metodami.
Można tego uniknąć, dodając obiekt danych do przechowywania parametrów i dysponując jedną metodą, która buduje zapytanie w oparciu o zestaw podanych parametrów (lub nie podanych). W takim przypadku dodanie nowej funkcji, takiej jak zakres dat, jest tak proste, jak dodanie obiektów ustawiających i pobierających zakres dat do obiektu danych, a następnie dodanie odrobiny kodu, w którym budowane jest sparametryzowane zapytanie:
... i gdzie parametry są dodawane do zapytania:
Takie podejście pozwala na liniowy wzrost kodu w miarę dodawania funkcji, bez konieczności zezwalania na dowolne, nieparametryzowane zapytania.
źródło
Myślę, że ogólną zgodą jest utrzymanie jak największego dostępu do danych w twoich modelach w MVC. Jednym z innych założeń projektowych jest przeniesienie niektórych bardziej ogólnych zapytań (tych, które nie są bezpośrednio związane z modelem) na wyższy, bardziej abstrakcyjny poziom, na którym można zezwolić na użycie go również w innych modelach. (W RoR mamy coś, co nazywa się frameworkiem). Jest jeszcze jedna rzecz, którą musisz wziąć pod uwagę i jest to możliwość utrzymania twojego kodu. W miarę rozwoju projektu, jeśli masz dostęp do danych w kontrolerach, będzie to coraz trudniejsze do wyśledzenia (obecnie mamy do czynienia z tym problemem w dużym projekcie) Modele, chociaż zaśmiecone metodami, zapewniają jeden punkt kontaktu dla każdego kontrolera, który może skończyć zapytania z tabel. (Może to również prowadzić do ponownego użycia kodu, co z kolei jest korzystne)
źródło
Interfejs warstwy usług może mieć wiele metod, ale wywołanie bazy danych może mieć tylko jedną.
Baza danych ma 4 główne operacje
Inną opcjonalną metodą może być wykonanie operacji na bazie danych, która nie wchodzi w zakres podstawowych operacji DB. Nazwijmy to Wykonaj.
Wstawianie i aktualizacje można łączyć w jedną operację o nazwie Zapisz.
Wiele twoich metod to kwerendy. Możesz więc stworzyć ogólny interfejs, który zaspokoi większość natychmiastowych potrzeb. Oto przykładowy ogólny interfejs:
Obiekt do przesyłania danych jest ogólny i zawiera wszystkie filtry, parametry, sortowanie itp. Warstwa danych byłaby odpowiedzialna za parsowanie i wyodrębnianie tego oraz konfigurowanie operacji w bazie danych za pomocą procedur składowanych, sparametryzowanych sql, linq itp. Tak więc SQL nie jest przekazywany między warstwami. Jest to zwykle czynność ORM, ale możesz wykonać własne i mieć własne mapowanie.
Więc w twoim przypadku masz Widżety. Widżety będą implementować interfejs IPOCO.
Tak więc w twoim modelu warstwy usług miałby
getList().
Musiałby warstwę mapowania obsłużyć tranforming
getList
doi wzajemnie. Jak inni wspominali, czasami dzieje się to za pośrednictwem ORM, ale ostatecznie kończy się to dużą ilością kodu typu „kocioł”, szczególnie jeśli masz setki tabel. ORM magicznie tworzy sparametryzowany SQL i uruchamia go w bazie danych. Jeśli tworzysz własne, dodatkowo w samej warstwie danych, maperzy będą potrzebni do skonfigurowania SP, linq itp. (Zasadniczo sql idzie do bazy danych).
Jak wspomniano wcześniej, DTO jest przedmiotem złożonym z kompozycji. Być może jednym z zawartych w nim obiektów jest obiekt o nazwie QueryParameters. Byłyby to wszystkie parametry zapytania, które byłyby ustawione i używane przez zapytanie. Innym obiektem byłaby lista zwróconych obiektów z zapytań, aktualizacji, ext. To jest ładowność. W takim przypadku ładunkiem byłaby lista widżetów.
Tak więc podstawową strategią jest:
W twoim przypadku myślę, że model może mieć wiele metod, ale optymalnie chcesz, aby wywołanie bazy danych było ogólne. Nadal kończy się wiele kodu mapowania typu kocioł (szczególnie z SP) lub magicznego kodu ORM, który dynamicznie tworzy dla Ciebie sparametryzowany SQL.
źródło