Package systextil.dao


package systextil.dao

Faz o mapeamento de tabelas de banco de dados do Systêxtil 5 para classes Java, utilizando orientação a objetos e funcionalidades de baixo nível. Este mapeamento tem por objetivo possibilitar a substituição do uso de comandos SQL distribuídos ao longo de extensos códigos-fonte convertidos do Vision por objetos Java com tipagem forte e comunicação automática com o banco de dados.

Não existe nesta package, ainda, uma organização hierárquica ou em módulos entre as classes. Todas são consideradas no mesmo nível. Não há modularização de negócio, e sim de arquitetura.

As classes nesta package não devem fazer referências a classes em outras packages mais específicas. Esta package forma uma camada de arquitetura de negócio acima das camadas de persistência (JDBC e systextil-connection) e abaixo das demais camadas de negócio (systextil-bo e systextil-function). Estas classes podem fazer referências a classes do projeto systextil-connection e da package systextil. Não podem de maneira nenhuma fazer referências a funções e a processos, para não prejudicar a modularização, mas podem ser referenciadas por eles. Não podem fazer referência a outras classes utilitárias, como classes de tratamento de mensagens, de acesso a arquivos, de geração de relatórios, etc. nem podem ser referenciadas por elas, porque se destinam exclusivamente a processos de negócio.

As classes desta package em geral fornecem objetos de acesso a dados (DAO) e não podem conter lógicas complexas, como cálculos, validações e processos. Esse tipo de coisa deve ficar com classes de processos e funções. Por isso, também, estas classes nunca lançam TagException. (Ainda está em estudos se é permitido lançar RuleViolation.)

Cada classe que representa uma tabela do Systêxtil 5 possui os comandos SQL associados a essa tabela. Somente essa classe pode conter comandos SQL associados a essa tabela, e não pode conter comandos SQL associados a outra tabela. Com isso cada classe encapsula os comandos SQL que se referem à sua tabela. O código-fonte de processos que utilizam essa tabela passa a utilizar objetos dessa classe no lugar de executar diretamente comandos SQL. Gradativamente, vai-se eliminando a execução de comandos SQL em processos de negócio e em funções. Os processos passam a utilizar cada vez mais orientação a objetos de verdade.

É permitido - e mesmo recomendado - que objetos de uma classe façam referência a objetos de outra classe desta mesma package com os quais possuam uma relação forte - p. ex. a capa de um pedido obter os dados do cliente associado, bem como carregar os seus próprios itens, e os itens de um pedido possuírem uma referência à sua capa e carregarem os dados dos produtos. Não há limites para as classes desta package fazerem referências umas às outras, sempre respeitando que uma jamais execute um comando SQL na tabela da outra, e que essas chamadas não envolvam regras e cálculos complexos. No entanto, é bem-vinda uma certa preocupação com a futura modularização do sistema em módulos especialistas em negócios.

Por exemplo: onde deve estar o método que traga uma lista de pedidos de um cliente?
a) um método da classe de clientes
b) um método estático da classe de pedidos
Como os pedidos dependem do cliente e o cliente não depende dos pedidos, então a resposta correta é b).

São permitidos cálculos bem elementares na obtenção de dados de um objeto, p. ex. obter o total dos itens de um pedido. Cálculos mais complexos, como a obtenção de descontos, aplicação de tabelas de preços, impostos, parcelamentos, são proibidos nesta package.

É importante ter sempre em mente as finalidades das classes desta package:

  1. gerar objetos contendo os dados que foram lidos de um registro, em modo somente-leitura;
  2. prover métodos para manipulação da tabela mediante comandos SQL elementares (insert, update e delete);
  3. obter facilmente outros objetos relacionados diretamente ao objeto atual.

Os dados de um registro são alterados exclusivamente mediante métodos que executam update. Não existem setters. Não existem métodos para persistir alterações no objeto.

