quinta-feira, agosto 23, 2007

As heurísticas do Merlin

Quando falo em utilizar heurísticas no Merlin, o pessoal fica perguntando: _Como podem ser implementadas essas coisas malucas em um software? Eu digo, simplesmente, que pode. Mas não digo como...

Na verdade, eu nunca me preocupei muito em como isso pode ser feito. Mas, como as perguntas continuam chegando, resolvi publicar a alternativa mais coerente e fácil. E a resposta é simples: _Usando o pattern Chain of Responsability.

Cadeias de responsabilidade
O padrão Chain of Responsability proposto por Gamma e seus amigos é muito bom nesse aspecto. Para quem trabalha com Filtros em Servlets, a coisa é bem semelhante.

Esse padrão diz, em suma, o seguinte: "Dado um problema X, passe esse elea para uma lista de objetos, para que um o resolva". E é bem isso mesmo. Se vocês já conhecem esse pattern, podem continuar. Se não conhecem, dêem uma procurada no Google e depois de ler, retornem para cá...

Heurísticas, Contexto e Histórico
Conforme comento na minha apresentação do Merlin, as heurísticas são regras simples, mas que podem ser conflitantes entre si. São coisas como, "ah, eu acho que assim é melhor (...)" ou "hum, isso é melhor que isso, nesse caso (...)". Mas o problema das heurísticas é justamente esse: podem existir muita gente...achando muita coisa diferente!

Pois bem, então para uma heurística ser boa, ela deve levar em consideração vários aspectos, como o estado do ambiente onde ela ocorre, o objeto a que ela se refere, quem a está utilizando, etc. Chamo isso tudo simplesmente de Contexto.

Mas não é tudo, a variável Tempo também deve ser considerda. Por exemplo, uma heurística que antes era magavilhosa (hic), pode não ser tão boa agora. E pior, ela pode ser péssima daqui a pouco.

Para controlar isso, é interessante contabilizar um Histórico de sucesso ou fracasso no uso das heurísticas. E daí, surge a necessidade das Estimativas.

As Estimativas
A cada uso de uma heurística, o sistema contabiliza se sela foi boa ou ruim. Se ela foi boa, é provável que ela seja interessante numa próxima vez (Hello, meninas: isso é o conceito de proximidade de contexto temporal, muito utilizado em algoritmos de caches). E daí ela ganha um pontinho. Se ela foi ruim, é provável que em um próximo momento, ela seja ruim novamente. E daí ela perde um pontinho.

Essas regras valem, quando os contextos de uso forem semelhantes. Caso contrário, não podemos ser tão simplistas... Mas tudo bem, não quero complicar mais ainda a vida de vocês...

Então, dito isso, vamos aos fatos.

Voltando ao Assado
E como essas heurísticas são implementadas, afinal ? A resposta é: juntamos o pattern de encadeamento, as heurísticas, as estimativas, o contexto e o histórico, num algoritmo assim:

1. O Merlin, que é nosso kernel, que utiliza várias e longas cadeia de responsabilidades para ir montando as telas em tempo de execução.
2. Essas cadeias de responsabilidades nada mais são do que montes de objetos que tratam problemas ao longo do tempo.
3. Cada vez que surge um problema (como por exemplo, escolher o melhor tipo de gui element para um atributo de classe), esse problema é delegado à uma lista de responsabilidades.
4. Esse problema vai navegando (pela própria natureza do pattern Chain) pela lista...
5. Cada heurística é um nodo dessa lista, sendo implementada como um padrão Command ou algo assim (ainda não decidi a melhor forma para isso).
6. Quando o problema X chega à uma heurística que se adeque a ele, a heurística aplica suas regras. Por exemplo, "se o atributo for notnull, então ela adiciona um validador de obrigatoriedade nele".
7. Daí, pela própria natureza do pattern Chain, a heurística acima encarrega-se de passar esse problema X para a próxima heurística da lista...
8. E daí o problema continua navegando, até que todas heurísticas sejam aplicadas.
9. Ao final do processo, todas heurísticas possíveis foram executadas.

