domain model - faculdades integradas do vale do ivaí · e arquitetura de software, criatividade e...

9
9 Uma Forma Mais Eficiente de Construir Aplicações Enterprise Roberto Perillo ([email protected]) é bacharel em Ciência da Computação e está atualmente cursando mestrado no ITA, onde já concluiu o curso de especialização em Engenharia de Software. Trabalha com Java há mais de 5 anos, possui as certificações SCJP, SCWCD, SCJD e SCBCD, e é moderador no JavaRanch, onde participa ativamente dos fóruns. Já trabalhou com JEE em grandes empresas, como Avaya e IBM, onde, nesta última, foi co-fundador do time de inovação de ibm.com GWPS LA. Atualmente, trabalha na BoldCron Technologies (uma empresa do grupo UOL) como Senior Java Developer. Construir aplicações envolve, além de experiência com uma determinada linguagem e arquitetura de software, criatividade e bom senso. Esses talvez sejam os aspectos mais importantes para se criar um modelo de domínio que reflita adequadamente as necessidades a serem contempladas pelo software. A implementação deste modelo requer um estilo de arquitetura diferente, que pode ser alcançado com o padrão arquitetural Domain Model. Este padrão propõe criar uma estrutura de objetos que utiliza plenamente todas as forças da orientação a objetos, o que torna a arquitetura da aplicação mais flexível e fácil de manter e evoluir com o passar do tempo. Este artigo mostra e exemplifica como o padrão Domain Model pode ser implementado, ilustrando os benefícios que podem ser obtidos com sua utilização. Domain Model: tualmente, a comunidade de desenvolvimento aceita que, ao se desenvolver software, a verdadeira complexi- dade está, na maioria das vezes, no domínio com o qual a aplicação deve lidar. A abordagem conhecida como Domain-Dri- ven Design provê uma série de práticas que ajudam a desenvolver softwares para domínios complexos. O principal produto dessa abordagem é o modelo de domínio, que representa uma abstração do problema a ser representado e solucionado pelo software. Em termos de código, a manifestação desse modelo se dá na camada de domínio da aplicação. Para a implementação desse modelo, o padrão Domain Model pode ser usado. Ele sugere uma estrutura de objetos que permite que o modelo seja implementado e refleti- do no código, o que permite tirar proveito de todos os benefícios da orientação a objetos, tornando assim a arquitetura da aplicação mais flexível e fácil de manter e evoluir com o passar do tempo. Essa abordagem também oferece mais reúso, pois os componentes não são semanticamente acoplados a nenhum framework, já que são POJOs. No entanto, pode ser necessário utilizar anotações de frameworks para que algum objetivo seja alcançado. Nesse caso, as anotações de domínio podem ser utilizadas para manter a im- plementação livre de frameworks externos. O objetivo deste artigo é introduzir o padrão Domain Model como uma forma mais efetiva de se construir aplicações enterprise, e está organizado da seguinte

Upload: dotuong

Post on 09-Dec-2018

214 views

Category:

Documents


0 download

TRANSCRIPT

9

Uma Forma Mais E�ciente de

Construir Aplicações Enterprise

Roberto Perillo

([email protected]) é bacharel em Ciência da Computação e está atualmente cursando mestrado no ITA, onde já concluiu o curso de especialização em Engenharia de Software. Trabalha com Java há mais de 5 anos, possui as certificações SCJP, SCWCD, SCJD e SCBCD, e é moderador no JavaRanch, onde participa ativamente dos fóruns. Já trabalhou com JEE em grandes empresas, como Avaya e IBM, onde, nesta última, foi co-fundador do time de inovação de ibm.com GWPS LA. Atualmente, trabalha na BoldCron Technologies (uma empresa do grupo UOL) como Senior Java Developer.

Construir aplicações envolve, além de experiência com uma determinada linguagem e arquitetura de software, criatividade e bom senso. Esses talvez sejam os aspectos mais importantes para se criar um modelo de domínio que reflita adequadamente as necessidades a serem contempladas pelo software. A implementação deste modelo requer um estilo de arquitetura diferente, que pode ser alcançado com o padrão arquitetural Domain Model. Este padrão propõe criar uma estrutura de objetos que utiliza plenamente todas as forças da orientação a objetos, o que torna a arquitetura da aplicação mais flexível e fácil de manter e evoluir com o passar do tempo. Este artigo mostra e exemplifica como o padrão Domain Model pode ser implementado, ilustrando os benefícios que podem ser obtidos com sua utilização.

Domain Model:

tualmente, a comunidade de desenvolvimento aceita que, ao se desenvolver software, a verdadeira complexi-dade está, na maioria das vezes, no domínio com o qual

a aplicação deve lidar. A abordagem conhecida como Domain-Dri-ven Design provê uma série de práticas que ajudam a desenvolver softwares para domínios complexos. O principal produto dessa abordagem é o modelo de domínio, que representa uma abstração do problema a ser representado e solucionado pelo software. Em termos de código, a manifestação desse modelo se dá na camada de domínio da aplicação. Para a implementação desse modelo, o padrão Domain Model pode ser usado. Ele sugere uma estrutura de objetos que permite que o modelo seja implementado e refleti-do no código, o que permite tirar proveito de todos os benefícios da orientação a objetos, tornando assim a arquitetura da aplicação mais flexível e fácil de manter e evoluir com o passar do tempo. Essa abordagem também oferece mais reúso, pois os componentes não são semanticamente acoplados a nenhum framework, já que são POJOs. No entanto, pode ser necessário utilizar anotações de frameworks para que algum objetivo seja alcançado. Nesse caso, as anotações de domínio podem ser utilizadas para manter a im-plementação livre de frameworks externos. O objetivo deste artigo é introduzir o padrão Domain Model como uma forma mais efetiva de se construir aplicações enterprise, e está organizado da seguinte

10

Para Saber Mais

