segunda-feira, 23 de novembro de 2015

Os perigos que o EAGER pode trazer para a sua aplicação (parte 1)

Olá, pessoal!

Embora o JPA tenha muito anos de estrada e inúmeros projetos já tenham nascido utilizando este útil e famoso padrão para persistência de dados, vejo com mais frequência do que eu gostaria o uso indiscriminado de um recurso simples, aparentemente até inofensivo, mas que pode ser o seu principal problema de performance no futuro.

Qual recurso? O famoso EAGER.

Acredito que uma parte significativa dos desenvolvedores utilizam o EAGER sem entender o que ele realmente faz por trás dos bastidores ou, mesmo quando sabe, não tem dimensão do impacto dele.

Sem mais delongas, vamos falar um pouco do que é o EAGER para entendermos o motivo dele ser tão perigoso.

O que é o EAGER no JPA


Para explicar como o EAGER funciona no JPA, vamos começar mostrando esta entidade Pessoa:

@Entity
public class Pessoa {
  @Id
  private Long id;
  @Column
  private String nome;
  @OneToMany
  private List<Endereco> enderecos;
  @ManyToOne
  private Endereco enderecoPrincipal;
}

E esta classe Endereco:

@Entity
public class Endereco{
  @Id
  private Long id;
  @Column
  private String logradouro;
  @Column
  private String numero;
  @ManyToOne(fetch = FetchType.LAZY)
  private Pessoa pessoa;
}

Quando fazemos uma busca pela entidade Pessoa no banco de dados, o JPA vai carregar sempre os atributo id e nome da Pessoa. Mas, quando há relacionamento entre 2 tabelas, como ocorre com enderecos (representado por uma lista de endereços da pessoa) e o enderecoPrincipal (um único endereço, o principal endereço da pessoa), existem 2 formas de obter estas as informações destas entidades no JPA:
  • Carregar a(s) entidade(s) já na busca de Pessoa, assim como ocorre com os outros atributos básicos (id e nome) de Pessoa. Este é o método EAGER.
  • Carregar a(s) entidade(s) quando ela for necessária. Ou seja, quando for solicitada. Este é o método LAZY.

No exemplo de mapeamento de entidade Pessoa e Endereco, mostrado acima, temos ambos os casos (EAGER e LAZY) com os relacionamentos entre Pessoa e Endereco.

Vamos, então, falar um pouco do método EAGER e sua relação com com os mapeamentos via anotações @OneToOne, @ManyToOne, @OneToMany e @ManyToMany.

O mapeamento *ToOne: o EAGER ninja


Silencioso e fatal, todo relacionamento no JPA do tipo *ToOne (OneToOne e ManyToOne), por padrão, é do tipo EAGER.

Isto quer dizer que sempre que ao consultar uma Pessoa no nosso exemplo, o framework que estiver usando (seja Hibernate1, OpenJPA, etc) vai trazer também o endereço principal da pessoa.

@ManyToOne // deste jeito, este cara é sempre EAGER!
public Endereco enderecoPrincipal;

Assim, se fizermos uma consulta JPQL como esta:

SELECT pessoa FROM Pessoa pessoa

E tivermos um mapeamento configurado como EAGER, conforme ocorre com o endereço principal, teremos um SQL parecido com este sendo gerado no momento da consulta JPA:

SELECT
p.id, p.nome, -- informações de pessoas
e.id, e.logradouro, e.numero -- informações de endereço
FROM Pessoa as p
JOIN Endereco e ON p.enderecoPrincipal_id = e.id;

Para entender o perigo que isto representa para o seu mapeamento de tabelas, veja a seguir este caso bem comum.

EAGER em dose dupla: o que ocorre quando um usuário é relacionado a pessoa


Considerando a classe Pessoa mostrada anteriormente, imagine que uma nova funcionalidade foi adicionada ao sistema e temos agora a entidade Usuario. Cada usuário é representado por uma pessoa, então é natural criarmos um relacionamento entre as entidades Usuario e Pessoa:

@Entity
public class Usuario{
  @Id
  private Long id;
  //outros atributos de Usuario
  @ManyToOne
  private Pessoa pessoa;
}

Temos agora mais um mapeamento @ManyToOne no sistema que, como já dissemos, é EAGER por padrão.

Agora sempre que consultarmos um inocente usuário:

SELECT usuario FROM Usuario usuario