Os Melhores em Último!
Obviamente, nesse processo, algumas heurísitcas podem acabar sobreescrevendo coisas de outras, ou seja, as últimas heurísticas da lista podem desfazer coisas feitas lá no começo...

Nesse sentido, quanto mais para o fim da fila uma heurística estiver, mais chances ela tem de suas regras permanecerem sobre o problema. É justamente aí que entram em cena as estimativas.

Durante todo esse processo, o sistema monitora e seleciona boas e más heurísticas, fazendo com que as que mais favoráveis fiquem no fim da fila.

Selecionando os Melhores
Mas como assim o sistema monitora e seleciona as melhores? É ai que entra em cena a figura humana.
Nas primeiras execuções, o Merlin usa suas (ou melhor, as minhas, hehehe) heurísticas, que dizem as regras básicas que devem ser aplicadas. Ou seja, é sua base de conhecimento inicial.

Conforme o cidadão vai utilzando ele no seu ambiente de desenvolvimento, pode ser identificado regras diferentes, como por exemplo "ah, na nossa empresa não usamos comboboxes, mas sim option buttons para valores que são mutuamente exclusivos...". Nesse caso, é criada uma heurística para isso e ela é colocada no fim da fila. Também poderia ser dito que a heurística das comboboxes é tirada da lista...

E como isso é feito? Pelas anotacões! O Merlin provê um conjunto (na verdade, bem simples e pequeno) de anotações na sua API. Assim, o desenvolvedor vai configurando o sistema da melhor forma para o seu contexto.

Conforme o tempo vai passando, o Merlin vai se ajustando ao ambiente onde ele está e as heurísticas vão sendo aplicadas automaticamente, de forma pró-ativa...

E deu-se o ciclo.

Conclusões (Conclusões ?)
Bem, aqui, simplesmente contei como o Merlin resolve essa questão complexa (hic) chamada heurísitca, ou como meu amigo Júlio diz, os Slistaks...

Claro, tem questões relacionadas a desempenho (nessa solução simplista), mas ainda não me preocupo, pois tenho outros algoritmos para evitar trabalhos redundantes ou sobreescritas ao longo da cadeia. Isso poderia ser feito através da simples reordenação da cadeia ou através do descarte de heurísiticas ruins antes mesmo de processá-las...mas não vem ao caso...

Não sei se consegui tirar as dúvidas da população que clama por mais informações sobre o submundo do Merlin. Mas, pelo menos, espero ter dado algumas dicas...

Para quem quer saber mais, acesse o grupo do Merlin.

Até mais.

domingo, agosto 12, 2007

EBAi 2007, submissão com suceso

No último post, eu comentei que estava com dúvida se meu artigo sobre desenvolvimento Java EE seria aceito no EBAI 2007...

Bem, após uma troca de email com a Carol, consegui que a submissão fosse aceita.

Agora é esperar o prazo das avaliações pra ver se poderemos ir para São Paulo em Outubro apresentar as idéias treelayerianas sobre construção de sistemas enteprise...

Avisarei sobre novidades.

sexta-feira, agosto 10, 2007

EBAi 2007, estamos tentando

No post do dia 03 do mes passado, eu comentei sobre o primeiro congresso de arquitetura de software que vai acontecer em São Paulo, agora em Outubro e a expectativa que eu tinha de escrever um artigo para esse evento.

Bom, escrevi. Mas não ficou como eu queria...

Não ficou como eu queria porque o texto ficou muito grande. O limite era 15 páginas (um bom limite) e mesmo assim, eu enchi todas elas falando dos Elementos do Desenvolvimento Multicamadas e não consegui falar sobre os Processos do Desenvolvimento Multicamadas e, por isso, acho que vou ter que gastar mais 30 horas para escrever um segundo texto.

