Data

8 jun, 2011

Afinal, o que é uma entidade?

Publicidade

Não vou começar falando de SQL, como geralmente é feito, mesmo que copiando trechos da Wikipedia.

Vejamos um conceito importante e interessante, mas que é fatalmente sonegado aos programadores, é o conceito de entidade. Basta tentar pesquisar por este termo, e notar como o conteúdo é escasso.

Talvez por isso, os resultados são sistemas mal modelados, não normalizados, em que a aplicação tenta resolver diversos problemas da estrutura, e onde qualquer novo requisito, gera um grande transtorno, pois a análise para criação do modelo, não foi feita corretamente, de modo a deixar a estrutura robusta e expansível.

Um exercício rápido

  • Volante, Pára-choque, Retrovisores, Câmbio, Chassis, Rodas, Motor, Portas…

O que lhe veio a mente? Qual objeto do nosso mundo agrega essas partes que citei? Espero que a sua resposta seja ‘um carro’. Faz parte do senso comum.

Mais uma vez

  • Galhos, Folhas, Raíz, Tronco, Copa, Calotas..

Uma árvore? Só as ‘calotas’ que parecem não estar no lugar correto, pois não combinam com os outros itens, e não conseguimos imaginar que as calotas, devam pertencer a nossa árvore.

Melhor mover as ‘calotas’ para o ‘carro’ lá de cima, para se encaixar melhor em uma normalidade aceitável.

Ao iniciar o levantamento de informações, o processo é mais ou menos parecido com esse. O carro, e a árvore são as nossas entidades e cada um dos itens que citei, são os atributos particulares dessas entidades. A idéia é saber a quem deve pertencer cada atributo, e sermos capazes de definirmos as entidades do nosso sistema.

Formalmente

  • Uma entidade possui atributos.
  • Os atributos são as características e não devem conter um grupo de informações.
  • Não existem entidades com menos de 2 atributos. Logo, cada entidade, é em si, um grupo de atributos.

Trazendo de forma livre para a nossa realidade, cada Entidade é uma tabela, e cada Atributo é cada uma das colunas dessas tabelas.