Uma anotação de domínio é uma anotação criada especifi-camente para um domínio e substitui as anotações de fra-meworks que são externos ao domínio. Isso permite manter a camada de domínio da aplicação isolada, o que facilita a evolução do software e aumenta o reúso dos componentes de negócio. Naturalmente, esse tipo de anotação requer tra-tamento especial, que pode ser aplicado com uma ferramenta como o Daileon, por exemplo. Consulte o artigo “Domain Annotations: Anotações Mais Próximas ao Domínio da Sua Aplicação”, publicado na edição 41 da revista MundoJ, para saber mais sobre anotações de domínio e a ferramenta Dai-leon.

Design Orientado a Objetos versus Design Procedural

Nas últimas duas décadas, a comunidade de desenvolvimento evo-luiu a tal ponto que as pessoas passaram a entender que a verdadeira complexidade, ao se desenvolver software, está na verdade no domí-nio no qual o programa será aplicado. Embora possam existir outras dificuldades, relacionadas com a infraestrutura de desenvolvimento, por exemplo, o foco principal dos esforços de desenvolvimento deve se centrar no domínio e em sua lógica. Com a introdução de tecno-logias como EJB 2, que promoviam projetos em estilo procedural, muitas das vantagens que a orientação a objetos oferece se perderam. Além disso, a complexidade introduzida no ciclo de desenvolvimen-to tornou a confecção do código mais complexa do que o próprio entendimento do domínio em si. Diante deste cenário, muitos foram os esforços que buscaram simplificar o processo de desenvolvimento, concentrando a maior parte dos esforços no entendimento do domí-nio no qual a aplicação é aplicada. Um exemplo disto é a abordagem conhecida como Domain-Driven Design, que é um conjunto de técnicas e práticas que ajudam a desenvolver aplicações para domí-nios complexos, essencialmente conectando implementação a um modelo que evolui constantemente. Uma das práticas promovidas por essa abordagem é a técnica chamada de modelagem de domínio, que promove uma abstração do domínio através de um modelo que contempla os aspectos relevantes ao desenvolvimento da aplicação, separando assim domínio de outros interesses, como infraestrutura, por exemplo.

O modelo não é um diagrama, e pode assim ser representado por praticamente qualquer coisa, desde um desenho a um diagrama UML. Em termos de código, a manifestação desse modelo se dá na camada de domínio, que constitui o “coração” do software. Para que se alcance mais reúso, menos acoplamento e mais coesão, a camada de domínio deve ficar o mais isolada possível das outras camadas sugeridas pelo DDD (como apresentação, aplicação e infraestrutura) e deve ficar o mais livre possível de implementações específicas, como um determinado banco de dados sendo acessado por uma API espe-cífica, por exemplo. O principal motivo para se isolar a camada de domínio das outras camadas é a evolução do software. Dessa forma, torna-se mais fácil manter e evoluir o software com o passar do tem-po. De acordo com a arquitetura Java EE, os componentes que lidam com a lógica de negócios em uma aplicação enterprise são os EJBs, o que implica na utilização de um Application Server (os chamados heavyweight containers, como o JBoss ou o IBM Websphere, por exemplo). O grande benefício da utilização de EJBs é que containers Java EE permitem que muitos requisitos não-funcionais presentes na maioria das aplicações enterprise sejam atendidos de forma fácil. Por exemplo, a partir da versão 3 da especificação EJB, tornou-se conside-ravelmente fácil tornar um método transacional e seguro através das anotações @TransactionAttribute e @RolesAllowed. Ironicamente, os EJBs (mesmo na versão 3) promovem um estilo de programação mais procedural, pois ao invés de se criar um modelo de objetos no qual as classes possuem tanto estado quanto comportamento e colaboram umas com as outras para organizar a lógica de negócios, criam-se clas-ses de granularidade grossa que normalmente não possuem estado e se utilizam de DTOs (objetos “burros” que não possuem nenhuma inteligência e espelham a estrutura das tabelas da base de dados) para transportar dados entre as camadas. Cada método dessas classes de negócio existe normalmente para tratar uma requisição da camada de apresentação, o que caracteriza o padrão Transaction Script, que é uma abordagem mais procedural. Como consequência, construir, testar, manter e evoluir a aplicação pode se tornar cada vez mais com-plexo com o passar do tempo.

Em contrapartida, o padrão Domain Model propõe organizar a lógica de negócios em um modelo de objetos que se utiliza de todos os be-nefícios da orientação a objetos, em que as classes nada mais são do que POJOs, isto é, classes simples que não estendem nenhuma classe de uma API específica. Assim, o código reflete efetivamente o modelo de domínio construído e, dessa forma, torna-se fácil manter e evoluir a aplicação com o passar do tempo. No entanto, POJOs por si só não são suficientes em uma aplicação enterprise, pois é necessário imple-mentar requisitos não-funcionais na maioria dos casos, como tran-sacionalidade ou segurança, por exemplo. Para isso, pode-se utilizar o framework Spring, no qual se consegue suprir essas necessidades facilmente através das funcionalidades de injeção de dependência e AOP, fornecidas pelo próprio Spring.

Graças às funcionalidades oferecidas pelo Spring, torna-se fácil utili-zar o padrão Domain Model com POJOs. No entanto, a utilização do padrão não depende especificamente do Spring. De fato, é possível utilizar o padrão em uma aplicação instalada em um container Java EE. É possível, inclusive, utilizar Stateless Session Beans, em que es-tes podem desempenhar o papel de facades, servicos e repositórios, abordados mais a frente neste artigo. Entretanto, além de algumas li-mitações, a aplicação nesse caso fica dependente de um container Java EE e o mais natural seria utilizar uma abordagem mais procedural, na qual os Session Beans são os componentes de negócio.

forma: primeiramente, é apresentada uma breve discussão sobre design orientado a objetos versus design procedural. Em seguida, é dada uma visão geral sobre modelagem de domínio, cujo resultado é o modelo de domínio a ser implementado. Logo após, o padrão Domain Model é apresentado. Após isso, é mostrado um modelo simplificado do domínio de comércio eletrônico e como ele pode ser implementado. E, finalmente, são apresentadas as considerações finais, finalizando o presente artigo.