Nas 15 páginas do artigo, eu falei sobre os Perfis, os Artefatos e as Ferramentas existentes em um ambiente de desenvolvimento Multicamadas, com foco, é claro na Java EE. Complementando o artigo, apresentei uma arquitetura-base que é padrão nos sistemas que desenvolvemos e conclui algumas coisas. Em suma, o artigo falou do seguinte:

Os Perfis
Foram comentados os três grandes grupos de perfis que existem no processo: o cliente, os gestores e a equipe de desenvolvimento. No âmbito do cliente, foram elencados os responsáveis gestão do projeto nessa parte, os usuários responsáveis pela homologação do sistema, e os usuários do chão-de-fábrica que, embora não sejam constantes no processo, têm cunho decisivo em detalhamentos operacionais. Na gestão, comentei sobre os atores que fazem o projeto acontecer na empresa, como os diretores, a gerência e as figuras, digamos, adimensionais, como a equipe de qualidade. Na equipe de desenvolvimento, foram aboradodos o restante da trupe, como arquitetos, projetistas, analistas, programadores e testadores. Estagiários, deixei fora. Mas dentro, ou melhor, externos (hic) foram elencados os designers e mesmo outros testadores. Entendam esses externos como perfis que a empresa pode contratar, por não ter mão-de-obra ou know how próprio.
Em suma, esses esses foram os Elementos Humanos.

Os Artefatos
Foram abordados os nada menos que 15 tipos de artefatos usados por nós para construir sistemas. Constaram, de forma categorizada desde os elementos de base, como o Documento de Visão e a (famosa) Planilha de Enquadramento, até coisas bem técnicas, como Diagramas de Componentes e Físico. Também entraram na lista os Esboços de Tela e claro, os Casos de Uso. De interesse para os menos avisados, descrevi o Diagrama de Requisitos, esse sim um elemento muito interessante. Para cada artefato, dei um Norte para contextualizá-lo no processo como um todo, os perfis envolvidos em sua criação e os comentários sobre cada um deles.
Esses foram então, os Elementos de Documentação.

As Ferramentas
Por ferramentas, podemos entender tanto software de base, como os editores visuais, as IDEs, os plugins, os geradores de PDF, a ferramenta CASE, servidor de aplicação, etc. Enfim, toda a panacéia que forma um ambiente de desenvolvimento profissional. No total, foi feita uma lista com nada menos que 53 ferramentas (uhu!), das quais 51 estão sob o conceito de Software Livre. As duas pagas são justamente a ótima ferramenta CASE Enterprise Architect e o famigerado Windows, que eu não consigo deixar de usar (agrhhh). Tentei colocar uma pequena análise de custos, para mostrar que sim, é possível desenvolver sistemas robustos com um custo abaixo de U$450, por máquina ou zero, no caso de máquinas de desenvolvedores que usam Linux.
Mas não é só de software que vive uma equipe de desenvolvimento e, portanto, coloquei coisas clássicas para nós aqui, como o (outro famoso) Quadro Branco, o Diário de Bordo, a máquina fotográfica e o gravador de áudio (que na prática, para nós, resumem-se nos "magavilhosos" W8xx da Sony-Ericson :).
Saliento que, no nosso processo, o uso de ferramentas da Gigante Branca, como Google Docs, Gmail, GTalk, Groups e Calendar são essenciais. E no artigo eu justifiquei o porquê.
Enfim, esses foram os Elementos do Arsenal.

E claro, a Arquitetura
Na segunda parte do artigo, tentei falar sobre o resultado final da condensação dos itens acima: a arquietura. Para tanto, separei os estratos da aplicação nas 5 camadas: Apresentação, Controle, Negócio, Persistência e Armazenamento. Comentei cada elemento dessas camadas, passando desde coisas como ignóbil sexteto da web (HTML+CSS+JavaScript+Ajax+JSF+Tiles) até o elegante JBoss Seam e os clássicos EJBs e seus patterns, como o Facade, Delegate, Action, Proxy e por aí vai. Mostrei um diagrama com as principais classes envolvidas e os pontos de extensão da arquitetura. Comentei sobre o controle de acesso que é feito pelo Magoo e maravilhas intrínsecas dessa ferramenta.

