Czy zapytania bazy danych powinny być wyodrębniane z samej strony?

10

Pisząc generowanie stron w PHP, często piszę zestaw plików zaśmiecony zapytaniami do bazy danych. Na przykład mogę mieć zapytanie o pobranie niektórych danych dotyczących posta bezpośrednio z bazy danych w celu wyświetlenia na stronie, na przykład:

$statement = $db->prepare('SELECT * FROM posts WHERE id=:id');
$statement->bindValue(':id', $id, PDO::PARAM_INT);
$statement->execute();
$post = $statement->fetch(PDO::FETCH_ASSOC);
$content = $post['content']
// do something with the content

Te szybkie, jednorazowe zapytania są zwykle małe, ale czasami kończę na dużych fragmentach kodu interakcji z bazą danych, które zaczynają wyglądać dość niechlujnie.

W niektórych przypadkach rozwiązałem ten problem, tworząc prostą bibliotekę funkcji do obsługi moich pokrewnych zapytań db, skracając ten blok kodu do prostej:

$content = post_get_content($id);

I to świetnie. A przynajmniej dopóki nie będę musiał zrobić czegoś innego. Może muszę pobrać pięć najnowszych postów, aby wyświetlić je na liście. Cóż, zawsze mogę dodać inną funkcję:

$recent_posts = post_get_recent(5);
foreach ($recent_posts as $post) { ... }

Ale to kończy się na SELECT *zapytaniu, którego zwykle tak naprawdę nie potrzebuję, ale często jest zbyt skomplikowane, aby można je było dość abstrakcyjnie. W końcu mam albo ogromną bibliotekę funkcji interakcji z bazą danych dla każdego pojedynczego przypadku użycia, albo szereg niechlujnych zapytań w kodzie każdej strony. I nawet gdy zbuduję te biblioteki, będę musiał wykonać jedno małe połączenie, którego wcześniej nie używałem, i nagle muszę napisać kolejną wysoce wyspecjalizowaną funkcję do wykonania tej pracy.

Jasne, mógłbym użyć funkcji do ogólnych przypadków użycia i zapytań do konkretnych interakcji, ale jak tylko zacznę pisać surowe zapytania, zacznę wracać do bezpośredniego dostępu do wszystkiego. Albo to, albo się lenię i zacznę robić rzeczy w pętlach PHP, które tak naprawdę powinny być zrobione bezpośrednio w zapytaniach MySQL.

Chciałbym zapytać osoby bardziej doświadczone w pisaniu aplikacji internetowych: czy zwiększenie łatwości konserwacji jest warte dodatkowych linii kodu i możliwych nieefektywności, które mogą wprowadzić abstrakcje? Czy po prostu użycie ciągów bezpośrednich zapytań jest akceptowalną metodą do obsługi interakcji z bazą danych?

Alexis King
źródło
Być może możesz użyć procedur przechowywanych do „zawinięcia” select
bałaganu

Odpowiedzi:

7

Gdy masz zbyt wiele wyspecjalizowanych funkcji zapytań, możesz spróbować podzielić je na części składowe. Na przykład

$posts = posts()->joinWithComments()->orderBy("post.post_date")->first(5);

Istnieje również hierarchia poziomów abstrakcji, o których warto pamiętać. Ty masz

  1. API mysql
  2. twoje funkcje mysql, takie jak select („select * from posts where foo = bar”); a może bardziej kompozycyjny jakselect("posts")->where("foo = bar")->first(5)
  3. na przykład funkcje specyficzne dla domeny aplikacji posts()->joinWithComments()
  4. funkcje specyficzne dla konkretnej strony, takie jak commentsToBeReviewed($currentUser)

Przestrzeganie tej kolejności abstrakcji jest bardzo opłacalne pod względem łatwości konserwacji. Skrypty stron powinny używać tylko funkcji poziomu 4, funkcje poziomu 4 powinny być pisane w kategoriach funkcji poziomu 3 i tak dalej. To prawda, że ​​zajmuje to trochę więcej czasu z góry, ale pomoże utrzymać stałe koszty utrzymania w czasie (w przeciwieństwie do „o kurcze, oni chcą kolejnej zmiany !!!”)

xpmatteo
źródło
2
+1 Ta składnia zasadniczo tworzy własną ORM. Jeśli naprawdę martwisz się, że dostęp do bazy danych się komplikuje i nie chcesz spędzać dużo czasu na majstrowaniu przy szczegółach, sugerowałbym użycie dojrzałego frameworka internetowego (np. CodeIgniter ), który już to wymyślił. Lub przynajmniej spróbuj go użyć, aby zobaczyć, jaki rodzaj cukru syntaktycznego daje ci, podobnie jak wykazał xpmatteo.
Hartley Brody,
5