Este artigo é inspirado nas práticas apresentadas no excelente livro POJOs In Action, escrito por Chris Richardson. Para uma leitura completa e com mais detalhes sobre o padrão Domain Model, recomenda-se a leitura desta obra.

: : www.mundoj.com.br : :

11

Modelagem de domínio

Atualmente, a comunidade de desenvolvimento de software aceita que a real complexidade, na maioria dos projetos de software, está no domínio no qual o software é aplicado. A modelagem de domínio, que é uma das práticas incentivadas pelo Domain-Driven Design, ajuda a lidar com essa complexidade. Em termos práticos, modelo pode ser definido como um sistema de abstrações que descreve aspectos selecio-nados de um domínio e podem ser utilizados para resolver problemas relacionados a esse domínio. Domínio pode ser definido como uma esfera de conhecimento, influência ou atividade. A área onde o usuário aplica o programa é o domínio do software. Por exemplo, para uma aplicação bancária, o domínio é a esfera de conhecimento bancário. Assim, modelo de domínio pode ser definido como uma abstração ri-gorosa e selecionada do conhecimento que os especialistas no domínio possuem, e embora seja somente uma abstração, é comumente repre-sentado através do diagrama de classes da UML. O modelo de domínio é o maior produto do Domain-Driven Design, que nada mais é do que um conjunto de técnicas e práticas que auxiliam no desenvolvimento de software para domínios complexos. É também uma forma de pensar e organizar prioridades, buscando acelerar projetos de software que lidam com domínios complexos. O resultado da modelagem de domí-nio é uma abstração cujos limites são os aspectos relevantes à resolução do problema através da construção do novo software. A evolução ou refinamento desse modelo se dá incluindo elementos que sejam impor-tantes para a resolução do problema ou excluindo elementos que não sejam relevantes. Em termos de código, a manifestação desse modelo se dá na camada de domínio da aplicação. No entanto, como a lógica de negócios é na maior parte concentrada nas entidades de domínio, a implementação desse modelo requer um estilo de arquitetura diferente. Para essa implementação, o padrão Domain Model pode ser utilizado. Ele sugere uma estrutura de objetos que permite organizar o mode-lo de domínio e oferecer as funcionalidades implementadas de uma forma totalmente orientada a objetos, incluindo extensibilidade e fácil manutenção.

O Padrão Domain Model

O padrão Domain Model sugere uma estrutura que implementa um modelo de domínio através de um modelo de objetos onde é possível se utilizar de todos os benefícios da orientação a objetos. De acordo com o Domain-Driven Design, a camada de domínio deve ficar o mais isolada possível do resto da aplicação. Através do padrão Domain Model, pode-se criar uma estrutura de objetos de forma a isolar consideravelmente a camada de domínio. No entanto, existem algumas situações nas quais a camada de domínio acaba inevitavel-mente sofrendo efeitos externos para que um determinado objetivo seja alcançado. Caso se trate de anotações externas ao domínio, o conceito de anotações de domínio, abordado na edição passada da revista, pode ser aplicado. As classes de domínio são invocadas direta ou indiretamente pela camada de apresentação e o tratamento de cada requisição resulta na chamada de uma ou mais entidades, que validam os dados fornecidos pelo usuário, realizam computações, lidam com a lógica de negócios e acessam o banco de dados através de repositórios. A seguir, com a estrutura sugerida por esse padrão torna-se fácil desenvolver e manter a aplicação, pois as responsabili-dades são distribuídas em classes mais coesas e de menor granulari-dade. Como as classes que implementam o modelo de domínio são POJOs, pode-se utilizar de todos os benefícios da orientação a objetos e utilizar sem nenhuma restrição qualquer padrão GoF.

Ao utilizar essa abordagem, é possível testar a aplicação mais facil-mente, pois não é necessário instalá-la em um Application Server. Um modelo de domínio deve refletir a abstração do domínio no qual o software é aplicado da forma mais adequada possível. Pode-se categorizar as classes que implementam esse modelo de acordo com o papel que cada classe desempenha. Cada papel implica em certas responsabilidades e relacionamentos com outras classes que desem-penham outros papéis. Dessa forma, definir o papel de cada classe facilita o projeto da implementação do modelo de domínio.

Os chamados building blocks, ou classes que implementam o mode-lo de domínio, podem ser categorizados em:

-rencia dos demais objetos. Por exemplo, uma classe Pessoa pode-ria ter um atributo representando seu CPF. As entidades represen-tam conceitos do domínio e concentram a maior parte da lógica implementada pela aplicação.

seus atributos e frequentemente são imutáveis, o que significa que uma vez criados não podem ser alterados. Na prática, exis-tem somente para compor entidades e podem ser o resultado do agrupamento de atributos duplicados em mais de uma entidade ou que fazem mais sentido ficarem separados das entidades para deixá-las com uma granularidade menor. Além disso, a literatura antiga de J2EE se referia a DTOs (padrão do catálogo Core J2EE Patterns) como value objects, cujo objetivo é somente transportar dados entre as camadas (normalmente, da camada de negócios para a camada de apresentação). No entanto, a diferença entre esses objetos é que um DTO é um objeto “burro” que possui so-mente campos e métodos de acesso e os value objects são objetos que fazem sentido em um domínio.

-tros objetos. Dessa forma, o mecanismo de criação de objetos fica encapsulado, promovendo assim mais flexibilidade. As fábricas também são bastante úteis quando o grafo do objeto a ser instan-ciado é complexo. Separar o mecanismo de criação desses objetos permite que o código que implementa as regras de negócio fique mais fácil de se ler e manter. No entanto, o maior benefício da utilização desse tipo de objeto é alcançado ao se utilizar polimor-fismo. Dessa forma, o código que utiliza a fábrica pode referenciar uma superclasse ou interface e atribuir a essa referência o objeto retornado pela fábrica, sendo que o tipo retornado pela fábrica pode variar sem precisar alterar o código cliente. Em termos de padrões de projeto, existem dois tipos de fábricas, chamados de Factory Method e Abstract Factory, que podem ser utilizados nes-sas fábricas nos contextos em que forem adequados.