Ao final, rolaram dois ou três parágrafos de conclusão (que ao meu ver são as eternas, enfadonhas mas necessárias linhas decisivas de todo e qualquer texto) e pronto.

O envio
Ahah. Até me lembro desse subtítulo - o Envio - no post do artigo do Merlin para o FISL2007.... Enfim, não sei se foi um envio propriamente dito. Isso porque, eu já estava com o prazo estourado e, mesmo com o sinal verde (e até amável) da Carol, acabei "furando" o tempo extra que ela me deu.
Era para essa Quinta pela manhã e eu tentei submeter só agora, às 23:57h. Ou seja, nada de cerveja. Mandei um mail direto pra ela e estou aguardando o retorno pra ver no que deu.

Sinceramente, o artigo não ficou como eu queria. Mas dificilmente ficaria, pois acho que sou muito como o Mano Roger e o Didigo: extremamente perfeccionisa. E sendo assim, se a submissão for aceita, eu acredito (mesmo, sério) que o artigo seja aprovado.

Se não for, paciência e, aí sim, cerveja!

quinta-feira, agosto 09, 2007

Performance para busca de estruturas com herança

Este post interessa à quem precisa armazenar estruturas com herança em banco de dados relacionais. Vou iniciar pelo problema e ao final mostro a solução, que minimiza o uso de junções (joins) para recuperar as informações na maioria dos casos.

1. O problema

1.1. Herança
É comum em sistemas de banco de dados, a figura a entidade
Pessoa. Ela representa justamente registros que são pessoas no sistema. Entretanto, essa simplicidade é somente aparente. Na maioria (senão a totalidade dos casos) um registro de pessoa não adianta muito. As regras do sistema geralmente a tratam sob a perspectiva do contexto de negócio. Por exemplo, a pessoa para a área de Vendas da empresa é chamada de Cliente, para o Estoque, de o Fornecedor; para o RH é de Colaborador, Convênio, Conveniado, etc. Ou seja, o mesmo registro de pessoa (por exemplo, de código 123) é interpretado de várias formas, dependendo de onde ele é usado.

1.2. Mundo OO versus Mundo Relacional
Esse tipo de situação, geralmente demanda o uso de estruturas com herança para ser implementado. No mundo OO, isso seria relativamente fácil, pois o suporte a
Polimorfismo e o (bom) uso de Interfaces e Classes Abstratas daria conta do recado. Com o advendo do Hibernate, a busca e a modificação dessas estruturas são muito fáceis.

Entretanto, é comum armazenar essas informações em banco de dados relacionais. E, nesse caso, uma série problemas acontece. O principal deles (ao meu ver) é a performance. Ou seja, por mais que tenhamos uma boa técnica de mapeamento objeto-relacional (o Hibernate e o JPA suportam
de cara três alternativas - trato isso na minha palestra sobre o Hibernate), cada abordagem possui prós e contras. Na prática, ou ganhamos em momentos de pesquisa, ou ganhamos em otimização do uso das tabelas no banco, ou ganhamos nas operações de ou insert, ou update ou delete. Em palavras simples, nunca teremos 100% nas três dimensões.

1.3. Exclusões Lógicas
Não obstante esses problemas, é muito comum em sistemas profissionais a questão da
Exclusão Lógica, onde uma operação de delete na verdade não apaga o registro no banco de dados, mas sim, simplesmente marca-o de forma que ele pareca não existir mais.

Esse comportamento é necessário para evitar que um registro seja usado novamente no sistema. Entretanto, ele não pode ser apagado porque podem existir dezenas (ou centenas) de outros registros que dependem dele. Assim, se ele fosse efetivamente removido do banco de dados, seria necessário atualizar ou remover esses registros dependentes. E isso pode ser um ciclo que - na prática - poderia acabar por remover em cascata todos os registros do banco de dados!