Como os dados dos objetos são somente para leitura, eles são válidos somente enquanto o registro original não é alterado no banco de dados. Assim que esse registro é alterado - mediante update ou alguma trigger - o objeto atual deve ser descartado e um novo deve ser obtido a partir de nova leitura do registro. Essa forma de trabalho é perfeitamente compatível com a programação atual do Systêxtil 5, que lê variáveis a partir de registros e repete as leituras conforme a necessidade. Assim, a conversão de Vision para Java neste framework fica bastante direta.

Detalhamento das regras para construção das classes

O nome da classe segue o padrão Java de nomenclatura de classes, e reflete a função da tabela nos processos de negócio do Systêxtil - no singular.

  • Se uma tabela não tiver propósito em existir independentemente de uma estrutura de tabelas já existente, pode ser conveniente atribuir à sua classe um nome que reflita essa estrutura, para maior clareza quando estiverem ordenadas alfabeticamente. Por exemplo, a classe que corresponde a endereços dos clientes fica melhor representada se for chamada de ClienteEndereco; a de item de pedido de venda seria PedidoVendaItem.

As colunas da tabela ficam representadas por campos somente-leitura. Isso significa que todos os campos correspondentes a colunas são definidos como public final e são preenchidos no construtor, a partir da leitura de um registro da tabela.

  • Os nomes desses campos devem corresponder exatamente aos nomes das colunas correspondentes na tabela do banco de dados, em minúsculas. Algumas exceções são apresentadas mais adiante.
  • O tipo dos campos é baseado no tipo das colunas, com as mesmas regras aplicadas pelo Vision - inclusive considerando que colunas definidas com dois dígitos decimais são mapeadas para campos Amount.
  • O preenchimento e leitura dos campos não é feito usando setters e getters, do padrão de JavaBeans, pois isso polui demais o código-fonte das classes e a API, e não tem necessidade, pois o valor desses campos nunca muda.
  • O uso de frameworks padronizados para persistência, como JPA, Hibernate, é inadequado neste contexto, pois estamos trabalhando com um legado de tabelas que não estão otimamente normalizadas e que são manipuladas mediante comandos SQL em profusão. A evolução a partir desse legado deve continuar mantendo o controle sobre os comandos SQL que fazem a manutenção dos dados no banco, sem que para isso os mesmos comandos SQL sejam copiados ou escritos várias e várias vezes nas classes de processos de negócio. Delegar a execução de comandos SQL a frameworks que os constroem automaticamente não é viável neste momento.

O construtor tem como único objetivo inicializar os campos do objeto. Por isso, esse construtor é private e é invocado exclusivamente em outro método private static make(AppConnection cn) que retorna o objeto inicializado com os valores extraídos de um registro do banco de dados. É esse método que será invocado pelos demais métodos públicos e estáticos da classe, retornando um objeto ou uma coleção de objetos lidos do banco de dados.

Métodos do tipo update ou delete que afetam o registro correspondente ao próprio objeto devem retornar void. Métodos que afetam registros mediante um filtro devem ser static e retornar a quantidade de registros afetados.

Métodos do tipo insert são static e normalmente retornam void. Em algum momento pode ser conveniente fazer que um método assim sempre retorne o objeto que foi inserido, para que ele já possa ser utilizado em seguida, quando isso for útil.

Métodos que executam update devem ter seu nome iniciando com update e complementado com o nome dos campos que são atualizados, ou uma descrição da finalidade desse update. Exemplos: public void updateSituacao(AppConnection conn, int cod_situacao) e public static int updateZerarNFe(AppConnection conn, int cod_solicitacao_nfe).

Todos os métodos que devem fazer alguma operação no banco de dados, inclusive os métodos que buscam outros objetos associados ao objeto atual, devem receber uma AppConnection como primeiro argumento.

A única maneira de obter uma instância a partir de sua chave primária é através do método estático get. Esse método pode ser sobrecarregado para aceitar como argumentos agrupadores de códigos (p. ex. Cnpj) ou objetos dos quais a própria instância dependa (p. ex. a capa do item) para permitir que se evite o tráfego de códigos elementares quando já se possui objetos prontos com as informações necessárias.