definem métodos para criá-las, alterá-las, excluí-las ou encontrá-las. Repositórios encapsulam frameworks de persistência, tornando assim o mecanismo de persistência transparente para o restante da camada de domínio. Um repositório é abstraído em uma interface e pode possuir uma ou mais classes de implementação. A interfa-ce define os métodos que o repositório oferecerá a seus clientes, enquanto as classes de implementação utilizam diretamente os fra-meworks de persistência. O que difere um repositório de um DAO (padrão do catálogo Core J2EE Patterns) é uma linha tênue, mais relacionada com o objetivo de cada um: um repositório gerencia coleções de entidades em um modelo de domínio e oferece méto-dos que têm relação direta com o domínio, enquanto um DAO visa apenas a encapsular o acesso a dados.

-

12

veem funcionalidade ao modelo de domínio. Normalmente, um serviço possui uma interface e uma implementação. É invocado pelo código cliente, que pode ser ou a camada de apresentação ou um Façade que envolve a camada de domínio, recupera enti-dades através de um repositório e delega a execução das tarefas a essas entidades. Como os serviços organizam o fluxo de trabalho da aplicação, eles normalmente possuem as responsabilidades encontradas nos casos de uso. De certa forma, se assemelham aos Application Services (padrão do catálogo Core J2EE Patterns), pois podem também conter lógica que não pode ser atribuída a uma entidade.

A combinação dessas classes resulta na implementação do modelo de domínio, e essa implementação corresponde ao “M” do padrão arqui-tetural MVC. Por exemplo, consideremos uma aplicação na qual um Front Controller receba todas as requisições, que delega a descoberta do componente que tratará uma requisição específica a um Appli-cation Controller. Este Application Controller delega o tratamento da requisição ao modelo e, após o tratamento, redireciona o usuário à tela apropriada. Dessa forma, têm-se os Controllers recebendo a requisição e delegando seu tratamento ao código que reflete o modelo de domínio, e após o tratamento, o Application Controller redirecio-nando o usuário à tela apropriada. Além das entidades, value objects, fábricas, repositórios e serviços, existe um sexto tipo de objeto que compõe o modelo de domínio e não é citado por Eric Evans no livro de Domain-Driven Design ou por Chris Richardson no livro POJOs In Action. Hoje em dia, Eric Evans reconhece que deveria ter incluído em sua excelente obra os domain events, ou eventos de domínio. Um evento de domínio é um acontecimento que é de interesse dos domain experts. Por exemplo, no modelo abordado por este artigo, o fechamento de um pedido é um evento de domínio.

Um modelo de domínio fictício implementado

Consideremos a representação simplificada do modelo de domínio apresentada a seguir, referente ao domínio de comércio eletrônico. Em um cenário real, o modelo de domínio deve ser criado com os “domain experts”. Embora possa parecer fácil, essa é uma tarefa extremamente difícil, pois o responsável por essa modelagem deve buscar extrair o conhecimento do domínio da forma mais adequa-da possível, para que o modelo contemple os aspectos relevantes à resolução do problema.

Figura 1. Representação simplificada do modelo de domínio de comércio eletrônico.

Definindo métodos de serviço

Para definir os métodos de serviço, é necessário identificar as requi-sições que deverão ser tratadas pela aplicação, avaliar os parâmetros que serão recebidos e o que deverá ser retornado para o cliente do modelo de domínio. Uma forma fácil de definir serviços é criar uma interface e uma implementação por caso de uso ou “user story”.

O modelo ilustrado na figura 2 inclui algumas classes e métodos necessários para organizar o fluxo da aplicação e permitir que o modelo lide com a lógica de negócios e trate as requisições dos usuários. Além disso, o modelo também inclui interfaces de reposi-tórios que lidam com as entidades persistentes.

No modelo apresentado na figura 1, uma categoria pode ter ne-nhuma ou muitas subcategorias. As categorias são compostas de produtos, que podem ter muitos produtos relacionados. Um item de carrinho de compras tem um produto. Dessa forma, pode-se manter os dados referentes ao carrinho de compras (como quan-tidade, por exemplo) separados dos produtos em si. Um pedido possui um ou muitos itens de carrinho de compras, pode possuir até um cupom de desconto, um endereço de entrega, dados de pagamento, status referente à condição atual do pedido e sempre pertence a um cliente.

O modelo de domínio pode se parecer com o esquema de ta-belas do banco de dados e grande parte das classes do modelo de domínio normalmente é persistente. Por essa razão, muitas vezes modelagem de dados também é referenciada como mo-delagem de domínio. Obviamente, o modelo pode conter ele-mentos que não podem ser representados no banco de dados, como herança, por exemplo.

Após a modelagem inicial, é preciso adicionar comportamento ao modelo, ou seja, atribuir as responsabilidades às classes do modelo. Uma responsabilidade é tudo que uma classe conhece ou faz. Para descobrir os métodos e efetuar essa atribuição, pode-se utilizar as seguintes técnicas em conjunto:

-do-se os requisitos (através de casos de uso ou “user stories”) ou projeto de interface do usuário;

-mínio deve expor para permitir que a camada de apresentação ou facades que envolvem a camada de domínio (que serão discutidas posteriormente) tratem dessas requisições.

Assim, pode-se utilizar estas técnicas, considerar como as entida-des agem no mundo real e atribuir as responsabilidades a elas. Por exemplo, no modelo ilustrado na figura 1, uma responsabilidade que pode ser atribuída à entidade Pedido é o fechamento do pedi-do, que insere no banco de dados um novo pedido com o status “aguardando pagamento”.

Para organizar o fluxo da aplicação, é necessário analisar as requi-sições que a aplicação deverá tratar e definir métodos baseados nessas requisições. Os clientes do modelo de domínio são normal-mente facades que o encapsulam ou a camada de apresentação. Os métodos que organizam o fluxo da aplicação são os métodos de serviço.

: : www.mundoj.com.br : :

13

Figura 2. Modelo de domínio de comércio eletrônico com elementos do Domain Model.

