As consultas do banco de dados devem ser abstraídas da própria página?

10

Ao escrever a geração de páginas em PHP, muitas vezes me pego escrevendo um conjunto de arquivos repletos de consultas ao banco de dados. Por exemplo, eu posso ter uma consulta para buscar alguns dados sobre uma postagem diretamente do banco de dados para exibir em uma página, como esta:

$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

Essas consultas rápidas e únicas geralmente são pequenas, mas às vezes acabo com grandes partes do código de interação do banco de dados, que começa a parecer bastante confuso.

Em alguns casos, resolvi esse problema criando uma biblioteca simples de funções para lidar com minhas consultas db pós-relacionadas, encurtando esse bloco de código para um simples:

$content = post_get_content($id);

E isso é ótimo. Ou pelo menos é até eu precisar fazer outra coisa. Talvez eu precise exibir as cinco postagens mais recentes em uma lista. Bem, eu sempre poderia adicionar outra função:

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

Mas isso acaba usando uma SELECT *consulta, da qual eu realmente não preciso, mas muitas vezes é muito complicado para ser razoavelmente abstrato. Acabo tendo uma biblioteca enorme de funções de interação com o banco de dados para cada caso de uso ou uma série de consultas confusas no código de cada página. E mesmo depois de criar essas bibliotecas, vou precisar fazer uma pequena junção que não havia usado antes e, de repente, preciso escrever outra função altamente especializada para fazer o trabalho.

Claro, eu poderia usar as funções para casos de uso gerais e consultas para interações específicas, mas assim que começo a escrever consultas brutas, começo a voltar ao acesso direto a tudo. Ou isso, ou ficarei preguiçoso e começarei a fazer coisas em loops PHP que realmente devem ser feitas diretamente nas consultas do MySQL.

Gostaria de perguntar aos mais experientes na criação de aplicativos da Internet: o aumento da capacidade de manutenção vale as linhas extras de código e as possíveis ineficiências que as abstrações podem apresentar? Ou simplesmente o uso de strings de consulta direta é um método aceitável para lidar com interações com o banco de dados?

Alexis King
fonte
Talvez você possa usar procedimentos para "envoltório" confuso armazenados selects - si só vai ter que chamar esses procedimentos com alguns parâmetros que você precisa
K102

Respostas:

7

Quando você tem muitas funções de consulta especializadas, pode tentar dividi-las em bits composíveis. Por exemplo

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

Há também uma hierarquia de níveis de abstração que você pode achar útil ter em mente. Você tem

  1. API mysql
  2. suas funções mysql, como select ("select * from posts where foo = bar"); ou talvez mais compostável comoselect("posts")->where("foo = bar")->first(5)
  3. funções específicas do seu domínio de aplicativo, por exemplo posts()->joinWithComments()
  4. funções específicas de uma página específica, como commentsToBeReviewed($currentUser)

Vale muito em termos de facilidade de manutenção respeitar esta ordem de abstrações. Os scripts de páginas devem usar apenas funções de nível 4, funções de nível 4 devem ser escritas em termos de funções de nível 3 e assim por diante. É verdade que isso leva um pouco mais de tempo, mas ajudará a manter os custos de manutenção constantes ao longo do tempo (em oposição a "oh meu Deus, eles querem outra mudança !!!")

xpmatteo
fonte
2
+1 Essa sintaxe cria essencialmente seu próprio ORM. Se você está realmente preocupado com o fato de o acesso ao banco de dados ficar complexo, e não quer gastar muito tempo mexendo nos detalhes, sugiro usar uma estrutura da web madura (por exemplo, CodeIgniter ) que já tenha resolvido esse problema. Ou pelo menos tente usá-lo para ver que tipo de açúcar sintático ele fornece, semelhante ao que o xpmatteo demonstrou.
Hartley Brody
5

Separação de preocupações é um princípio sobre o qual vale a pena ler, consulte o artigo da Wikipedia.

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

Outro princípio que vale a pena ler é o Coupling:

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

Você tem duas preocupações distintas, uma é a organização dos dados do banco de dados e a segunda é a renderização desses dados. Em aplicativos realmente simples, provavelmente há pouco com o que se preocupar, você acoplou fortemente sua camada de acesso e gerenciamento de banco de dados à sua camada de renderização, mas para aplicativos pequenos isso não é grande coisa. O problema é que os aplicativos Web tendem a evoluir e, se você quiser escalar um aplicativo Web, seja de que forma for o desempenho ou a funcionalidade, terá alguns problemas.

Digamos que você esteja gerando uma página da Web de comentários gerados pelo usuário. Aí vem o chefe de cabelos pontudos e pede para você começar a oferecer suporte a aplicativos nativos, como iPhone / Android etc. Precisamos de alguma saída JSON, agora você precisa extrair o código de renderização que estava gerando HTML. Quando você fez isso, agora possui uma biblioteca de acesso a dados com dois mecanismos de renderização e está tudo bem, você foi dimensionado funcionalmente. Você pode até ter conseguido manter tudo separado, ou seja, a lógica de negócios da renderização.