Vamos ter a pessoa relacionada a este usuário também sendo carregada! A consulta SQL gerada seria parecida com esta:

SELECT
  u.id, u.* -- informações de Usuario
  p.id, p.nome, -- informações de Pessoa
  e.id, e.logradouro, e.numero -- informações de Endereco
FROM Usuario as u
JOIN Pessoa as p ON u.pessoa_id = p.id
JOIN Endereco e ON p.enderecoPrincipal_id = e.id;

Pode não parecer, mas temos um grande problema nascendo.

Como podem notar, quando fazemos agora uma simples busca pela entidade Usuario não temos mais um único EAGER, temos 2 EAGERs:
  • de usuario para pessoa
  • de pessoa para o endereço principal

Quando ocorrer uma consulta por Usuario, ele vai trazer sempre a Pessoa por causa do relacionamento ManyToOne (que é EAGER por padrão) que, por sua vez, vai trazer sempre o endereço principal da Pessoa (que também está com um relacionamento do tipo EAGER).

Momento de reflexão: pense agora como as várias entidades dentro de um sistema podem se relacionar assim, cada uma com várias colunas, com campos de texto de tamanhos consideráveis... Você logo chegará a conclusão que o impacto deste comportamento pode ser bem significativo. Com alguns EAGERs, você pode correr o risco de trazer uma porção de informações do banco de dados que você não vai utilizar em 90% das vezes.

Eu arrisco dizer que o uso indiscriminado de EAGER é o principal problema de performance em sistemas que usam um framework JPA.

Se isto já parece um problema sério o bastante para você, há um outro cenário comum em projetos envolvendo EAGER que tem impacto muito pior no desempenho. Mas falaremos dele no próximo texto :).


1. No Hibernate Mapping Files, que não segue a especificação JPA, todos os mapeamentos estarão configurados como LAZY por padrão, o que eu considero o comportamento mais correto. É no JPA que está especificado que os mapeamentos *ToOne devem ser EAGER por padrão e, por esta razão, o Hibernate (e demais frameworks JPA) são assim.

segunda-feira, 9 de março de 2015

O que é uma branch no Git?

Olá, pessoal!

Vamos falar hoje de como o Git organiza internamente as branches criadas no repositório. Embora soe como um assunto complicado, veremos que é muito simples e que irá esclarecer várias dúvidas sobre o próprio Git.

O Git realiza a organização dos commits por meio de uma estrutura de grafos, conforme foi dito e explicado em um texto anterior aqui no blog. Entendendo este conceito, podemos agora falar de como o Git usa esta estrutura em grafo para saber onde começa (e termina) uma branch e em qual branch o usuário está dentro de um repositório.

As branches: o que elas realmente são


Para o Git saber onde estão as branches, em qual branch o usuário está e até para criar tags dentro do repositório, o Git usa uma única coisa: ponteiros.

Uma branch no Git é um ponteiro para um commit.

Sim, é tão simples quanto isto. Agora, como ele consegue "se virar" apenas com um único ponteiro?

Imaginemos que criamos um repositório novo e fizemos o nosso primeiro commit! Como ficaria a árvore de grafos do Git? Vejamos:

  M01

Bem, isto não nos diz muita coisa ainda. Como este repositório é novo e todo repositório novo tem obrigatoriamente uma branch master1, o Git precisa saber que o M01 faz parte da branch master. Assim, o Git cria um ponteiro para o commit M01:

  M01
   |
   |
|MASTER|

O |MASTER| é o nome do ponteiro criado pelo Git para indicar qual commit faz parte da branch master.

Digamos que o usuário faz um novo commit na branch master:

  M01<---M02

O que o Git irá fazer com relação ao ponteiro |MASTER|? Vai duplicá-lo no M02? 

Não, o ponteiro de uma branch é sempre único! Ocorrerá o seguinte:

  M01<---M02
          |
          |
       |MASTER|

O que ocorreu? O Git moveu o ponteiro |MASTER| para o commit M02. Assim, o Git determina que tudo para trás do M02 pertence a branch master. Se fizermos mais 2 commits, a estrutura de grafos do repositório Git ficaria assim:

M01<---M02<---M03<---M04
                      |
                      |
                   |MASTER|

Simples assim :). 

Digamos que, agora, queremos criar uma nova branch chamada de hotfix para este repositório. Esta branch será criada a partir do último commit da branch master.