É muito comum efetuar exclusões lógicas nas estruturas de
pessoas. Por exemplo, em uma loja poderia ser desejado excluir o cliente 123. Entretanto, essa pessoa é, também, um funcionário da loja. Assim, não é possível excluir o cliente sem perder o registro do funcionário. Daí entra a exclusão lógica, onde o registro 123 é marcado como logicamente excluído para o cliente - mas não para o funcionário.

A implementação da exclusão lógica é feita, na forma trivial, pela adição de um campo de
flag na tabela Cliente, que indica se o registro está ativo ou não. É essa a abordagem clássica.

1.4. Os Problemas Juntos na Vida Real
Ao juntar as peças do quebra-cabeça acima, surgem situações que geram, no mínimo, graves problemas de performance no banco de dados. Vou dar um exemplo de um sistema que estou desenvolvendo. Nele, a figura
pessoa possui cerca de 12 subtipos diferentes. Pelo esquema criado no banco (pelos analistas) existe uma tabela-mãe Pessoa e 12 tabelas-filha, digamos assim. Nessa implementação, os dados comuns à todas as pessoas são armazenados na tabela-mãe, ao passo que os dados específicos de cada um dos subtipos são armazenados na respectiva tabela-filha. A chave primária é compartilhada entre as tabelas, de forma que a pessoa 123 tem seu ID replicado em cada tabela-filha que ela exista.

Nesse cenário, para recuperar os dados completos do
Colaborador número 123, por exemplo, é necessário uma junção entre a tabela Pessoa e a Colaborador buscando pelo registro 123. Isso é simples e é performático.

O problema surge quanto desejamos efetuar buscas como:
Identificar todos os tipos da pessoa 123. Nessa busca, a solução clássica é ser feita de duas formas: (1) ou efetua-se uma junção entre a tabela-mãe e todas as tabelas-filha, (2) ou cria-se um campo extra na tabela-mãe que contém um identificador dizendo que, aquele registro, é do tipo colaborador, cliente, fornecedor, etc.

No início do sistema, os analistas propuseram a solução da junção. Entretanto, viram logo que isso seria demasiado custoso, uma vez que a grande maioria das pesquisas do sistema necessitaria efetuar essas junções. Logo depois, tentaram a solução de uma coluna
String na tabela Pessoa, de forma a executar um famigerado "substring" nessa coluna para identificar os subtipos da pessoa. Logo perceberam que o substring era tão ruim quanto as junções da outra solução.

Não obstante a essa situação adversa, eles ainda não tinham colocado a variável da exclusão lógica nesse cenário - o que acresceria, no mínimo, um operador AND nos selects. Em palavras simples, estavam no mato sem cachorro.

Foi essa
a bola que passaram para os projetistas resolverem...

2. Uma Solução Razoável (e matematicamente simples)

Por obra do destino, o projetista responsável pelo módulo que trata a estrutura de pessoas era justamente...eu :)

Do cenário deles, eu já estava a par. E também sabia as soluções que poderiam ser feitas no modo trivial, alá Hibernate ou JPA. Mas elas não seriam melhores do que as que os meus colegas já haviam tentado. Era preciso algo mais.

Não lembro como foi, mas o fato é que pensando na situação, me veio à cabeça o esquema de permissões de arquivos do Unix. Legal, ?!

Bem, nesse esquema, é dado um número único para um recurso, que é capaz de identificar completamente a permissão que existe sobre ele. É o famoso CHMOD. Esse esquema funciona com base em um argumento matemático simples para números binários, onde um número de base 2 qualquer é sempre maior que a soma de todos os números que o precedem em uma unidade. Vou dar um exemplo:

0 = 0
1 = (1)
2 = (1) + 1
4 = (1) + 1 + 2
8 = (1) + 1 + 2 + 4
16 = (1) + 1 + 2 + 4 + 8
32 = (1) + 1 + 2 + 4 + 8 + 16
64 = (1) + 1 + 2 + 4 + 8 + 16 + 32
128 = (1) + 1 + 2 + 4 + 8 + 16 + 32 + 64
256 = (1) + 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128
512 = (1) + 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256