Para ilustrar como o modelo de domínio pode tratar requisições dos usuários, consideremos a situação na qual o usuário está navegando pelo site e deseja adicionar um produto ao carrinho de compras. O objeto do carrinho de compras pode ficar na sessão do usuário para que o estado conversacional entre cliente e servidor seja mantido ao longo das requisições. Pode-se definir uma classe de serviços para tratar as requisições referentes às operações do carrinho de compras e lidar com cada requisição. Como o objeto do carrinho de compras fica armazenado na sessão do usuário, ele deve ser fornecido para o modelo pela camada de apresentação. Um Front Controller deve somente receber requisições e pode delegar a descoberta do compo-nente que tratará uma requisição específica a um Application Con-troller. Dessa forma, a tarefa de instanciar um carrinho de compras caso não exista um na sessão do usuário e fornecê-lo para o modelo não se encaixa em nenhum desses componentes. Uma opção é criar uma classe similar a um Application Service, que seria uma camada entre o modelo e os Controllers cujo objetivo seria impedir que o modelo se acople à camada de apresentação, pois é necessário lidar com os objetos HttpServletRequest e HttpSession, já que o carrinho de compras fica na sessão do usuário.

A Listagem 1 apresenta parcialmente uma possível implementação para a interface ServicosCarrinhoCompras, incluída no modelo ilustrado na figura 2.

Listagem 1. Implementação parcial da interface Servi-cosCarrinhoCompras.

package br.com.mj.ec.domain;

public class ServicosCarrinhoComprasDefault

implements ServicosCarrinhoCompras {

private RepositorioProdutos repositorioProdutos;

public ServicosCarrinhoComprasDefault(

RepositorioProdutos repositorioProdutos) {

super();

this.repositorioProdutos = repositorioProdutos;

}

public void adicionarItem(CarrinhoCompras carrinho, long idItem,

int quantidade) {

Produto produto = repositorioProdutos.encontrarProduto(idItem);

ItemCarrinhoCompras item = criarItemCarrinhoCompras(produto);

item.setQuantidade(quantidade);

carrinho.adicionarItem(item);

}

public void alterarQuantidade(CarrinhoCompras carrinho, long idItem,

int quantidade) {

Produto produto = repositorioProdutos.encontrarProduto(idItem);

ItemCarrinhoCompras item = criarItemCarrinhoCompras(produto);

carrinho.alterarQuantidade(item, quantidade);

}

public void removerItem(CarrinhoCompras carrinho, long idItem) {

Produto produto = repositorioProdutos.encontrarProduto(idItem);

ItemCarrinhoCompras item = criarItemCarrinhoCompras(produto);

carrinho.removerItem(item);

}

// Implementação dos outros métodos omitida...

}

Através da implementação parcial da interface ServicosCarrinho-Compras, pode-se perceber que o objetivo de um método de ser-viço é receber as requisições dos clientes do modelo de domínio, recuperar entidades através de um repositório e delegar a execução das tarefas a essas entidades, organizando assim o fluxo da apli-cação. Como a camada de apresentação já possui uma referência ao objeto do carrinho de compras, não é necessário retorná-lo em nenhum dos métodos. Em um caso onde seja necessário retornar valores, pode-se retornar os objetos de domínio ou criar novos ob-jetos contendo os dados requeridos pelo cliente do serviço. No caso do método finalizarPedido(), da interface ServicoCarrinhoCom-pras, é retornado um objeto Pedido, contendo os dados a serem exibidos na tela de fechamento de pedido. A Listagem 2 apresenta a implementação da classe CarrinhoCompras, utilizada pela classe ServicosCarrinhoComprasDefault.

Repositórios

Um repositório abstrai um framework de persistência através de uma interface. Pode-se utilizar JDBC diretamente, entretanto, uma apli-cação enterprise envolve muitos comandos SQL, e mantê-los pode consumir muito tempo. Assim, uma abordagem mais prática é a utilização frameworks de persistência que usam mapeamento objeto-relacional, como o Hibernate, por exemplo. As classes persistentes devem ser mapeadas para que possam ser persistidas. Pode-se tam-bém utilizar a Java Persistence API, que pode ser utilizada dentro ou fora de um container Java EE. Nesse caso, os repositórios utilizariam um EntityManager. A Listagem 3 apresenta a classe RepositorioPro-dutosHibernate, que é uma possível implementação da interface RepositorioProdutos utilizando o framework Hibernate. Essa imple-mentação estende uma classe utilitária do Spring chamada Hiberna-teDaoSupport, que oferece vários métodos utilitários que facilitam a implementação do repositório, como save, load, find, flush etc. Em uma aplicação enterprise, colaborações envolvendo vários objetos são inerentes. No exemplo mostrado na Listagem 1, um objeto da classe ServicosCarrinhoComprasDefault depende de uma implementação da interface RepositorioProdutos. Um objeto que implementa essa interface poderia ser instanciado dentro da classe ServicosCarrinho-ComprasDefault e então utilizado. No entanto, uma abordagem que proporciona mais flexibilidade é definir o arranjo de objetos utilizan-do-se de injeção de dependência, fornecida pelo próprio Spring. No

Listagem 4. Arranjo de objetos de domínio com o Spring.

Listagem 3. Implementação da interface Reposito-rioProdutos utilizando Hibernate.

<bean id=”hibernateTemplate”

class=”org.springframework.orm.hibernate3.HibernateTemplate”>

<property name=”sessionFactory” ref=”sessionFactory” />

</bean>

<bean id=”repositorioProdutos”

class=”br.com.mj.ec.domain.RepositorioProdutosHibernate”>

<constructor-arg ref=”hibernateTemplate” />

</bean>

<bean id=”servicosCarrinhoCompras”

class=”br.com.mj.ec.domain.ServicosCarrinhoComprasDefault”>

<constructor-arg ref=”repositorioProdutos” />

</bean>

package br.com.mj.ec.domain;

// imports omitidos…

public class RepositorioProdutosHibernate