Métodos que retornam uma coleção de objetos a partir de uma busca devem ter seu nome começando com list e complementado com o que for necessário para a compreensão de sua finalidade. Uma exceção é quando traz a lista de todos os objetos associados ao objeto atual, quando continua seguindo o padrão getAlgo. Alguns exemplos:

  • A lista de itens de uma capa: public static Item[] list(AppConnection conn, Capa capa) na classe Item.
  • A lista de itens desta capa: public Item[] getItens(AppConnection conn) na classe Capa. (Curiosamente, este método deve invocar o anterior.)
  • A lista de itens bloqueados por ordem de data: public static Item[] listBloqueadosOrderByDate(AppConnection conn, Capa capa) na classe Item.

Instâncias "nulas"

No Systêxtil 5 em Vision, é comum que algumas variáveis sejam preenchidas na consulta a um registro de uma tabela, e se esse registro não existir (isto é, status <> 0), essas variáveis recebem um valor padrão, como zero ou vazio.

Neste framework, se não existir um registro a retornar através do método estático get, o retorno é nulo. Em programas convertidos do Vision para Java, se o método get retornar nulo, é preciso preencher aquelas variáveis com valores padrão, o que pode ser muito inconveniente. O código fica poluído com testes do tipo if(obj != null) {/* valores lidos */} else {/* valores padrão */}.

Por conveniência, algumas classes possuem definida uma instância fictícia previamente preenchida com valores padrão, chamada NULL_INSTANCE. Essa instância pode ser usada como retorno nos casos em que não é encontrado registro no banco - desde que o método que a retorna tenha NotNull no nome. Por exemplo, um método chamado getNaturezaDeOperacao retorna nulo se o registro não existir, e o método getNaturezaDeOperacaoNotNull retorna NaturezaDeOperacao.NULL_INSTANCE nessa situação. O uso de métodos NotNull garante que o retorno nunca será nulo, e não é preciso testar essa condição.

Herança

Apesar das classes desta package serem destinadas primariamente a facilitar o acesso a dados de uma e somente uma tabela do banco de dados, sem o uso de regras de negócios e de processos, a aplicação de técnicas de orientação a objetos a estas classes facilita tremendamente seu uso em processos complexos.

A presença de redundâncias de colunas em tabelas que possuem finalidades parecidas pode ser contornada facilmente utilizando classes abstratas e herança.

Por exemplo: as classes correspondentes a notas fiscais de entrada e notas fiscais de saída, apesar de representarem tabelas diferentes, ambas são subclasses de uma classe abstrata de notas fiscais, a qual centraliza características e métodos que são comuns a ambas. Desta maneira, processos que se aplicam igualmente a notas fiscais de entrada e de saída não precisam ter sua lógica duplicada.

Outro exemplo: as classes de clientes e de fornecedores são subclasses de uma classe abstrata que centraliza características e métodos que são comuns a empresas em geral.

Quando se utiliza herança, pode ser necessário utilizar nomes de campos que não sejam idênticos aos das colunas no banco de dados para unificar campos comuns a algumas classes. Somente nestas situações é permitido utilizar nomes de campos ligeiramente diferentes dos nomes das colunas originais. Isto também se aplica a subclasses de agrupadores de códigos.

Agrupadores de códigos

Nesta package encontram-se também classes que servem para agrupar códigos compostos que são muito usados ao longo do sistema. Exemplos são as que agrupam os códigos de CNPJ, os códigos de produto e de alternativa. Estas classes não estão vinculadas a tabelas do banco de dados. Sempre que possível, o uso de objetos dessas classes substitui a leitura ou o fornecimento de grupos de códigos ou campos que são sempre usados juntos, o que é muito melhor que ficar manipulando objetos tão genéricos como códigos numéricos e Strings. Algumas classes de DAO inclusive são subclasses desses agrupadores, pois seus objetos podem ser utilizados diretamente com a mesma finalidade.