Должны ли запросы к базе данных абстрагироваться от самой страницы?


10

Когда я пишу генерацию страниц на PHP, я часто пишу набор файлов, заполненных запросами к базе данных. Например, у меня может быть запрос на получение некоторых данных о записи непосредственно из базы данных для отображения на странице, например:

$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

Эти быстрые одноразовые запросы обычно невелики, но иногда я получаю большие части кода взаимодействия с базой данных, который начинает выглядеть довольно грязно.

В некоторых случаях я решил эту проблему, создав простую библиотеку функций для обработки моих db-запросов, связанных с записями, сокращая этот блок кода до простого:

$content = post_get_content($id);

И это здорово. Или, по крайней мере, пока мне не нужно будет заняться чем-то другим. Может быть, мне нужно получить пять последних сообщений для отображения в списке. Ну, я всегда мог добавить еще одну функцию:

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

Но это заканчивается использованием SELECT *запроса, который мне обычно действительно не нужен, но часто слишком сложен, чтобы его можно было резюмировать. В конечном итоге я получаю либо обширную библиотеку функций взаимодействия с базой данных для каждого отдельного варианта использования, либо серию грязных запросов внутри кода каждой страницы. И даже когда я построю эти библиотеки, мне понадобится сделать одно крошечное объединение, которое я раньше не использовал, и мне вдруг понадобится написать еще одну узкоспециализированную функцию для выполнения этой работы.

Конечно, я мог бы использовать функции для общих случаев использования и запросов для конкретных взаимодействий, но как только я начинаю писать необработанные запросы, я начинаю возвращаться к прямому доступу для всего. Либо так, либо я стану ленивым и начну делать циклы PHP, которые действительно должны выполняться непосредственно в запросах MySQL.

Я хотел бы спросить тех, кто более опытен в написании интернет-приложений: стоит ли повышение удобства обслуживания дополнительными строками кода и возможными недостатками, которые могут вносить абстракции? Или просто использование прямых строк запроса является приемлемым методом для обработки взаимодействий с базой данных?


Может быть, вы можете использовать хранимые процедуры, чтобы «обернуть» грязную систему select- вам нужно будет вызывать такие процедуры только с некоторыми необходимыми параметрами
k102

Ответы:


7

Если у вас слишком много специализированных функций запросов, вы можете попытаться разбить их на составные биты. Например

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

Существует также иерархия уровней абстракции, которую вы, возможно, найдете полезным иметь в виду. У тебя есть

  1. MySQL API
  2. ваши функции mysql, такие как select ("select * from posts, где foo = bar"); или, может быть, более сложным, какselect("posts")->where("foo = bar")->first(5)
  3. функции, специфичные для вашей прикладной области, например posts()->joinWithComments()
  4. функции, специфичные для конкретной страницы, такие как commentsToBeReviewed($currentUser)

Чтобы упорядочить этот порядок абстракций, нужно много платить за простоту обслуживания. Скрипты страниц должны использовать только функции уровня 4, функции уровня 4 должны быть написаны в терминах функций уровня 3 и так далее. Это правда, что это занимает немного больше времени, но это поможет сохранить ваши расходы на обслуживание постоянными с течением времени (в отличие от «о, черт возьми, они хотят еще одно изменение !!!»)


2
+1 Этот синтаксис по сути создает ваш собственный ORM. Если вы действительно беспокоитесь о сложностях доступа к базе данных и не хотите тратить много времени на то, чтобы поработать с деталями, я бы предложил использовать зрелую веб-инфраструктуру (например, CodeIgniter ), которая уже поняла это. Или, по крайней мере, попытайтесь использовать его, чтобы увидеть, какой синтаксический сахар он вам дает, аналогично тому, что продемонстрировал xpmatteo.
Хартли Броуди

5

Разделение проблем - это принцип, о котором стоит прочитать, см. Статью в Википедии.

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

Еще один принцип, о котором стоит прочитать, это сцепление:

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

У вас есть две разные проблемы, одна из них - сбор данных из базы данных, а вторая - рендеринг этих данных. В действительно простых приложениях, вероятно, не о чем беспокоиться: вы тесно связали свой уровень доступа к базе данных и уровень управления с вашим уровнем рендеринга, но для небольших приложений это не составляет большого труда. Проблема в том, что веб-приложения имеют тенденцию развиваться, и если вы когда-нибудь захотите увеличить масштаб веб-приложения каким-либо образом, например, с точки зрения производительности или функциональности, вы столкнетесь с некоторыми проблемами.

Допустим, вы создаете веб-страницу пользовательских комментариев. Вместе с остроконечным боссом он попросит вас начать поддерживать Native Apps, например iPhone / Android и т. Д. Нам нужен вывод JSON, теперь вам нужно извлечь код рендеринга, который генерировал HTML. Когда вы сделали это, у вас теперь есть библиотека доступа к данным с двумя механизмами рендеринга, и все в порядке, вы масштабировали функционально. Возможно, вам даже удалось отделить все, т.е. бизнес-логику от рендеринга.