extends HibernateDaoSupport implements RepositorioProdutos {

public RepositorioProdutosHibernate(HibernateTemplate template) {

super();

setHibernateTemplate(template);

}

public Produto encontrarProduto(int idProduto) {

return (Produto) getHibernateTemplate()

.load(Produto.class, new Integer(idProduto));

}

// Implementacao dos outros metodos omitida...

}

exemplo em questão, trata-se de injeção de dependência via construtor, que é uma das três formas possíveis de injeção de dependência. Dessa forma, pode-se trocar a implementação utilizada sem alterar o código do modelo de domínio. A Listagem 4 mostra como algumas classes do modelo de domínio abordado neste artigo podem ser arranjadas, utilizando o Spring. Para tanto, deve-se definir um arquivo XML e indicar alguns parâmetros no web.xml da aplicação, para que objetos sejam corretamente instanciados e arranjados pelo Spring quando a aplicação for iniciada. Dessa forma, quando a aplicação for iniciada, os objetos de domínio já estarão prontos para serem utilizados. No entanto, em alguns casos, não é possível tirar proveito da injeção de dependência. Não seria possível, por exemplo, injetar um objeto ins-tanciado pelo Spring em um Servlet porque Servlets são instanciados pelo web container. Nesses casos, pode-se implementar uma fábrica de objetos arranjados pelo Spring, como mostra a Listagem 5, e utilizá-la como mostra a Listagem 6.

Listagem 6. Recuperando beans através da fábrica de beans do Spring.

// Recuperando um bean do Spring utilizando a fabrica

// O nome do bean eh o ID definido no XML de definicao de

// beans do Spring

FactoryBean fabrica = SpringBeansFactory.getInstance();

String beanName = “servicosCarrinhoCompras”;

ServicosCarrinhoCompras servicos =

(ServicosCarrinhoCompras) fabrica.getBean(beanName);

Como a Listagem 1 mostra, a classe ServicosCarrinhoComprasDe-fault recebe uma instância da interface RepositorioProdutos no cons-trutor. Isso é possível porque os dois objetos serão instanciados pelo próprio Spring. No entanto, essa abordagem nem sempre funciona para entidades porque entidades são instanciadas pelo framework de persistência. É por essa razão que a entidade Pedido recebe Reposi-torioPedido no método finalizarPedido. Para testar a classe Pedido, pode-se utilizar um objeto mock, representando a interface Repo-

Listagem 2. Implementação da classe CarrinhoCompras.

package br.com.mj.ec.domain;

// imports omitidos…

public class CarrinhoCompras {

private Set<ItemCarrinhoCompras> itens =

new HashSet<ItemCarrinhoCompras>();

public void adicionarItem(ItemCarrinhoCompras item) {

itens.add(item);

}

public void removerItem(ItemCarrinhoCompras item) {

itens.remove(item);

}

public Pedido finalizarPedido() {

Pedido pedido = new Pedido();

Iterator<ItemCarrinhoCompras> it = itens.iterator();

while (it.hasNext()) {

ItemCarrinhoCompras item = it.next();

pedido.adicionarItem(item);

}

pedido.setStatusPedido(StatusPedido.PEDIDO_ABERTO);

return pedido;

}

public void alterarQuantidade(ItemCarrinhoCompras item,

int quantidade) {

Iterator<ItemCarrinhoCompras> it = itens.iterator();

while (it.hasNext()) {

ItemCarrinhoCompras itemCarrinho = it.next();

if (itemCarrinho.equals(item)) {

itemCarrinho.setQuantidade(quantidade);

break;

}

}

}

// Implementacao dos outros metodos omitida...

}

: : www.mundoj.com.br : :

14

15

Listagem 5. Código da fábrica de beans Spring e como se pode utilizá-la.

package br.com.mj.ec.util;

// imports omitidos...

public class SpringBeansFactory implements FactoryBean,

ApplicationContextAware {

private ApplicationContext context;

private static final FactoryBean INSTANCE =

new SpringBeansFactory();

public static FactoryBean getInstance() {

return INSTANCE;

}

public Object getBean(String name) {

return getContext().getBean(name);

}

public ApplicationContext getApplicationContext() {

synchronized (context) {

if (context == null) {

String path = “br/com/mj/ec/resources/spring.xml”;

context = new ClassPathXmlApplicationContext(path);

}

}

return context;

}

public void setApplicationContext(ApplicationContext context) {

this.context = context;

}

}

Listagem 7. Fachada dos serviços do carrinho de compras.

package br.com.mj.ec.domain;

// imports omitidos...

public class FacadeServicosCategoriasDefault

implements FacadeServicosCategorias {

private ServicosCategorias servicosCategorias;

private ServicosProdutos servicosProdutos;

private FabricaResultadosServicosCategorias fabrica;

public FacadeServicosCategoriasDefault(

ServicosCategorias servicosCategorias,

ServicosProdutos servicosProdutos,

FabricaResultadosServicosCategorias fabrica) {

super();

this.servicosCategorias = servicosCategorias;

this.servicosProdutos = servicosProdutos;

this.fabrica = fabrica;

}

public ResultadoFachadaCategorias visitarCategoria(int id) {

Categoria categoria = servicosCategorias.visitarCategoria(id);

Set<Produto> produtosHomeCategoria = servicosProdutos

.recuperarProdutosHomeCategoria(id);

Set<Produto> produtosPremium = servicosProdutos

.recuperarProdutosPremium();

return fabrica.criarResultadoFachadaCategorias(categoria,

produtosHomeCategoria, produtosPremium);

}

// Outros metodos requeridos pela camada

// de apresentacao omitidos...

}

Para Saber Mais

Um objeto mock é uma implementação falsa que é utilizada unicamente para testes. Utilizando interfaces, é possível, por exemplo, criar uma entidade, implementar um método que dependa de uma interface e utilizar um objeto mock para simular o funcionamento de uma possível implementação da interface. Assim, é possível focar em uma única implementa-ção por vez. Consulte o excelente artigo “Testes de Unidade Avançados Com o JMock 2”, escrito pelo meu mestre Eduar-do Guerra na edição 31 da revista MundoJ.

