Extensão — Além do SQLite: PostgreSQL, MySQL e MongoDB

Extensão — Além do SQLite: PostgreSQL, MySQL e MongoDB

Trocar o SQLite por PostgreSQL, MySQL ou MongoDB muda a API e quase nada do resto: a classe de conexão continua abrindo no construtor e fechando no destrutor. O panorama trata da distinção entre embutido e servidor, do modelo relacional frente ao de documentos, e de como escolher por requisito.
Linguagem C++

13 min de leitura

No artigo Extensão — Páginas Dinâmicas: HTML Gerado com Templates unimos banco, servidor e templates numa aplicação web completa, usando o SQLite como nossa persistência. O SQLite foi a escolha ideal para aprender — embutido, simples, num arquivo. Mas o Extensão mapeou outros bancos que dominam o mundo real: os servidores de banco de dados, como PostgreSQL, MySQL e MongoDB. Hoje damos o panorama deles. Não vou repetir código de integração para cada um — seria redundante, porque a lição mais importante é justamente esta: os princípios do curso se aplicam a todos eles de forma uniforme. RAII envolve suas conexões, exceções ou expected tratam suas falhas, a STL organiza seus resultados. Conhecer as diferenças entre eles (para escolher bem) e a semelhança de como o C++ os aborda (RAII em toda parte) é o que completa seu mapa da persistência. Este é um artigo de panorama e critério, não de mais um exemplo repetido.

Embutido versus servidor: a distinção fundamental

A diferença mais importante entre o SQLite e os outros três é arquitetural, e já a tocamos no Extensão. O SQLite é embutido: roda dentro do seu processo, guarda tudo num arquivo local, sem servidor separado. Os outros — PostgreSQL, MySQL, MongoDB — são servidores: processos (frequentemente em máquinas dedicadas) aos quais sua aplicação se conecta pela rede. Essa diferença tem consequências profundas. Um banco servidor atende múltiplos clientes simultâneos, gerencia usuários e permissões, escala para volumes enormes de dados e requisições, e permite que muitas aplicações compartilhem os mesmos dados. Em troca, exige instalação, configuração, administração e uma conexão de rede. Regra prática: use embutido (SQLite) para dados locais de uma única aplicação; use servidor quando múltiplas aplicações ou usuários precisam compartilhar dados, ou quando o volume exige uma infraestrutura dedicada. A escolha é arquitetural, não de preferência.

Os três servidores e quando escolher cada um

Cada banco servidor tem um perfil, e escolher bem depende do seu problema. Vale o mapa com critério honesto:

O PostgreSQL é um banco relacional (dados em tabelas com relações, consultados com SQL) conhecido por robustez, conformidade rigorosa com padrões, e um conjunto riquíssimo de recursos avançados (tipos customizados, consultas sofisticadas, extensibilidade). É frequentemente a escolha padrão para aplicações sérias que precisam de um banco relacional confiável. Em C++, acessa-se tipicamente via a biblioteca libpqxx. Escolha-o quando quer um banco relacional completo, correto e poderoso, e a robustez importa mais que a simplicidade.

O MySQL (e seu fork MariaDB) é outro banco relacional servidor, historicamente popular por ser rápido, simples de começar, e onipresente em hospedagem web. É uma escolha sólida e madura, com vasta comunidade. Em C++, usa-se o MySQL Connector/C++. Escolha-o quando quer um relacional bem-estabelecido, especialmente em contextos onde ele já é o padrão (muitas stacks web tradicionais), ou por familiaridade da equipe.

