Neste artigo muito tardio, explicarei por que, na minha opinião, na maioria dos casos, ao desenvolver um modelo de dados para um aplicativo, você deve seguir a primeira abordagem do banco de dados. Em vez da abordagem "Java [qualquer outra língua] primeiro"), que leva você a uma longa trajetória cheia de dor e sofrimento, assim que o projeto começa a crescer.

" CC muito ocupado para ser melhor", licenciado por Alan O'Rourke / Audience Stack . Imagem original
Este artigo foi inspirado em uma pergunta recente do StackOverflow .
Discussões interessantes do reddit / r / java e / r / programação .
Geração de código
Para minha surpresa, um pequeno grupo de usuários parece ter ficado chocado com o fato de o jOOQ estar fortemente vinculado à geração do código-fonte.
Embora você possa usar o jOOQ exatamente como você gosta, a maneira preferida (de acordo com a documentação) é começar com o esquema do banco de dados existente, gerar as classes de cliente necessárias (correspondentes às suas tabelas) usando o jOOQ e, depois disso, é fácil escrever com segurança de tipo consultas para estas tabelas:
for (Record2<String, String> record : DSL.using(configuration)
O código pode ser gerado manualmente fora da montagem ou automaticamente com cada montagem. Por exemplo, essa geração pode ocorrer imediatamente após a instalação das migrações do Flyway , que também podem ser iniciadas manualmente ou automaticamente.
Geração de código fonte
Existem diferentes filosofias, vantagens e desvantagens em relação a essas abordagens à geração de código que eu não quero discutir neste artigo. Mas, em essência, o significado do código gerado é que é uma representação Java do que consideramos um tipo de "padrão" (dentro e fora do sistema). De certa forma, os compiladores fazem a mesma coisa quando geram bytecode, código de máquina ou algum outro código-fonte a partir da fonte - como resultado, temos uma idéia do nosso "padrão" em outro idioma específico.
Existem alguns desses geradores de código. Por exemplo, o XJC pode gerar código Java a partir de arquivos XSD ou WSDL . O princípio é sempre o mesmo:
- Existe algum padrão (externo ou interno), como especificação, modelo de dados, etc.
- É necessário ter sua própria idéia desse padrão em nossa linguagem de programação usual.
E quase sempre faz sentido gerar essa visão para evitar trabalho desnecessário e erros desnecessários.
Provedores de tipo e processamento de anotação
Vale ressaltar que outra abordagem mais moderna para a geração de código no jOOQ é o Type Providers ( como é feito em F # ), onde o código é gerado pelo compilador durante a compilação e nunca existe na forma original. Uma ferramenta semelhante (mas menos sofisticada) em Java são os processadores de anotação, como o Lombok .
Nos dois casos, tudo é igual à geração normal de código, exceto:
- Você não vê o código gerado (talvez para muitos isso seja uma grande vantagem?)
- Você deve garantir que sua "referência" esteja disponível em todas as compilações. Isso não causa nenhum problema no caso do Lombok, que anota diretamente o próprio código fonte, que é o "padrão" nesse caso. Um pouco mais complicado com modelos de banco de dados que dependem de uma conexão ao vivo sempre ativa.
Qual é o problema com a geração de código?
Além da questão complicada de gerar o código manual ou automaticamente, algumas pessoas pensam que o código não precisa ser gerado. A razão pela qual eu ouço com mais frequência é que essa geração é difícil de implementar no pipeline de CI / CD. E sim, é verdade, porque somos sobrecarregados por criar e oferecer suporte a infraestrutura adicional, especialmente se você é novo nas ferramentas usadas (jOOQ, JAXB, Hibernate etc.).
Se a sobrecarga de estudar o gerador de código for muito alta, haverá realmente poucos benefícios. Mas este é o único argumento contra. Na maioria dos outros casos, não faz absolutamente sentido escrever código manualmente, que é a representação usual de um modelo de algo.
Muitas pessoas afirmam que não têm tempo para isso, porque agora você precisa lançar outro MVP o mais rápido possível. E eles poderão finalizar seu pipeline de CI / CD algum tempo depois. Nesses casos, costumo dizer: "Você está ocupado demais para melhorar".
"Mas o Hibernate / JPA facilita muito o primeiro desenvolvimento do Java."
Sim, é verdade. Isso é uma alegria e uma dor para os usuários do Hibernate. Com ele, você pode simplesmente escrever vários objetos do formulário:
@Entity class Book { @Id int id; String title; }
E isso está quase pronto. Em seguida, o Hibernate executará toda a rotina sobre como definir esse objeto no DDL e no dialeto SQL desejado:
CREATE TABLE book ( id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, title VARCHAR(50), CONSTRAINT pk_book PRIMARY KEY (id) ); CREATE INDEX i_book_title ON book (title);
Esta é realmente uma ótima maneira de iniciar rapidamente o desenvolvimento - você só precisa iniciar o aplicativo.
Mas nem tudo é tão róseo. Ainda existem muitas perguntas:
- O Hibernate irá gerar o nome necessário para a chave primária?
- Vou criar o índice necessário no campo TITLE?
- Um valor de ID exclusivo será gerado para cada registro?
Parece que não. Mas enquanto o projeto está em desenvolvimento, você sempre pode jogar fora seu banco de dados atual e gerar tudo do zero, adicionando as anotações necessárias ao modelo.
Portanto, a classe Book em sua forma final será mais ou menos assim:
@Entity @Table(name = "book", indexes = { @Index(name = "i_book_title", columnList = "title") }) class Book { @Id @GeneratedValue(strategy = IDENTITY) int id; String title; }
Mas você pagará por isso, um pouco mais tarde
Cedo ou tarde, seu aplicativo entra em produção e o esquema descrito para de funcionar:
Em um sistema vivo e real, você não pode mais simplesmente pegar e soltar seu banco de dados, porque os dados são usados e podem custar muito dinheiro.
A partir de agora, você precisa escrever scripts de migração para cada alteração no modelo de dados, por exemplo, usando o Flyway . No entanto, o que acontece com suas classes de clientes? Você pode adaptá-los manualmente (o que levará ao trabalho dobrado) ou pedir ao Hibernate para gerá-los (mas qual a probabilidade dos resultados dessa geração atenderem às expectativas?). Como resultado, você pode esperar grandes problemas.
Assim que o código entra em produção, é quase imediatamente necessário fazer as correções e o mais rápido possível.
E porque a instalação de migrações de banco de dados não é incorporada à sua linha de montagem; você precisará instalar esses patches manualmente por sua conta e risco. Não haverá tempo suficiente para voltar e fazer tudo certo. É o suficiente para culpar o Hibernate por todos os seus problemas.
Em vez disso, você poderia ter agido de maneira bem diferente desde o início. Ou seja, use rodas redondas em vez de quadradas.
Ir para o banco de dados primeiro
A referência e o controle do esquema de dados estão no escritório do seu DBMS. Um banco de dados é o único local em que um esquema é definido e todos os clientes têm uma cópia desse esquema, mas não vice-versa. Os dados estão no seu banco de dados e não no seu cliente; portanto, faz sentido fornecer controle do esquema e sua integridade exatamente onde os dados estão.
Isso é sabedoria antiga, nada de novo. Chaves primárias e exclusivas são boas. Chaves estrangeiras são lindas. Verificar as restrições no lado do banco de dados é maravilhoso. A afirmação (quando finalmente são implementadas) é ótima.
E isso não é tudo. Por exemplo, se você estiver usando Oracle, poderá especificar:
- Em que espaço de tabela está sua tabela?
- Qual o significado do PCTFREE que ela possui
- Qual é o tamanho do cache de sequência?
Talvez tudo isso não importe em sistemas pequenos, mas em sistemas maiores você não precisa seguir o caminho do "big data" até extrair todos os sucos do seu armazenamento atual. Nem um único ORM que eu já vi (incluindo o jOOQ) permitirá que você use o conjunto completo de parâmetros DDL que o seu DBMS fornece. Os ORMs oferecem apenas algumas ferramentas para ajudá-lo a escrever DDL.
Por fim, um esquema bem projetado só deve ser gravado manualmente usando uma DDL específica do DBMS. Todos os DDLs gerados automaticamente são apenas uma aproximação a isso.
E o modelo do cliente?
Como mencionado anteriormente, você precisará de uma certa representação do esquema do banco de dados no lado do cliente. Escusado será dizer que essa visão deve ser sincronizada com o modelo real. Como fazer isso? Obviamente, usando geradores de código.
Todos os bancos de dados fornecem acesso às suas meta-informações através do bom e velho SQL. Portanto, por exemplo, você pode obter uma lista de todas as tabelas de diferentes bancos de dados:
São essas consultas (assim como consultas semelhantes para visualizações, visualizações materializadas e funções de tabela) que são executadas quando o método DatabaseMetaData.getTables () de um driver JDBC específico é chamado, ou no módulo jOOQ-meta.
A partir dos resultados dessas consultas, é relativamente fácil criar qualquer representação de cliente do modelo de banco de dados, independentemente da tecnologia de acesso a dados usada.
- Se você usar JDBC ou Spring, poderá criar um grupo de constantes String
- Se estiver usando o JPA, você mesmo poderá criar objetos
- Se você estiver usando o jOOQ, poderá criar metamodelos do jOOQ
Dependendo do número de recursos que sua API de acesso a dados oferece (jOOQ, JPA ou outra coisa), o metamodelo gerado pode ser realmente rico e completo. Como exemplo, a função de junção implícita no jOOQ 3.11, que se baseia em meta-informações sobre os relacionamentos de chaves estrangeiras entre suas tabelas .
Agora, qualquer alteração no esquema do banco de dados levará automaticamente a uma atualização do código do cliente.
Imagine que você precise renomear uma coluna em uma tabela:
ALTER TABLE book RENAME COLUMN title TO book_title;
Tem certeza de que deseja fazer este trabalho duas vezes? De jeito nenhum. Apenas confirme esse DDL, execute a compilação e aproveite o objeto atualizado:
@Entity @Table(name = "book", indexes = {
Além disso, o cliente recebido não precisa ser compilado todas as vezes (pelo menos até a próxima alteração no esquema do banco de dados), o que já pode ser uma grande vantagem!
A maioria das alterações DDL também são alterações semânticas, não apenas sintáticas. Portanto, é ótimo ver no código do cliente gerado quais exatamente as alterações mais recentes no banco de dados afetadas.
A verdade está sempre sozinha
Independentemente da tecnologia usada, sempre deve haver apenas um modelo, que é o padrão para o subsistema. Ou, pelo menos, devemos nos esforçar por isso e evitar confusão nos negócios, onde o "padrão" está em todo lugar e em nenhum lugar ao mesmo tempo. Isso torna tudo muito mais fácil. Por exemplo, se você estiver compartilhando arquivos XML com outro sistema, provavelmente está usando o XSD. Como um metamodelo INFORMATION_SCHEMA jOOQ no formato XML: https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd
- XSD é bem entendido
- XSD descreve perfeitamente o conteúdo XML e permite a validação em todos os idiomas do cliente
- O XSD torna o versionamento fácil e compatível com versões anteriores
- XSD pode ser transformado em código Java usando XJC
Damos especial atenção ao último ponto. Ao se comunicar com um sistema externo por meio de mensagens XML, devemos ter certeza da validade das mensagens. E é realmente muito fácil de fazer com coisas como JAXB, XJC e XSD. Seria louco pensar sobre a adequação da abordagem Java primeiro neste caso. O XML gerado com base nos objetos XML será de baixa qualidade, será mal documentado e difícil de estender. E se houver um SLA para essa interação, você ficará desapontado.
Honestamente, isso é semelhante ao que está acontecendo com as várias APIs JSON agora, mas essa é uma história completamente diferente ...
O que torna os bancos de dados piores?
Ao trabalhar com um banco de dados, tudo é o mesmo aqui. O banco de dados possui os dados e também deve ser o mestre do esquema de dados. Todas as modificações de esquema devem ser feitas diretamente através do DDL para atualizar a referência.
Após atualizar a referência, todos os clientes devem atualizar suas idéias sobre o modelo. Alguns clientes podem ser gravados em Java usando jOOQ e / ou Hibernate ou JDBC. Outros clientes podem ser escritos em Perl (boa sorte para eles) ou mesmo em C #. Isso não importa. O modelo principal está no banco de dados. Embora os modelos criados usando o ORM sejam de baixa qualidade, pouco documentados e difíceis de estender.
Portanto, não faça isso e desde o início do desenvolvimento. Em vez disso, comece com um banco de dados. Crie um pipeline de CI / CD automatizado. Use a geração de código para gerar automaticamente um modelo de banco de dados para clientes para cada build. E pare de se preocupar, tudo ficará bem. Tudo o que é necessário é um pequeno esforço inicial para configurar a infraestrutura, mas, como resultado, você obterá um ganho no processo de desenvolvimento para o restante do seu projeto nos próximos anos.
Não, obrigado.
Explicações
Para consolidar: este artigo de forma alguma afirma que o modelo de banco de dados deve ser aplicado a todo o sistema (área de assunto, lógica de negócios etc.). Minhas declarações consistem apenas no fato de que o código do cliente que interage com o banco de dados deve ser apenas uma representação do esquema do banco de dados, mas não o define e forma de forma alguma.
Nas arquiteturas de duas camadas que ainda existem, o esquema do banco de dados pode ser a única fonte de informações sobre o modelo do seu sistema. No entanto, na maioria dos sistemas, vejo o nível de acesso a dados como um "subsistema" que encapsula um modelo de banco de dados. Algo assim.
Exceções
Como em qualquer outra boa regra, a nossa também tem suas exceções (e eu já avisei que a primeira abordagem do banco de dados e a geração de código nem sempre é a escolha certa). Essas exceções (talvez a lista não esteja completa):
- Quando o circuito não é conhecido antecipadamente e precisa ser investigado. Por exemplo, você é um provedor de uma ferramenta para ajudar os usuários a navegar em qualquer esquema. Obviamente, não pode haver geração de código. Mas, em qualquer caso, você precisa lidar com o próprio banco de dados e seu esquema.
- Quando, para alguma tarefa, você precisa criar um esquema em tempo real. Isso pode ser semelhante a uma das variações do padrão Entity-attribute-value , como você não tem um padrão claramente definido. Assim como não há certeza de que o RDBMS, neste caso, seja a escolha certa.
A peculiaridade dessas exceções é que elas raramente se encontram na vida selvagem. Na maioria dos casos, ao usar bancos de dados relacionais, o esquema é conhecido antecipadamente e é o "padrão" do seu modelo, e os clientes devem trabalhar com uma cópia desse modelo gerada usando geradores de código.