Rozdzielenie obaw jest zasadą, o której warto przeczytać, zobacz artykuł na ten temat w Wikipedii.

http://en.wikipedia.org/wiki/Separation_of_concerns

Kolejną zasadą, o której warto przeczytać, jest Sprzęganie:

http://en.wikipedia.org/wiki/Coupling_(computer_science )

Masz dwie wyraźne obawy, jedną z nich jest zbieranie danych z bazy danych, a drugą renderowanie tych danych. W naprawdę prostych aplikacjach prawdopodobnie nie ma się czym martwić, ściśle powiązałeś warstwę dostępu do bazy danych i warstwę zarządzania z warstwą renderującą, ale w przypadku małych aplikacji nie jest to wielka sprawa. Problem polega na tym, że aplikacje internetowe mają tendencję do ewolucji, a jeśli kiedykolwiek chcesz skalować aplikację internetową w jakikolwiek sposób, tj. Wydajność lub funkcjonalność, napotkasz pewne problemy.

Powiedzmy, że generujesz stronę internetową z komentarzami generowanymi przez użytkowników. Nadchodzi spiczasty szef i prosi o rozpoczęcie obsługi aplikacji natywnych, np. IPhone / Android itp. Potrzebujemy trochę danych wyjściowych JSON, teraz musisz wyodrębnić kod renderujący, który generował HTML. Po wykonaniu tej czynności masz bibliotekę dostępu do danych z dwoma silnikami renderującymi i wszystko jest w porządku, skalowałeś się funkcjonalnie. Być może udało ci się oddzielić wszystko, tzn. Logikę biznesową od renderowania.

Wraz z nim pojawia się szef i mówi, że ma klienta, który chce wyświetlać posty na swojej stronie, potrzebuje XML i potrzebuje około 5000 żądań na sekundę szczytowej wydajności. Teraz musisz wygenerować XML / JSON / HTML. Możesz ponownie rozdzielić renderowanie, tak samo jak poprzednio. Jednak teraz musisz dodać 100 serwerów, aby wygodnie uzyskać wymaganą wydajność. Teraz twoja baza danych jest atakowana ze 100 serwerów z prawdopodobnie dziesiątkami połączeń na serwer, z których każdy jest bezpośrednio narażony na trzy różne aplikacje o różnych wymaganiach i różnych zapytaniach itp. Dostęp do bazy danych na każdej maszynie frontendowej stanowi zagrożenie bezpieczeństwa i rośnie jeden, ale nie pojadę tam. Teraz musisz skalować pod kątem wydajności, każda aplikacja ma inne wymagania dotyczące buforowania, tj. Różne problemy. Możesz spróbować i zarządzać tym w jednej ściśle powiązanej warstwie, tj. W warstwie dostępu do bazy danych / logiki biznesowej / renderingu. Obawy każdej warstwy zaczynają się od siebie wzajemnie przeszkadzać, tzn. Wymagania buforowania danych z bazy danych mogą być bardzo różne niż warstwa renderująca, logika, którą masz w warstwie biznesowej, prawdopodobnie spadnie na SQL tzn. Przesuwa się do tyłu lub może krwawić do warstwy renderującej, jest to jeden z największych problemów, jakie widziałem mając wszystko w jednej warstwie, to jak wlewanie zbrojonego betonu do twojej aplikacji i nie w dobry sposób.

Istnieją standardowe sposoby podejścia do tego typu problemów, np. Buforowanie HTTP serwisów internetowych (squid / yts itp.). Buforowanie na poziomie aplikacji w samych usługach internetowych z czymś takim jak memcached / redis. Będziesz także mieć problemy, gdy zaczniesz skalować bazę danych, tj. Wiele hostów odczytu i jednego wzorca lub podzielone dane między hostami. Nie chcesz, aby 100 hostów zarządzało różnymi połączeniami z bazą danych, które różnią się w zależności od żądań zapisu lub odczytu lub w podzielonej bazie danych, jeśli użytkownik „usera” zamienia się w „[tabela / baza danych] foo” dla wszystkich żądań zapisu.

Rozdzielenie trosk jest twoim przyjacielem, wybór kiedy i gdzie to zrobić, jest decyzją architektoniczną i odrobiną sztuki. Unikaj ciasnego łączenia czegokolwiek, co ewoluuje, aby mieć bardzo różne wymagania. Istnieje wiele innych powodów oddzielania rzeczy, tj. Upraszcza testowanie, wdrażanie zmian, bezpieczeństwo, ponowne użycie, elastyczność itp.