O MongoDB é fundamentalmente diferente: é um banco de documentos (NoSQL), que guarda dados como documentos flexíveis semelhantes a JSON, não em tabelas rígidas. Não usa SQL, e não força um esquema fixo — cada documento pode ter estrutura diferente. Em C++, acessa-se via o driver mongocxx. Escolha-o quando seus dados são naturalmente hierárquicos ou variáveis (não se encaixam bem em tabelas), quando você precisa de flexibilidade de esquema, ou para certos padrões de escala horizontal. A honestidade: a escolha entre relacional (Postgre/MySQL) e documentos (Mongo) é uma decisão de arquitetura de dados com trade-offs reais — relacionais brilham com dados estruturados e relações complexas e garantias de consistência fortes; documentos brilham com flexibilidade e dados aninhados. Não há vencedor universal; há o certo para o seu modelo de dados.

A semelhança que importa: RAII em toda parte

Agora a lição central, e a razão de eu não repetir código para cada um. Apesar de suas diferenças, todos esses bancos apresentam, do ponto de vista do C++, o mesmo padrão de recurso — e você já sabe domá-lo. Cada um tem uma conexão que se abre e deve fechar-se: isso é RAII (Fase 2), envolvido numa classe que abre no construtor e fecha no destrutor, exatamente como fizemos com o BancoSQLite no Extensão. Cada um pode falhar (conexão recusada, consulta inválida, servidor fora do ar): isso é tratamento de erros (Fase 6), com exceções ou expected transformando falhas em algo que seu código trata com clareza. Cada um devolve resultados — linhas ou documentos: isso é a STL (Fase 4), com os resultados armazenados em vector, map ou tipos apropriados. Conceitualmente, uma classe de conexão para qualquer um deles tem a mesma forma:

// O PADRÃO é o mesmo para todos os bancos — só muda a API interna.
class ConexaoBanco {
public:
    ConexaoBanco(const std::string& parametros) {
        // Abre a conexão usando a API específica (libpqxx, mysqlcppconn, mongocxx...).
        // Lança se falhar. — Fase 6
        conectar(parametros);
    }
    ~ConexaoBanco() {
        // Fecha a conexão automaticamente. — Fase 2 (RAII)
        desconectar();
    }
    ConexaoBanco(const ConexaoBanco&) = delete;   // dono único — Fase 2/8

    // Executa e devolve resultados numa estrutura da STL. — Fase 4
    std::vector<Registro> consultar(const std::string& query);
private:
    /* handle da conexão específico do banco */
};

O interior de conectar, desconectar e consultar difere entre PostgreSQL, MySQL e MongoDB — cada um usa sua biblioteca. Mas a estrutura — RAII envolvendo a conexão, exceções para falhas, STL para resultados — é idêntica. É por isso que dominar os princípios do curso vale mais que decorar a API de cada banco: aprenda o padrão uma vez, aplique-o a qualquer banco. Quando você precisar do PostgreSQL amanhã, não estará aprendendo do zero — estará aplicando o RAII que já domina a uma nova API. Os conceitos transcendem as bibliotecas específicas.

A honestidade sobre este panorama e a integração

Prometo a franqueza final sobre persistência. Não mostrei código completo de integração para cada banco por duas razões honestas. Primeira, seria repetitivo: como acabei de mostrar, o padrão RAII é o mesmo, e ver três versões quase idênticas ensinaria pouco além do que o SQLite já ensinou. Segunda, e importante, integrar esses bancos servidor é significativamente mais trabalhoso que o SQLite — envolve instalar e rodar o servidor, configurar credenciais e rede, e integrar bibliotecas mais pesadas via CMake (libpqxx, mysqlcppconn, mongocxx têm suas próprias dependências). Esse trabalho de infraestrutura, embora real e importante, é específico de cada biblioteca e ambiente, muda com o tempo, e distrairia dos conceitos — é o tipo de coisa que se resolve consultando a documentação atual de cada driver quando você for usá-lo. O que fica desta aula, e que é o durável, é o critério (qual banco para qual problema) e o padrão (RAII, erros, STL aplicados uniformemente). Quando você integrar um desses na prática, seguirá a documentação do driver para os detalhes de API, mas a estrutura do seu código C++ já a conhece. É a diferença entre saber a receita e saber cozinhar.