Junto vem o chefe e informa que ele tem um cliente que deseja exibir as postagens no site deles, eles precisam de XML e precisam de cerca de 5000 solicitações por segundo em desempenho máximo. Agora você precisa gerar XML / JSON / HTML. Você pode separar sua renderização novamente, como antes. No entanto, agora você precisa adicionar 100 servidores para obter confortavelmente o desempenho necessário. Agora, seu banco de dados está sendo atingido em 100 servidores com possivelmente dezenas de conexões por servidor, cada um dos quais diretamente exposto a três aplicativos diferentes, com requisitos e consultas diferentes, etc. Ter o acesso ao banco de dados em cada máquina front-end é um risco à segurança e um crescente um, mas eu não vou lá. Agora você precisa escalar para obter desempenho, cada aplicativo tem requisitos de armazenamento em cache diferentes, ou seja, preocupações diferentes. Você pode tentar gerenciar isso em uma camada fortemente acoplada, ou seja, na camada de acesso ao banco de dados / lógica de negócios / renderização. As preocupações de cada camada estão começando a atrapalhar uma a outra, ou seja, os requisitos de armazenamento em cache dos dados do banco de dados podem ser muito diferentes da camada de renderização, a lógica que você possui na camada de negócios provavelmente sangrará no SQL, ou seja, retroceder ou pode sangrar para a camada de renderização, este é um dos maiores problemas que já vi ao ter tudo em uma camada, é como derramar concreto reforçado no seu aplicativo e não no bom sentido.

Existem maneiras padrão de abordar esses tipos de problemas, ou seja, o cache HTTP dos serviços da web (squid / yts etc). O armazenamento em cache no nível de aplicativo nos próprios serviços da Web com algo como memcached / redis. Você também terá problemas ao começar a expandir seu banco de dados, ou seja, vários hosts de leitura e um mestre ou dados fragmentados nos hosts. Você não deseja 100 hosts gerenciando várias conexões com seu banco de dados que diferem com base em solicitações de gravação ou leitura ou em um banco de dados fragmentado se um usuário "usera" fizer o hash em "[table / database] foo" para todas as solicitações de gravação.

A separação de preocupações é sua amiga, escolher quando e onde fazer é uma decisão arquitetônica e um pouco de arte. Evite o acoplamento apertado de qualquer coisa que evolua para ter requisitos muito diferentes. Existem várias outras razões para manter as coisas separadas, isto é, simplifica testes, implantação de alterações, segurança, reutilização, flexibilidade etc.

atormentar
fonte
Entendo de onde você vem e não discordo de nada do que você disse, mas essa é uma pequena preocupação no momento. O projeto em questão é pessoal e a maior parte do meu problema com o modelo atual vem do instinto do programador para evitar um acoplamento rígido, mas eu sou realmente um novato no desenvolvimento complexo do servidor, então o final disso foi um pouco sobre minha cabeça. Ainda assim, +1 para o que me parece um bom conselho, mesmo que eu não o siga inteiramente para este projeto.
Alexis King
Se o que você está fazendo permanecer pequeno, eu o manteria o mais simples possível. Um bom princípio a seguir aqui é YAGNI .
Harry
1

Presumo que, quando você diz "a própria página", você quer dizer o arquivo de origem PHP que gera dinamicamente HTML.

Não consulte o banco de dados e gere HTML no mesmo arquivo de origem.

O arquivo de origem no qual você consulta o banco de dados não é uma "página", mesmo que seja um arquivo de origem PHP.

No arquivo de origem PHP em que você cria dinamicamente o HTML, basta fazer chamadas para as funções definidas no arquivo de origem PHP em que o banco de dados é acessado.

Tulains Córdova
fonte
0

O padrão usado para a maioria dos projetos de média escala é o seguinte:

  • Todas as consultas SQL são separadas do código do servidor, em um local separado.

    Trabalhando com C #, significa usar classes parciais, ou seja, colocar as consultas em um arquivo separado, uma vez que essas consultas estarão acessíveis a partir de uma única classe (veja o código abaixo).

  • Essas consultas SQL são constantes . Isso é importante, pois evita a tentação de criar consultas SQL dinamicamente (aumentando assim o risco de injeção de SQL e, ao mesmo tempo, dificultando a revisão posterior das consultas).

Abordagem atual em C #

Exemplo:

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

Essa abordagem tem o benefício de ter as consultas em um arquivo separado. Isso permite que um DBA revise e modifique / otimize as consultas e submeta o arquivo modificado ao controle de origem sem entrar em conflito com as confirmações feitas pelos desenvolvedores.

Um benefício relacionado é que o controle de origem pode ser configurado de forma a limitar o acesso do DBA apenas aos arquivos que contêm as consultas e negar o acesso ao restante do código.

É possível fazer em PHP?

O PHP não possui classes parciais e classes internas; portanto, não pode ser implementado no PHP.

Você pode criar um arquivo separado com uma classe estática separada ( DemoQueries) contendo as constantes, pois a classe estará acessível de qualquer lugar. Além disso, para evitar poluir o escopo global, você pode colocar todas as classes de consulta em um espaço para nome dedicado. Isso criará uma sintaxe bastante detalhada, mas duvido que você possa evitar a verbosidade:

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
fonte