Assim, os serviços organizam o fluxo da aplicação, recuperando entidades por meio de repositórios e delegando a execução das tarefas a essas entidades. Mas há ainda uma questão importante, que são os requisitos não-funcionais que devem ser implementa-dos pela aplicação. Consideremos que o método finalizarPedido

Envolvendo o modelo de domínio com facades

Uma forma de encapsular o modelo de domínio é definindo facha-das, que são a porta de entrada do mundo externo para o modelo de domínio. Essas fachadas são responsáveis por tarefas de infraestrutu-ra, como delimitar transações, aplicar segurança e desanexar objetos persistentes do contexto de persistência.

Os métodos das fachadas devem ser definidos em função das necessi-dades da camada de apresentação, e normalmente, recebem as requi-

da classe Pedido deva ser transacional. Uma transação é um con-junto de operações que devem ser processadas de forma atômica, para que os recursos envolvidos (como bancos de dados, por exemplo) não fiquem em estado inconsistente. É fácil tornar um bean gerenciado pelo Spring transacional, bastando apenas escre-ver algumas linhas de XML. Uma abordagem possível é tornar os próprios beans de serviço transacionais. No entanto, teríamos nesse caso classes de domínio lidando com detalhes de infraestru-tura. Objetos persistentes também precisam ser “desanexados” do contexto de persistência antes de retornarem à camada de apre-sentação, para que não ocorram erros de conexão com o banco de dados quando suas propriedades forem acessadas. Pode-se então envolver o modelo de domínio com facades, onde esses detalhes de infraestrutura podem ser tratados.

sitorioPedido. Seguindo essa abordagem, é possível implementar e testar as entidade de domínio e implementar os repositórios somente depois que a lógica de cada entidade estiver correta.

16

Listagem 9. Tornando os métodos de uma fachada transacionais.

Listagem 8. Implementação da interface FabricaRe-sultadosServicosCategorias utilizando Hibernate.

<bean id=”hibernateTemplate”

class=”org.springframework.orm.hibernate3.HibernateTemplate”>

<property name=”sessionFactory” ref=”sessionFactory” />

</bean>

<bean id=”fabricaResultadosCategoria”

class=”br.com.mj.ec.domain.FabricaResultadosServicosCategoriasHibernate”>

<constructor-arg ref=”hibernateTemplate” />

</bean>

<bean id=”fachadaCategorias”

class=”br.com.mj.ec.domain.FacadeServicosCategoriasDefault”>

<constructor-arg ref=”servicosCategorias” />

<constructor-arg ref=”servicosProdutos” />

<constructor-arg ref=”fabricaResultadosCategoria” />

</bean>

<bean id=”transactionManager”

class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>

<property name=”sessionFactory” ref=”sessionFactory” />

</bean>

<bean id=”matchAllMethods”

class=”org.springframework.transaction.interceptor

.MatchAlwaysTransactionAttributeSource” />

<bean id=”transactionInterceptor”

class=”org.springframework.transaction.interceptor.TransactionInterceptor”>

<property name=”transactionManager”>

<ref bean=”transactionManager” />

</property>

<property name=”transactionAttributeSource”>

<ref bean=”matchAllMethods” />

</property>

</bean>

<bean id=”transactionProxyCreator”

class=”org.springframework.aop.framework.autoproxy.

BeanNameAutoProxyCreator”>

<property name=”beanNames”>

<list>

<idref bean=”fachadaCategorias” />

</list>

</property>

<property name=”interceptorNames”>

<list>

<idref bean=”transactionInterceptor” />

</list>

</property>

</bean>

package br.com.mj.ec.domain;

// imports omitidos…

public class FabricaResultadosServicosCategoriasHibernate

extends HibernateDaoSupport

implements FabricaResultadosServicosCategorias {

public FabricaResultadosServicosCategoriaHibernate(

HibernateTemplate template) {

super();

setHibernateTemplate(template);

}

public ResultadoFachadaCategorias criarResultadoFachadaCategorias(

Categoria categoria, Set<Produto> produtosHomeCategoria,

Set<Produto> produtosPremium) {

getHibernateTemplate().initialize(categoria.getProdutos());

getHibernateTemplate().initialize(produtosHomeCategoria);

getHibernateTemplate().initialize(produtosPremium);

ResultadoFachadaCategorias resultado =

new ResultadoFachadaCategorias(produtosCategoria,

produtosHomeCategoria,

produtosPremium);

return resultado;

}

// Implementacao dos outros metodos omitida...

}

fachada precisa somente assegurar que os objetos sejam carregados antes de serem retornados à camada de apresentação. Neste exem-plo, a FabricaResultadosServicosCategorias é uma interface cujas implementações inicializam os objetos de domínio utilizando a API específica de cada framework. A Listagem 8 mostra uma possível implementação da interface FabricaResultadosServicosCategorias utilizando a API do Hibernate.

Caso o carregamento dos objetos persistentes seja “preguiçoso”, estes precisarão ser carregados antes de retornarem à camada de apresenta-ção, para que quando forem acessados, não ocorram exceções devido à conexão com o banco de dados já estar fechada. Colocar o código que inicializa os objetos acoplaria a fachada ao framework de persis-tência. Uma opção é definir uma nova classe que seja responsável por desanexar os objetos que retornam para a camada de apresentação e também por criar novos objetos contendo os dados requeridos pela camada de apresentação. No caso do Hibernate, os objetos são desanexados automaticamente quando a sessão é fechada. Assim, a

sições da camada de apresentação e repassam para os serviços. Dessa forma, os métodos das fachadas normalmente possuem as mesmas assinaturas dos métodos de serviço. O retorno dos métodos pode ser o objeto de domínio com o grafo de objetos que são acessados pela camada de apresentação inicializado ou um objeto parecido com um DTO com os objetos que podem ser acessados pela camada de apre-sentação, caso o retorno do método de serviço não seja suficiente. A partir do resultado da chamada de um método de serviço, a fachada inicializa os objetos persistentes, garantindo assim que não ocorrerão exceções quando forem acessados pela camada de persistência.