Os bancos mudam de API, não de forma: abrir no construtor, fechar no destrutor, proibir cópia, permitir movimento — a classe de conexão do PostgreSQL e a do MongoDB têm o mesmo esqueleto, ainda que nenhuma linha coincida. A escolha entre eles é de requisito: consistência forte e relações complexas puxam para o relacional, campos que variam de registro para registro puxam para documentos, e rodar sem servidor puxa para o embutido.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Explique, com suas palavras, a diferença arquitetural entre um banco embutido (SQLite) e um banco servidor (PostgreSQL). Para um aplicativo de celular que guarda notas localmente, qual escolher e por quê?

Ver resposta

✓ Resposta: Um banco embutido como o SQLite roda dentro do processo da aplicação e guarda os dados num arquivo local, sem servidor separado nem rede — a aplicação e o banco são uma coisa só. Um banco servidor como o PostgreSQL roda como um processo separado (possivelmente em outra máquina) ao qual a aplicação se conecta pela rede, atendendo múltiplos clientes simultâneos com gestão de usuários e recursos. Para um aplicativo de celular que guarda notas localmente, o SQLite (embutido) é a escolha clara: as notas são dados de um único usuário, no próprio dispositivo, sem necessidade de compartilhamento entre aplicações ou usuários; o SQLite não exige rodar nem administrar um servidor (impraticável num celular), o banco é só um arquivo no dispositivo (fácil de fazer backup e sincronizar), e sua leveza casa com os recursos limitados de um telefone. Um banco servidor seria um exagero absurdo para dados locais de um app — de fato, é por isso que praticamente todos os apps de celular usam SQLite internamente.

Exercício 2

Distinga um banco relacional (PostgreSQL, MySQL) de um banco de documentos (MongoDB). Dê um exemplo de dados que se encaixam bem no modelo relacional e outro que se encaixa melhor no modelo de documentos.

Ver resposta

✓ Resposta: Um banco relacional organiza dados em tabelas com colunas fixas e relações entre elas, consultadas com SQL, e impõe um esquema rígido (toda linha de uma tabela tem as mesmas colunas). Um banco de documentos guarda dados como documentos flexíveis (semelhantes a JSON), sem esquema fixo — cada documento pode ter estrutura diferente. Exemplo que se encaixa bem no relacional: um sistema bancário com contas, transações e clientes — os dados são altamente estruturados, com relações claras (uma transação liga duas contas, uma conta pertence a um cliente), e as garantias de consistência do modelo relacional são essenciais. Exemplo que se encaixa melhor em documentos: um catálogo de produtos de e-commerce onde produtos de categorias diferentes têm atributos totalmente diferentes (um livro tem autor e ISBN; uma camiseta tem tamanho e cor; um eletrônico tem voltagem e garantia) — forçar isso em tabelas relacionais com colunas fixas seria desajeitado (muitas colunas nulas), enquanto um documento por produto, cada um com seus próprios campos, modela isso naturalmente.

Exercício 3

A aula afirma que "o padrão RAII é o mesmo para todos os bancos". Explique concretamente como uma classe de conexão para PostgreSQL e uma para MongoDB, apesar de usarem bibliotecas totalmente diferentes, compartilhariam a mesma estrutura RAII.

Ver resposta

✓ Resposta: Ambas as classes teriam a mesma estrutura apesar de usarem bibliotecas diferentes: um construtor que adquire a conexão (para PostgreSQL, criando um objeto de conexão da libpqxx; para MongoDB, criando um cliente mongocxx) e lança se falhar; um destrutor que libera a conexão (fechando-a pela API respectiva); a cópia proibida (posse única do recurso de conexão); e métodos que executam operações e devolvem resultados em estruturas da STL. O que difere é apenas o conteúdo dessas funções — as chamadas específicas de cada biblioteca dentro do construtor, destrutor e métodos. Mas o esqueleto — RAII amarrando a aquisição ao construtor e a liberação ao destrutor, garantindo que a conexão nunca vaze — é idêntico, porque ambos enfrentam o mesmo problema fundamental (um recurso que se abre e deve fechar), e o RAII é a solução do C++ para esse problema, independente de qual banco. Você escreve a mesma forma de classe, preenchendo-a com a API de cada driver.

