Thursday, December 28, 2006

Generics + reflection + hierarquias de classes... argh!

Via Urubatan, li este post no blog Caelum que mostra um exemplo (como dito no próprio post, bizarro) de um DAO genérico, com resolução 'automática' do tipo da classe persistente via reflection do parâmetro de tipo.

Numa API que eu estava projetando, eu tentei usar este tipo de truque, para evitar ter que passar a classe de parametro no construtor, mas isso quebra rapidinho, quando entram hierarquias de classes. Exemplo:
public class Dao {
private Class persistentClass;
public Dao() {
this.persistentClass = (Class)((ParameterizedType)getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
public void save(T object) {
System.out.println("Saving object of class " + persistentClass);
}
public static void main(String[] args) {
Dao dao = new Dao(){};
dao.save(new Date());
}
}
Beleza, isso funciona (imprime o tipo correto). Mas, como eu quero adicionar métodos ao DAO, eu crio uma subclasse:
public class PessoaDao extends Dao {
// ...
public static void main(String[] args) {
PessoaDao dao = new PessoaDao();
dao.save(new Pessoa());
}
}
Isso continua funcionando. Mas, com o decorrer do projeto, eu preciso criar uma subclasse mais especializada:
public class PessoaJuridicaDao extends PessoaDao {
public static void main(String[] args) {
PessoaJuridicaDao dao = new PessoaJuridicaDao();
dao.save(new PessoaJuridica());
}
}
... e isto quebra o esquema (ClassCastException), pois não há mais parâmetro de tipo na superclasse. Eu poderia deixar isto funcionando se mantivesse o parametro de tipo no PessoaDao:
public class PessoaDao extends Dao {
public static void main(String[] args) {
PessoaDao dao = new PessoaDao();
dao.save(new Pessoa());
}
}
... mas daí eu ia ter que, ou modificar todas as declarações de PessoaDao para PessoaDao, ou então ter que conviver com milhares de warnings.

Outro problema é se uma subclasse faz algo deste tipo:
public class PessoaDao extends Dao {
public Pessoa load(DOC chave) { return new Pessoa(); } //dummy
}
public class PessoaJuridicaDao extends PessoaDao {
public static void main(String[] args) {
PessoaJuridicaDao dao = new PessoaJuridicaDao();
dao.save(new PessoaJuridica());
}
}
Isto imprime "Saving object of class class TipoCnpj"!

O caso é, este é um mecanismo extremamente frágil. Creio que até seria possível recuperar o parâmetro , se percorrêssemos a hierarquia, mas será que vale a pena entrar nesse terreno arcano?

Ter que passar uma classe como parâmetro do construtor agora não parece tão ruim assim. :)

Tuesday, August 23, 2005

Simplicidade e produtividade no acesso a banco, com Spring

Exemplo de uma consulta com atualização de banco de dados, usando JDBC puro, Spring+JDBC e Spring+Hibernate:

JDBC:
public void nomeUpperCase(long id) {
Connection con = null;
PreparedStatement ps1 = null;
PreparedStatement ps2 = null;
ResultSet rs = null;
try {
con = dataSource.getConnection();
con.setAutoCommit(false);
ps1 = con.prepareStatement(
"update NOME from CLIENTES where ID = ?");
ps1.setLong(1, id);
ResultSet rs = ps1.executeQuery();
if (rs.next()) {
String nome = rs.getString("NOME");
ps2 = con.prepareStatement("update CLIENTES set NOME = ? where ID = ?");
ps2.setString(1, nome.trim().toUpperCase());
ps2.setLong(2, id);
ps2.executeUpdate();
}
con.commit();
} catch (SQLException e) {
con.rollback();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
e.printStackTrace();
}
}
if (ps2 != null) {
try {
ps2.close();
} catch (SQLException e1) {
e.printStackTrace();
}
}
if (ps1 != null) {
try {
ps1.close();
} catch (SQLException e1) {
e.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e1) {
e.printStackTrace();
}
}
}
}

Spring+JDBC:
public void nomeUpperCase(long id) {
String nome = (String) getJdbcTemplate().queryForObject(
"select NOME from CLIENTES where ID = ?",
new Object[] { new Long(id) },
new RowMapper() {
public Object mapRow(ResultSet rs) {
return rs.getString("NOME");
}
});
getJdbcTemplate().update(
"update CLIENTES set NOME = ? where ID = ?",
new Object[] { nome.trim().toUpperCase(), new Long(id) });
}

Spring+Hibernate:
public void nomeUpperCase(long id) {
Cliente c = (Cliente) getHibernateTemplate().load(Cliente.class, new Long(id));
c.setNome(c.getNome().trim().toUpperCase());
}