Ao criar uma nova branch chamada hotfix, teremos um novo ponteiro chamado |HOTFIX|. Veja:


                   |HOTFIX|
                      |
                      |
M01<---M02<---M03<---M04
                      |
                      |
                   |MASTER|


Temos agora 2 branches (master e hotfix) apontando para o mesmo commit M04.

Digamos que quero fazer um commit na branch hotfix. Logicamente, este commit só deve pertencer a branch hotfix e não pertencer a branch master.

Fazendo um commit M05 na branch hotfix, vamos ter o seguinte grafo:

                              |HOTFIX|
                                  |
                                  |
                             .---M05
                            /
M01<---M02<---M03<---M04<--´
                      |
                      |
                   |MASTER|


Como é possível observar, o ponteiro do master permaneceu apontando para o mesmo commit que antes, o M04. Do M04 para trás temos a branch master. O ponteiro do hotfix moveu-se para o novo commit M05. Do M05 para trás temos a branch hotfix.

Agora, vamos fazer um commit M06 na master! Como ficará o grafo? Vejamos:

                              |HOTFIX|
                                  |
                                  |
                             .---M05
                            /
M01<---M02<---M03<---M04<--´-----M06
                                  |
                                  |
                               |MASTER|

Como ocorreu com o hotfix, o ponteiro do master moveu-se para o novo commit, o M06. Tanto o M05 como o M04 tem como commit "pai" o M04. Agora do M06 para trás (observe o grafo!) temos a branch master, na qual não incluirá o commit M05, que pertence neste momento unicamente a branch hotfix.


Onde estou? Conheça o ponteiro HEAD


No exemplo anterior, mencionamos que o usuário poderia estar na branch master ou hotfix quando realizava os seus commits. Mas, como o Git sabe em qual branch o usuário está?

Agora temos na jogada um outro ponteiro, o HEAD. O HEAD nos diz onde você está dentro da estrutura de grafos de commits do Git, e ele pode apontar para uma branch, tag2 ou um commit específico.

Quando faço o comando para mudar de branch:

git checkout master

Estou alterando o ponteiro HEAD para apontar para a branch master. Temos no grafo então:

                              |HOTFIX|
                                  |
                                  |
                             .---M05
                            /
M01<---M02<---M03<---M04<--´-----M06
                                  |
                                  |
                               |MASTER|
                                  |
                                  |
                                <HEAD>

Assim como se eu fizer um:

git checkout hotfix

Estou alterando o ponteiro HEAD para a hotfix.

   
                               <HEAD>
                                  |
                                  |
                              |HOTFIX|
                                  |
                                  |
                             .---M05
                            /
M01<---M02<---M03<---M04<--´-----M06
                                  |
                                  |
                               |MASTER|

Você também pode fazer um checkout para qualquer commit específico. Se quisermos fazer um checkout para o M03:


                              |HOTFIX|
                                  |
            <HEAD>                |
               |             .---M05
               |            /
M01<---M02<---M03<---M04<--´-----M06
                                  |
                                  |
                               |MASTER|

Acredito que com este texto tenha ficado mais claro como o Git organiza-se para controlar as branches do repositório e de como ele controla em qual branch o usuário está no momento.


Nos próximos textos pretendo falar um pouco de alguns comandos interessantes (para dizer o mínimo), como o stash, squash, tag, etc.

1. O master é uma branch como qualquer outra dentro do Git.
2. A tag é usada para marcar algum commit relevante dentro do repositório (como a entrega de uma versão, por exemplo). Tal como uma branch, a tag também é um ponteiro, tanto que uma tag não pode ter o mesmo nome de uma branch já existente no repositório.

sexta-feira, 13 de fevereiro de 2015

Entendendo os JOINs no Entity Framework

Olá, pessoal!

Para quem leu os meus posts anteriores, estou agora me aprofundando na plataforma .NET e gostaria de compartilhar um pouco do que aprendi. E, como seria possível imaginar para quem veio do mundo Java com Hibernate, logo minha curiosidade voltou-se ao principal framework de persistência da plataforma. Por esta razão, venho hoje para falar um pouco do Entity Framework.