Exercício 4

Você está projetando um sistema que precisa: (a) guardar transações financeiras com garantias fortes de consistência e relações complexas entre contas; (b) guardar perfis de usuário com campos que variam muito entre usuários. Que tipo de banco escolheria para cada caso e por quê?

Ver resposta

✓ Resposta: Para (a) transações financeiras com consistência forte e relações complexas: um banco relacional como PostgreSQL — dados financeiros exigem garantias de consistência rigorosas (uma transferência não pode debitar sem creditar; o dinheiro não pode se perder), que os bancos relacionais oferecem através de transações ACID robustas, e as relações complexas entre contas, transações e clientes são exatamente o que o modelo relacional (com suas junções e integridade referencial) modela melhor. O PostgreSQL, em particular, é conhecido pela conformidade rigorosa que dados financeiros demandam. Para (b) perfis de usuário com campos muito variáveis: um banco de documentos como MongoDB — se os perfis têm campos que variam bastante entre usuários (uns têm redes sociais, outros preferências específicas, outros dados profissionais), o esquema flexível dos documentos acomoda essa variação naturalmente, sem a rigidez de colunas fixas que forçaria muitos campos nulos ou tabelas auxiliares complexas num modelo relacional. Cada escolha segue o modelo dos dados: estruturado com relações e consistência crítica → relacional; flexível e variável → documentos.

Exercício 5

Discuta por que dominar os princípios do curso (RAII, tratamento de erros, STL) é mais valioso que decorar a API de um banco específico. Como isso se aplica quando você precisa migrar uma aplicação de um banco para outro?

Ver resposta

✓ Resposta: Dominar os princípios é mais valioso que decorar uma API porque os princípios são estáveis e universais, enquanto as APIs são específicas e mudam. O RAII, o tratamento de erros e a STL se aplicam a qualquer banco, biblioteca ou recurso — são o como pensar sobre gerenciar recursos e falhas em C++. A API específica de um banco (os nomes exatos das funções da libpqxx, os tipos do mongocxx) é um detalhe que você consulta na documentação quando precisa, e que muda entre versões e bancos. Quem decorou só a API de um banco está preso a ele; quem domina os princípios aplica-os a qualquer um. Isso se torna concreto ao migrar uma aplicação de um banco para outro: se você envolveu o banco antigo numa classe RAII com uma interface limpa (consultar, inserir) que esconde a API específica, migrar significa reescrever apenas o interior dessa classe para a nova API — o resto da aplicação, que usa a interface abstrata, não muda. Os princípios (encapsular o recurso atrás de uma interface, RAII, tratamento de erros) tornam a migração uma troca localizada em vez de uma reescrita. É a diferença entre ter aprendido uma ferramenta e ter aprendido a arte de usar ferramentas — e essa arte é o que o curso inteiro cultivou.

Comentários

Mais em Linguagem C++

Requisitos Explícitos — Concepts e o Fim dos Erros Ilegíveis
Requisitos Explícitos — Concepts e o Fim dos Erros Ilegíveis

Sem restrição declarada, passar um tipo inadequado a um template produz…

Entidades de Verdade — Componentes e o Padrão ECS
Entidades de Verdade — Componentes e o Padrão ECS

Modelar objetos de jogo por herança quebra no primeiro inimigo que também voa…

O Projeto Final Começa — Arquitetando um Mini Banco de Dados em Memória
O Projeto Final Começa — Arquitetando um Mini Banco de Dados em Memória

O projeto final não traz conceito novo — cobra os que já apareceram, todos ao…