Preciso dizer mais alguma coisa? :)

Monday, August 22, 2005

Spring ao resgate!

Segundo seu criador, o objetivo do Spring é facilitar a criação de aplicações J2EE, reduzindo o esforço e o custo de desenvolvimento, e ao mesmo tempo melhorando a cobertura de testes e a qualidade.

Na prática, ele preenche uma lacuna, a da camada de negócio.

No início, era o MVC. Pioneiro, o Struts criou uma base para a implementação da apresentação da aplicação, incluindo controle de navegação, validação e reúso de fragmentos (Tiles). Mais tarde, vários outros frameworks para a camada de apresentação foram criados, alguns que seguiram a linha do Struts, implementando o padrão MVC, outros tentando novos modelos, como o Tapestry e o JSF, que utilizam uma abstração de componentes e eventos.

Depois, foi a vez da camada de persistência. Mapeadores O/R como o Hibernate, o OJB e o iBatis surgiram para facilitar o uso do banco de dados, oferecendo um modelo mais orientado a objetos, mesmo sobre um banco de dados relacional, além de promover a portabilidade entre bancos de dados.

Entre as camadas de apresentação e de persistência, fica a camada de negócio, ou de aplicação. É lá que a dita lógica de negócio se concentra, fazendo a ligação entre o armazenamento e a visualização/manipulação dos dados. E é nessa camada que a lógica específica da aplicação, isto é, que é mais ligada intimamente ao domínio do negócio, e não à tecnologia da implementação, se localiza.

Os outros frameworks (apresentação e persistência) resolvem problemas tecnológicos, respectivamente as limitações e complexidades das APIs Servlet e JDBC. Mas como é possível simplificar a implementação de uma camada que é constituída basicamente de lógica específica, que não pode ser generalizada e automatizada facilmente por um framework?

Bem, não é bem assim. A camada de negócio, tradicionalmente, fica longe de conter apenas lógica de negócio. Isso porque ela ainda tem que lidar com diferentes APIs, incluindo as dos frameworks de apresentação e de persistência, além de ser necessário algum tipo de gerenciamento e organização das classes. Assim, o código de negócio normalmente fica enterrado em um emaranhado de blocos try/catch/finally, para lidar com SQLExceptions, HibernateExceptions, etc. Além disso, muitas vezes os desenvolvedores se vêem na necessidade de abusar de alguns padrões de projeto, como o Abstract Factory e o Singleton. Deste modo, mais da metade do código da camada de negócio acabava lidando com aspectos tecnológicos, e não com regras negócio.

Os Enterprise JavaBeans (EJB) foram originalmente criados para fazer parte desta camada, e especificavam um modelo de componentes, com formas de acesso a recursos, controle transacional, remotabilidade e gerenciamento de ciclo de vida. Porém, a complexidade necessária para fornecer estas funcionalidades era alta, além de forçar um forte acoplamento ao container EJB com suas APIs invasivas.

O Spring surgiu para simplificar este cenário. Ele foi projetado para ser modular e o menos invasivo possível, e ao mesmo tempo flexível e poderoso. O framework foi construído sobre o conceito da Inversão de Controle (Inversion of Control, ou IoC), fazendo uso extenso de um container de injeção de dependências (Dependency Injection, ou DI), um framework simplificado de AOP, e de classes auxiliares que ajudam na integração com outros frameworks, encapsulando detalhes da tecnologia e fornecendo hooks de customização via callbacks.

Bom, o parágrafo anterior contém vários jargões, que tento explicar a seguir:
  • Inversão de Controle: Também chamado de "Princípio de Hollywood", isto é, "Don't call us, we call you". Esta é uma característica comum aos frameworks, que implementam o fluxo principal de parte do sistema, fornecendo pontos de extensão para o desenvolvedor de aplicação adicionar a lógica específica. Assim, não é a aplicação que 'chama' o framework (como funcionam as bibliotecas), mas sim o framework que 'chama' os módulos da aplicação.
  • Injeção de Dependências: Definido por Martin Fowler, este padrão descreve um modo de gerência de componentes e resolução de dependências onde as dependências dos componentes (recursos como DataSources, configurações e outros componentes) são 'injetadas' pelo container na inicialização do componente, se contrapondo ao tradicional Service Locator, onde o componente pede um recurso ao locator. Esta inversão faz com que toda a configuração seja feita no container, retirando a necessidade da criação de fábricas customizadas. Isto facilita a execução de testes (pode-se usar objetos de teste diretamente, ao invés de ter que se criar uma fábrica de objetos de teste) e promove o desacoplamento entre os componentes.
  • Callbacks: Interfaces que encapsulam uma determinada parte de um algoritmo, chamadas pela implementação concreta do algoritmo. Um exemplo no Spring seria a execução de uma query SQL: Normalmente, você teria que obter uma conexão com o banco, criaria um PreparedStatement, informaria os parâmetros, executaria a consulta, transformaria os dados do ResultSet em algum objeto ou lista, trataria as exceções e liberaria os recursos. Os 'templates' do Spring implementam toda a lógica, exceto a leitura e transformação do ResultSet. Apenas este trecho do algoritmo é implementado pelo desenvolvedor da aplicação, na forma de um callback, sem se preocupar com a execução da consulta e a liberação de recursos.
  • AOP: A programação orientada a aspectos é um outro pardigma de programação, complementar à orientação a objetos, que visa modularizar aspectos que permeiam os limites entre objetos, como gerenciamento de recursos, logging, controle de transações, etc. O Spring fornece uma biblioteca de 'componentes AOP', que simplificam o desenvolvimento, controlando coisas como conexões com o banco e transações de maneira centralizada e totalmente transparente às classes de negócio.