Por exemplo, consideremos uma situação hipotética em que a cama-da de apresentação precisa de uma categoria, dos produtos da página inicial da categoria e de produtos “premium", que são exibidos em todas as páginas iniciais de categorias. A Listagem 7 mostra como poderia ser a fachada dos serviços de categorias.

: : www.mundoj.com.br : :

17

Normalmente, um método da fachada deve ser executado dentro de uma transação, para assegurar que o banco de dados seja atu-alizado atomicamente. Uma transação deverá ser iniciada quan-do o método da fachada for chamado e confirmar ou revertê-la ao final da execução do método. Além disso, uma conexão com o banco de dados deve ser aberta antes da execução do método e finalizada após a execução do método.

Aplicar esses recursos em um objeto instanciado pelo Spring é fácil, graças aos recursos de AOP oferecidos pelo framework. As transações podem ser locais (envolvendo somente um recurso/banco de dados) ou globais (envolvendo mais de um recurso/banco de dados). Assim, as transações devem ser configuradas com o recurso apropriado.

A Listagem 9 mostra como tornar um bean Spring transacional.

Como a Listagem 9 mostra, são definidos novos beans que envolvem os métodos da fachada com interceptadores AOP que gerenciam transações e conexões. No exemplo apresentado na Listagem 9, todos os métodos da fachada são interceptados pelo gerenciador de transações, como indica a propriedade transactionAttributeSource do bean transactionInterceptor, mas é possível definir outras formas de gerenciamento de transações. Por exemplo, assim como no EJB, é possível indicar se uma nova transação deve ser iniciada caso um método seja invocado a partir de outro método que já esteja par-ticipando de uma transação. Além disso, no exemplo da Listagem 9, é utilizada uma instância de HibernateTransactionManager, que é um gerenciador de transações específico para o Hibernate. Caso a transação seja global, pode-se utilizar uma instância de JtaTran-sactionManager.

Em relação à segurança, pode-se implementá-la na própria camada de apresentação a partir de métodos do objeto HttpServletRequest. É possível, por exemplo, identificar o usuário que está efetuando a requisição ou se ele está em um determinado papel.

No entanto, caso seja necessário implementar a segurança no mo-delo de domínio, outra alternativa é utilizar o framework Acegi Se-curity, que provê segurança para aplicações Spring. Ele provê várias opções para armazenar os usuários e seus papéis. Através de meca-nismos AOP, é possível verificar se um usuário possui autorização para chamar um método da fachada. Por exemplo, o interceptador MethodSecurityInterceptor permite verificar se o chamador está autorizado a chamar um método da fachada e levantar uma exce-ção caso o chamador não esteja autorizado. É possível configurá-lo quase da mesma forma que um TransactionInterceptor. Na defini-ção XML, é possível especificar quais papéis podem invocar cada método. Assim, o framework Acegi Security oferece várias opções para tornar a aplicação mais segura.

Considerações finais

Este artigo é baseado na excelente obra POJOs In Action, escrita por Chris Richardson, e apresenta uma introdução ao padrão Domain Model, que permite que uma aplicação enterprise possa ser implementada com POJOs, utilizando plenamente todas as forças que a orientação a objetos oferece. Para os requisitos não-funcionais que a maioria das aplicações enterprise requer, pode-se utilizar os recursos de AOP do framework Spring, que permite, por exemplo, que um método seja transacional a partir da defi-

EVANS, E. Domain-Driven Design: Tackling Complexity in The Heart of Softwa-

re. Boston: Addison-Wesley Longman, 2003.

FOWLER, Martin. Patterns of Enterprise Application Architecture. Boston: Addi-

son-Wesley Professional, 2002.

GAMMA, E. et al. Design Patterns: Elements of Reusable Object-Oriented Sof-

tware. Boston: Addison-Wesley Professional, 1994.

PERILLO, José Roberto Campos. Daileon: Uma Ferramenta Para Habilitar o Uso

de Anotações de Domínio. 2010. 68. Trabalho de Conclusão de Curso. (Lato Sensu)

– Instituto Tecnológico de Aeronáutica, São José dos Campos.

RICHARDS, Chris. POJOs In Action. Greenwich, CT: Manning Publ., 2006.

Referências

nição de um bean e injeção de transação. Dessa forma, torna-se muito mais fácil testar a aplicação e o desenvolvimento se torna mais produtivo.

A utilização do padrão Domain Model é recomendada quando a lógica de domínio é complexa e se busca reúso de componentes de domínio. No entanto, em uma mesma aplicação, o padrão Domain Model pode ser combinado com o padrão Transaction Script, no qual os transaction scripts podem lidar com a lógica simples da aplicação. Nesse caso, é recomendado que esses tran-saction scripts sejam criados separadamente do código que reflete o modelo de domínio. Para aplicações que não dispõem de um framework de persistência ou que a lógica de domínio é muito simples, o padrão Transaction Script é mais adequado.

Existem algumas situações em que EJBs são a escolha mais ade-quada (consulte o excelente artigo “EJB 3 Vs. Spring: Uma Disputa de Gigantes”, que foi capa da edição 33 da revista MundoJ, para conferir uma excelente comparação entre a plataforma EJB 3 e o framework Spring). Assim, pode-se concluir que nenhuma tec-nologia ou abordagem é a “melhor”, mas sim, uma tecnologia ou abordagem pode ser mais adequada do que outra dentro de um contexto. Além disso, frequentemente existe mais de uma escolha que pode ser considerada adequada em um cenário. A escolha da arquitetura deve ser embasada primariamente nos requisitos não-funcionais a serem atendidos pela aplicação, mas, na maioria das vezes, optar por utilizar um container leve, como o Spring, por exemplo, arranjando os objetos e os preparando para atenderem aos requisitos funcionais e não-funcionais da aplicação, em que esses objetos são POJOs que aumentam a produtividade, facilitam a construção da aplicação, flexibilizam a arquitetura e permitem que os componentes sejam testados de forma mais fácil, pode ser