Złupić
źródło
Rozumiem, skąd pochodzisz i nie zgadzam się z tym, co powiedziałeś, ale teraz jest to niewielki problem. Projekt, o którym mowa, jest osobisty i większość mojego problemu z moim obecnym modelem wynika z instynktu mojego programisty, aby uniknąć ścisłego powiązania, ale tak naprawdę jestem nowicjuszem w złożonym rozwoju po stronie serwera, więc koniec tego poszedł trochę nad moją głową. Mimo to +1 za coś, co wydaje mi się dobrą radą, nawet jeśli nie będę stosować się do tego całkowicie w tym projekcie.
Alexis King
Jeśli to, co robisz, pozostanie małe, postaram się, aby było to tak proste, jak to możliwe. Dobrą zasadą do przestrzegania tutaj jest YAGNI .
Harry
1

Zakładam, że mówiąc „sama strona”, masz na myśli plik źródłowy PHP, który dynamicznie generuje HTML.

Nie przeszukuj bazy danych i nie generuj HTML w tym samym pliku źródłowym.

Plik źródłowy, w którym przeszukujesz bazę danych, nie jest „stroną”, nawet jeśli jest to plik źródłowy PHP.

W pliku źródłowym PHP, w którym dynamicznie tworzysz HTML, po prostu wywołujesz funkcje zdefiniowane w pliku źródłowym PHP, do którego uzyskuje się dostęp do bazy danych.

Tulains Córdova
źródło
0

Wzór, którego używam w większości projektów na średnią skalę, jest następujący:

  • Wszystkie zapytania SQL są oddzielane od kodu po stronie serwera, w osobnym miejscu.

    Praca z C # oznacza użycie klas częściowych, tj. Umieszczenie zapytań w osobnym pliku, biorąc pod uwagę, że zapytania te będą dostępne z jednej klasy (patrz kod poniżej).

  • Te zapytania SQL są stałymi . Jest to ważne, ponieważ zapobiega pokusie budowania zapytań SQL w locie (zwiększając w ten sposób ryzyko wstrzyknięcia SQL, a jednocześnie utrudniając późniejsze przeglądanie zapytań).

Obecne podejście w C #

Przykład:

// Demo.cs
public partial class Demo : DataRepository
{
    public IEnumerable<Stuff> LoadStuff(int categoryId)
    {
        return this
            .Query(Queries.LoadStuff)
            .With(new { CategoryId = categoryId })
            .ReadRows<Stuff>();
    }

    // Other methods go here.
}

public partial class Demo
{
    private static class Queries
    {
        public const string LoadStuff = @"
select top 100 [StuffId], [SomeText]
    from [Schema].[Table]
    where [CategoryId] = @CategoryId
    order by [CreationUtcTime]";

        // Other queries go here.
    }
}

Zaletą tego podejścia jest to, że zapytania znajdują się w osobnym pliku. Dzięki temu DBA może przeglądać i modyfikować / optymalizować zapytania i zatwierdzać zmodyfikowany plik do kontroli źródła bez konfliktu z zatwierdzeniami dokonanymi przez programistów.

Pokrewną korzyścią jest to, że kontrola źródła może być skonfigurowana w taki sposób, aby ograniczyć dostęp DBA do tylko tych plików, które zawierają zapytania, i odmówić dostępu do reszty kodu.

Czy można to zrobić w PHP?

PHP nie ma zarówno klas cząstkowych, jak i wewnętrznych, dlatego nie można go zaimplementować w PHP.

Możesz utworzyć osobny plik z oddzielną klasą statyczną ( DemoQueries) zawierającą stałe, biorąc pod uwagę, że klasa będzie dostępna z dowolnego miejsca. Ponadto, aby uniknąć zanieczyszczenia globalnego zakresu, możesz umieścić wszystkie klasy zapytań w dedykowanej przestrzeni nazw. Spowoduje to utworzenie dość szczegółowej składni, ale wątpię, czy można uniknąć gadatliwości:

namespace Data {
    public class Demo inherit DataRepository {
        public function LoadStuff($categoryId) {
            $query = \Queries\Demo::$LoadStuff;
            // Do the stuff with the query.
        }

        // Other methods go here.
    }
}

namespace Queries {
    public static class Demo {
        public const $LoadStuff = '...';

        // Other queries go here.
    }
}
Arseni Mourzenko
źródło