O ponto que quero levantar é a compreensão do conceito. Se tenho que fazer um cadastro de usuário, preciso pensar o que a entidade usuário significa para o meu sistema e o que preciso saber dela ou não.

    CREATE TABLE `test`.`usuario` (  
       `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , 
       `nome` VARCHAR( 50 ) NOT NULL 
    ) ENGINE = InnoDB; 

A nossa entidade usuario possui um `nome`, e um `id`, que é um identificador único desse objeto no nosso modelo.

Agora o cliente nos informa que ele precisa saber dos usuarios dele, pelo menos:

  • um telefone residencial, e
  • um celular.

Num primeiro momento, a nossa reação será adicionar essas informações à entidade usuário, pois o telefone e o celular pertencem respectivamente a cada usuário nosso.

    ALTER TABLE `usuario` ADD `telefone` VARCHAR( 14 ) NOT NULL ,  
    ADD `celular` VARCHAR( 14 ) NOT NULL  


Mas se nosso chefe vem e nos informa que agora ele precisa também do Telefone Comercial do nosso usuário e quem sabe talvez um Telefone de Contato? É nesse momento que o nosso modelo pode naufragar ou não.

Adicionar mais essas 2 colunas a tabela seria um erro no ponto de vista da modelagem de negócios, pois estaríamos novamente, subestimando a aplicação. E se lá pra frente, ele decidir incluir o Pager (ainda usam isso? hahaha), ou o id Nextel do usuário?

Nesta hora, deveria começar a ficar claro que essa expansão clama por uma nova entidade na nossa modelagem.

ALTER TABLE `usuario` DROP `telefone` , DROP `celular` ; 

Estrutura da nova entidade

Dados cadastrados:

Portanto, se precisamos de inserir mais formas de contato, basta adicionar um registro a essa tabela. E isso pode ser facilmente feito na nossa aplicação, sem a necessidade de alterarmos a estrutura do modelo.

    INSERT INTO `test`.`contato` (`id`, `nome`) VALUES (NULL, 'Nextel');  

A única questão não respondida ainda é como vincular os dados do usuário a esta nossa nova entidade. Faremos então outra tabela, já bem normalizada, da seguinte forma:

    CREATE TABLE `test`.`contato_usuario` (  
        `id_usuario` INT NOT NULL , 
        `id_contato` INT NOT NULL , 
        `descricao` VARCHAR( 70 ) NOT NULL 
    ) ENGINE = InnoDB; 

Essa nossa nova tabela será responsável por fazer a ligação entre as nossas entidades.

A relação entre as tabelas é evidente e o nosso JOIN para resgatar esses dados fica da seguinte forma:

    SELECT `usuario`.`id` AS `id_usuario` , `usuario`.`nome` AS `nome_usuario` ,  
      `contato`.`nome` AS `nome_contato` , `contato_usuario`.`descricao` 
        FROM `usuario` 
            INNER JOIN `contato_usuario` ON `usuario`.`id` = `contato_usuario`.`id_usuario` 
            INNER JOIN `contato` ON `contato`.`id` = `contato_usuario`.`id_contato` 

Saída:


Continuando, surgem novos atributos:

data de nascimento, cidade natal, email, estado civil..

Precisamos dividir esses atributos nas nossas entidades. A data de nascimento, a cidade natal e estado civil cabem muito bem na entidade usuário. Já o email, podemos colocar na entidade contato, concorda?

Se mais adiante precisarmos de um ‘email_secundario’, para proteção do sistema, onde o usuário poderá resgatar a senha dele na nossa aplicação, bastará novamente, inserirmos um registro na entidade contato, sem a menor alteração do modelo que criamos.

Esse particionamento que fizemos tem o seu custo. A maior complexidade do modelo, a queda de performance (afinal, agora consultamos 3 tabelas e não apenas uma). E em contrapartida, temos os ganhos que essa modelagem trouxe a estrutura da nossa aplicação.

Analise bem os requisitos do seu sistema antes de implementá-lo. Não tome todo e qualquer tutorial como lei ou verdade absoluta.

Dump completo do SQL usado:

    -- phpMyAdmin SQL Dump  
    -- version 3.3.8.1 
    -- http://www.phpmyadmin.net 
    -- 
    -- Servidor: localhost 
    -- Tempo de Geração: Mar 29, 2011 as 02:54 AM 
    -- Versão do Servidor: 5.5.8 
    -- Versão do PHP: 5.3.4 
     
    SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; 
     
    /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 
    /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 
    /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 
    /*!40101 SET NAMES utf8 */; 
     
    -- 
    -- Banco de Dados: `test` 
    -- 
     
    -- -------------------------------------------------------- 
     
    -- 
    -- Estrutura da tabela `contato` 
    -- 
     
    CREATE TABLE IF NOT EXISTS `contato` ( 
      `id` int(11) NOT NULL AUTO_INCREMENT, 
      `nome` varchar(40) NOT NULL, 
      PRIMARY KEY (`id`) 
    ) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ; 
     
    -- 
    -- Extraindo dados da tabela `contato` 
    -- 
     
    INSERT INTO `contato` (`id`, `nome`) VALUES 
    (1, 'Telefone Residencial'), 
    (2, 'Telefone Comercial'), 
    (3, 'Celular'), 
    (4, 'Nextel'); 
     
    -- -------------------------------------------------------- 
     
    -- 
    -- Estrutura da tabela `contato_usuario` 
    -- 
     
    CREATE TABLE IF NOT EXISTS `contato_usuario` ( 
      `id_usuario` int(11) NOT NULL, 
      `id_contato` int(11) NOT NULL, 
      `descricao` varchar(70) NOT NULL 
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 
     
    -- 
    -- Extraindo dados da tabela `contato_usuario` 
    -- 
     
    INSERT INTO `contato_usuario` (`id_usuario`, `id_contato`, `descricao`) VALUES 
    (1, 2, '(11) 1234-5678'), 
    (1, 4, '55*11*1111'), 
    (2, 1, '(12) 4321-8765'), 
    (2, 2, '(11) 8765-4321'), 
    (2, 4, '55*22*2222'); 
     
    -- -------------------------------------------------------- 
     
    -- 
    -- Estrutura da tabela `usuario` 
    -- 
     
    CREATE TABLE IF NOT EXISTS `usuario` ( 
      `id` int(11) NOT NULL AUTO_INCREMENT, 
      `nome` varchar(50) NOT NULL, 
      PRIMARY KEY (`id`) 
    ) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ; 
     
    -- 
    -- Extraindo dados da tabela `usuario` 
    -- 
     
    INSERT INTO `usuario` (`id`, `nome`) VALUES 
    (1, 'Bruno'), 
    (2, 'Silveira');