Sunday, May 29, 2005

Por que Java?

Bem, este texto eu escrevi em resposta a um post no JavaFree de um rapaz que acabou de entrar no curso de Ciência da Computação, sobre a escolha entre Java e C#. Como eu gostei do produto final (e gastei um bom tempo escrevendo isso), vou postar aqui, mesmo sem ter feito uma nota de abertura do blog ainda... -sigh--

Se você souber bem como projetar um sistema, tanto faz a linguagem. Um bom desenvolvedor Java pode aprender a sintaxe e as bibliotecas necessárias do C# em dois meses (já que ele é uma cópia escarrada mesmo). Claro que a produtividade só vem com o tempo e com a prática (leva tempo pra decorar a documentação das classes, né), mas nada que não se resolva com algum estudo e esforço (disso você não vao conseguir correr, não importa a linguagem). Idem pra um desenvolvedor C#.

O detalhe, que na minha opinião faz toda a diferença, é que no mundo Java há a cultura da liberdade, da qualidade, do aprendizado e do compartilhamento de conhecimento.

Por mais que o pessoal da FSF reclame, a Sun mantém o Java de forma extremamente aberta, dando poder aos usuários e desenvolvedores, ao invés de manipular a evolução dele unicamente a seu favor. Tem também a liberdade de escolha, já que o Java roda em qualquer SO que possua uma JVM. Há servidores de aplicação e IDEs livres, e a JVM é gratuita (a versão livre já está a caminho), o que possibilita o uso da plataforma a custo zero (e sem pirataria!), tanto para aprendizado, desenvolvimento e produção. Os frameworks mais usados têm o código aberto. As arquiteturas e a forma de implementação deles não é segredo pra ninguém, pelo contrário, são discutidos de forma livre e aberta, geralmente com o incentivo de seus desenvolvedores. Pode-se usar isso como ferramenta de aprendizado, pois todo o código está aí, para entendermos e aprendermos. Também há toneladas de documentação, tutoriais e exemplos na Internet, é só saber procurar, fora, é claro, a imensa base de conhecimento acumulado em fóruns como o do JavaFree, GUJ, Java.net, TheServerSide, dos próprios projetos open-source, etc.

Além disso, normalmente desenvolvedores Java consideram coisas do tipo 'SQL em JSP' um lixo, e somos encorajados a projetar a aplicação de forma bem estruturada. Patterns e frameworks fazem parte do dia-a-dia do desenvolvedor java, agregando valor e melhorando a qualidade do software (bem, muitas vezes as pessoas exageram, o que é mau, mas fazer o que...)

Também há o fator 'comunidade'. Existem dezenas de comunidades Java, regionais, nacionais e virtuais (como o nosso JavaFree!). Elas unem os desenvolvedores, ajudando-os a evoluir como profissionais.

Bem, não conheço o mundo Microsoft, nunca programei uma única linha de C# ou de ASP, então não posso dizer muita coisa sobre 'o outro lado'.

O que posso dizer é que não me arrependo de estar aqui, pois o Java me possibilitou adquirir conhecimentos que eu tenho certeza que jamais iria adquirir sendo programador C# (os cursos são caros demais!). E se algum dia eu tiver que aprender C#, eu compro um livro e depois de uns dois ou três meses, sei que terei condições de aplicar todos os conhecimentos adquiridos aqui, de forma muito mais efetiva que a maioria dos programadores ASP por aí.

Mas por enquanto, concentre-se em Estruturas de Dados e Análise de Algoritmos, e veja se sai daí desse curso sabendo alguma coisa além de uma linguagem X ou Y.

Tetsuo