Приходит начальник и говорит, что у него есть клиент, который хочет отображать сообщения на своем веб-сайте, им нужен XML и им нужно около 5000 запросов в секунду с максимальной производительностью. Теперь вам нужно сгенерировать XML / JSON / HTML. Вы можете снова выделить ваш рендеринг, как и раньше. Однако теперь вам нужно добавить 100 серверов, чтобы комфортно получить необходимую производительность. Теперь ваша база данных загружается со 100 серверов, возможно, с десятками соединений на сервер, каждое из которых напрямую связано с тремя различными приложениями с различными требованиями и различными запросами и т. Д. Наличие доступа к базе данных на каждом внешнем компьютере представляет собой угрозу безопасности и растущий один, но я не пойду туда. Теперь вам нужно масштабировать для повышения производительности, у каждого приложения свои требования к кешированию, то есть разные проблемы. Вы можете попробовать и управлять этим в одном тесно связанном слое, т.е. уровне доступа к базе данных / бизнес-логики / рендеринга. Проблемы каждого уровня теперь начинают мешать друг другу, т. Е. Требования к кешированию данных из базы данных могут сильно отличаться от уровня рендеринга, логика, которую вы используете на бизнес-уровне, вероятно, будет вливаться в SQL, т. Е. Движение назад, или он может перетекать в слой рендеринга, это одна из самых больших проблем, с которыми я сталкивался, когда все в одном слое, это похоже на заливку железобетона в ваше приложение, и это не очень хорошо.

Существуют стандартные способы решения этих проблем, например, HTTP-кэширование веб-сервисов (squid / yts и т. Д.). Кэширование на уровне приложений внутри самих веб-сервисов с помощью что-то вроде memcached / redis. Вы также столкнетесь с проблемами, когда начнете масштабировать свою базу данных, например, несколько хостов для чтения и один мастер или разделенные данные между хостами. Вам не нужно, чтобы 100 хостов управляли различными подключениями к вашей базе данных, которые различаются в зависимости от запросов на запись или чтение или в изолированной базе данных, если пользователь «usera» хеширует «[table / database] foo» для всех запросов на запись.

Разделение забот - это ваш друг, выбирать, когда и где делать, это архитектурное решение и немного искусства. Избегайте тесного соединения всего, что будет развиваться по самым разным требованиям. Существует множество других причин для разделения вещей, то есть это упрощает тестирование, развертывание изменений, безопасность, повторное использование, гибкость и т. Д.


Я понимаю, откуда ты, и я не согласен с тем, что ты сказал, но сейчас это небольшая проблема. Рассматриваемый проект является личным, и большая часть моей проблемы с моей текущей моделью исходит из инстинкта моего программиста, чтобы избежать тесной связи, но я действительно новичок в комплексной разработке на стороне сервера, так что конец этого пошёл немного я ничего не понял. Тем не менее, +1 за то, что мне кажется хорошим советом, даже если я не буду полностью следовать этому проекту.
Алексис Кинг,

Если то, что вы делаете, останется маленьким, то я сделаю это максимально простым. Хороший принцип для подражания - это ЯГНИ .
Гарри

1

Я предполагаю, что когда вы говорите «сама страница», вы имеете в виду исходный файл PHP, который динамически генерирует HTML.

Не запрашивайте базу данных и не генерируйте HTML в одном и том же исходном файле.

Исходный файл, в котором вы запрашиваете базу данных, не является "страницей", даже если это исходный файл PHP.

В исходном файле PHP, где вы динамически создаете HTML, вы просто вызываете функции, которые определены в исходном файле PHP, к которому осуществляется доступ к базе данных.


0

Шаблон, который я использую для большинства проектов среднего масштаба, следующий:

  • Все SQL-запросы размещаются отдельно от серверного кода в отдельном месте.

    Работа с C # означает использование частичных классов, то есть помещение запросов в отдельный файл, учитывая, что эти запросы будут доступны из одного класса (см. Код ниже).

  • Эти SQL-запросы являются константами . Это важно, поскольку оно предотвращает искушение создавать запросы SQL на лету (что увеличивает риск внедрения SQL и в то же время усложняет последующий анализ запросов).

Текущий подход в C #

Пример:

// 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.
    }
}

Этот подход имеет то преимущество, что запросы находятся в отдельном файле. Это позволяет администратору базы данных просматривать, модифицировать / оптимизировать запросы и передавать измененный файл в систему контроля версий, не вступая в конфликт с коммитами, сделанными разработчиками.

Связанное преимущество заключается в том, что управление исходным кодом может быть настроено таким образом, чтобы ограничить доступ администратора баз данных только к тем файлам, которые содержат запросы, и запретить доступ к остальной части кода.

Можно ли это сделать в PHP?

В PHP отсутствуют как частичные, так и внутренние классы, поэтому он не может быть реализован в PHP.

Вы можете создать отдельный файл с отдельным статическим классом ( DemoQueries), содержащим константы, учитывая, что класс будет доступен везде. Более того, во избежание загрязнения глобальной области вы можете поместить все классы запросов в выделенное пространство имен. Это создаст довольно многословный синтаксис, но я сомневаюсь, что вы можете избежать многословия:

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.
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.