... e por aí vai.

Mas o que isso tem a ver com o caso de pessoa e seus subtipos? Explico:

Vamos supor que façamos isso:

Registros que são Pessoa tem valor 1; Colaborador tem valor 2; Funcionário, 4; Convênio, 8; Fornecedor, 16; Conveniado, 32. Páro aí nesse exemplo.

Então, se uma pessoa for Colaborador e Funcionário, ela vale 2 + 4 = 6. Se ela for Colaborador e Conveniado, ela vale 2 + 32 = 34. Se for Fornecedor e Convênio, ela vale 16 + 8 = 24.

Em outras palavras, cada vez que uma pessoa assume um novo perfil, ela tem seu valor somado. Se ela perde um perfil, ela tem seu valor subtraído.

Assim, o que temos? Temos um chamado número mágico, que é capaz de identificar todas os subtipos de uma pessoa através de uma decomposição matemática. Esse número é equivalente à solução da "substring", mas muito mais performático, uma vez que evita demorados cálculos sobre campos alfanuméricos. É esse campo que é salvo na famosa coluna Tipo na coluna Pessoa.

Assim, para eu efetuar uma pesquisa do tipo "O que o registro pessoa 123 é no sistema?" é fácil eu fazer um select sobre a tabela pessoa e, dentro da aplicação (usando uma estrutura de hash simples) mostrar quais são os tipos de uma pessoa. Da mesma forma, para uma pesquisa como "Me retorne todos os colaboradores" basta efetuar um select sobre a tabela Pessoa buscando os registros que resultem verdadeiro para a operação Pessoa.Tipo & Colaborador == Colaborador, algo simples e eficiente em qualquer BD ou linguagem de programação

E quanto aos ao registros excluídos logicamente? Nesse caso, eu faço a técnica da tentativa. Em outras palavras, quando o Colaborador é excluído logicamente ocorrem duas coisas. A primeira, é subtrair o valor de Colaborador do número mágico na tabela Pessoa. A segunda, é setar o flag de exclusão (no meu caso, um campo de timestamp) na tabela Colaborador. Então, quando a busca de colaboradores é feita, não é encontrado o registro 123 no retorno. Porém, quando alguém tentar incluir um colaborador com esse código, o sistema vai gerar um conflito, informando para o usuário que esse número já existe. Daí, o usuário podem optar por reativar o perfil Colaborador para esse registro, caso em que o número mágico é incrementado novamente.

3. Conclusões

A solução descrita acima está em uso e mostra-se como muito performática. Claro, quando a busca do precisa ter filtros que sejam colunas declaradas nas tabelas-filha não tem como tem fugir da junção.

Para isso, em conjunto com os analistas, temos um trabalho de "elevar" essas colunas para a tabela-mãe. São coisas clássicas, como nome, cpf, cnpj e outros campos identificadores comuns às tabelas-filha e que tenham boas chances (ou heurísticas) de serem usado frequentemente nas pesquisas. Certo, nesse caso abrimos espaço para os buracos com valores nulos, uma vez que numa linha de registro nem todos os campos estariam presentes. É a historinha das dimensões acima...

Bom, nesse post tentei dar uma idéia de uma solução interessante para busca de estruturas com herança armazenadas em bancos relacionais.

Antes de terminar, porém, exponho o comentário do meu amigo Corvalis (hic), que disse que a própria estrutura de pessoa que usamos nesse sistema não é compilante com com um bom mapeamento objeto-relacional e não poderia ser migrada para um Hibernate ou JPA. Disse à ele que nesse caso não tem problema, pois o arquiteto do projeto optou por não usar essas tecnologias e, portanto, não é prioridade nossa (ainda) se preocupar com isso.

Assim, dá-lhe CHMOD nas tabelas, hehehe.