Uma dificuldade inicial que tive foi de entender como funcionavam os JOINs no Entity Framework. Não pela complexidade no entendimento em si, mas pela falta de um texto que sintetizasse bem as dúvidas de como o Entity Framework gerava os SQLs nas consultas montadas com LINQ e expressões Lambda. Como desenvolvedor, é importante eu saber como e quando o framework faz um LEFT JOIN, quando quero especificar mais de uma condição em um JOIN, como posso retornar apenas alguns valores, etc.

O JOIN, LEFT JOIN e Include


Como exemplo, vamos considerar o relacionamento entre 2 entidades: Pessoa e Endereço. Neste exemplo, uma Pessoa precisa ter um Endereço. O mapeamento seria:

modelBuilder.Entity<Pessoa>()
    .HasRequired(x => x.Endereco)
    .WithMany()
    .HasForeignKey(x => x.EnderecoID);

Podemos observar no mapeamento que especifiquei o relacionamento como HasRequired. Assim, estou dizendo ao Entity Framework que toda Pessoa deve ter, obrigatoriamente, Endereço. Ou seja, se eu fizer uma busca como esta:

var pessoas = context.Pessoas
.Include(p => p.Endereco)
.ToList();

Terei como resultado uma lista de pessoas com o endereço de cada uma 1.

Quando utilizo o "Include", o Entity Framework faz o JOIN no SQL entre Pessoa e Endereço. A consulta SQL gerada seria algo parecido com esta:

select p.*, e.* from Pessoa p
JOIN Endereco e ON p.EnderecoID = e.ID

Mas, digamos que eu não quero um JOIN, pois o Endereço na Pessoa é opcional. Há pessoas que não tem endereço e eu gostaria de trazê-las também no resultado da consulta. Se é opcional, então não usamos HasRequired, e sim HasOptional no mapeamento:

modelBuilder.Entity<Pessoa>()
    .HasOptional(x => x.Endereco)
    .WithMany()
    .HasForeignKey(x => x.EnderecoID);

Com este mapeamento, a mesma consulta anterior vai fazer um LEFT JOIN entre Pessoa e Endereço:

select p.*, e.* from Pessoa p
LEFT JOIN Endereco e ON p.EnderecoID = e.ID

Ou seja, quando fazemos o famoso "Include", o Entity Framework vai decidir se vai usar o JOIN ou LEFT JOIN olhando se o mapeamento entre as entidades está HasRequired ou HasOptional, respectivamente.

No próximo texto vou falar de como montar uma consulta com LINQ e expressões Lambda para fazer um JOIN ou LEFT JOIN caso precise "desrespeitar" o mapeamento vigente entre e entidades, mostrar como fazer um JOIN/LEFT JOIN com mais de uma condição, eager loading, lazy loading entre outras coisas. Até mais!

Bônus: use o WithMany com sabedoria


Outro comentário importante, que vai mais como um bônus, é no uso do WithMany. Já observei que muitas pessoas sempre configuram o WithMany sem pensar na razão de estarem fazendo isto, pois não é obrigatório no mapeamento. O WithMany nada mais é do que o mapeamento do "outro lado" do relacionamento. Ou seja, ainda usando nosso exemplo entre Pessoa e Endereço, poderíamos ter:

modelBuilder.Entity<Pessoa>()
    .HasOptional(x => x.Endereco)
    .WithMany(y => y.Pessoas)
    .HasForeignKey(x => x.EnderecoID);

No mapeamento acima, a entidade de Endereço tem uma lista de pessoas. Mas, digamos que para o nosso sistema não faz sentido saber quais pessoas pertencem a determinado Endereço. Neste caso, qual a vantagem de fazer o relacionamento dos 2 lados?

Nenhuma. Na verdade, há desvantagens como: poluir o código, aumentar a complexidade e até mesmo criar problemas de performance.

Sim, problemas de performance. Não pelo mapeamento em si, mas pelo risco de isto acontecer por uso indevido. Explico.

Digamos que você está em um sistema bancário que tem a entidade Movimentacao e ContaCorrente. Faz perfeitamente sentido a Movimentacao pertencer a uma ContaCorrente e ter este relacionamento mapeado no sistema. Porém, é muito perigoso o inverso: a ContaCorrente ter uma lista mapeada de Movimentacao, afinal, pode haver milhares de movimentações! Deixar algo assim facilmente "acessível" no mapeamento, podendo ser carregado na memória a qualquer instante, pode gerar sérios problemas na aplicação.

1. Para quem vem do mundo Hibernate, esta consulta mostrada tem um comportamento igual ao de um FETCH JOIN.