Lidando com eventos dentro do Spring

No livro que escrevi  sobre o Spring MVC na Casa do Código, construo um projeto similar ao e-commerce usado na própria editora. Gosto bastante de exemplos baseados em lojas online, por mais que sejam batidos, eles possuem o necessário para discutirmos diversas aspectos dentro do desenvolvimento de um projeto. Por exemplo, quando uma compra é fechada e aprovada é necessário que algumas ações sejam tomadas: enviar um email, gravar a compra no banco de dados, disparar o processo de geração do ebook  etc.

A maneira simples resolver isso é invocando todos os métodos necessários logo após a compra ter sido aprovada.

	@RequestMapping(value = "checkout", method = RequestMethod.POST)
	public ModelAndView checkout(@AuthenticationPrincipal SystemUser user) {

			BigDecimal total = shoppingCart.getTotal();
			String uriToPay = "http://book-payment.herokuapp.com/payment";
			try {
				restTemplate.postForObject(uriToPay,
						new PaymentData(total), String.class);

                                paymentDAO.persist(new Payment(shoppingCart,user));
                                mailSender.send(...);

				return new ModelAndView("redirect:/payment/success");
			} catch (HttpClientErrorException exception) {
				return new ModelAndView("redirect:/payment/error");
			}			

	}

E se a gente precisar colocar mais um passo? E se a execução de algum dos passos envolver uma condição da compra sendo efetuada? Agora o email só é disparado caso a compra tenha um valor maior que R$ 100,00. Para todos esses casos somos obrigados a ficar alterando o trecho de código responsável, não importa a localização dele. Você pode tirar essa responsabilidade do controller e jogar para outra classe, quando um novo passo precisar ser adicionado será necessário alterar o trecho de código. Este tipo de situação em um projeto, onde um acontecimento gera a necessidade de execução de vários eventos, já é bem conhecido no mundo de desenvolvimento Orientado a Objetos e, inclusive, criaram até um Design Pattern cujo objetivo é justamente lidar com isso, o Observer. 

O pattern já é bem conhecido, tem até suporte dentro do próprio Java. Caso você queira criar uma classe que representa um evento, pode herdar de java.util.EventObject e trabalhar com ela dentro do seu código. O único ponto negativo é que com pattern ou sem pattern, você ainda é obrigado a guardar uma lista de objetos que precisam ser notificados, ou seja, o código continua nas suas mãos. Pensando nisso, alguns frameworks já fornecem a implementação deste Design Pattern para você. Aqui no post, vamos discutir como tirar proveito disso com o Spring.

O primeiro passo é trocar todo aquele código de controle sobre a execução dos eventos. Vamos simplesmente usar a classe ApplicationContext e informar que aconteceu um novo evento no sistema.

        public ModelAndView checkout(@AuthenticationPrincipal SystemUser user) {
          ...

	  restTemplate.postForObject(uriToPay,new PaymentData(total), String.class);
	  applicationContext.publishEvent(new NewPurchaseEvent(this, shoppingCart, user));
        }

O método publishEvent é o responsável por notificar todo mundo que está esperando pelo evento representado pelo objeto do tipo NewPurchaseEvent. Agora vamos dar uma olhada nessa classe.

  public class NewPurchaseEvent extends ApplicationEvent {

	/**
	 *
	 */
	private static final long serialVersionUID = 7410524290553788367L;
	private final ShoppingCart cart;
	private final SystemUser systemUser;

	public NewPurchaseEvent(Object source, ShoppingCart cart,
			SystemUser systemUser) {
		super(source);
		this.cart = cart;
		this.systemUser = systemUser;
	}

	public ShoppingCart getCart() {
		return cart;
	}

	public SystemUser getSystemUser() {
		return systemUser;
	}

  }

Perceba que ela herda de ApplicationEvent, uma classe do próprio Spring que herda de EventObject. A única diferença é que ela também adiciona um timestamp, apenas para saber quando o evento foi criado. Sempre quando você trabalhar com este modelo de programação, que também é conhecido como publish/subscriber, é importante que você tenha classes específicas que representam os eventos. Dessa forma você vai conseguir criar tratadores também bem específicos, mantendo a coesão do código. Agora que um novo evento está sendo disparado, quem deve tratá-lo?

   public class PurchaseEmailEventListener implements ApplicationListener<NewPurchaseEvent>{

	@Autowired
	private MailSender mailer;

	@Override
	public void onApplicationEvent(NewPurchaseEvent event) {
		System.out.println("Enviando email de nova compra "+event.getCart().getTotal());
		SimpleMailMessage email = new SimpleMailMessage();
		email.setFrom("compras@casadocodigo.com.br");
		email.setTo(event.getSystemUser().getLogin());
		email.setSubject("Nova compra");
		email.setText("corpodo email");
		mailer.send(email);
	}

   }

   public class PurchaseSaveDatabaseEventListener implements  ApplicationListener<NewPurchaseEvent>{

	@Override
	public void onApplicationEvent(NewPurchaseEvent event) {
		System.out.println("salvando no banco de dados");
	}

  }

A interface ApplicationListener deve ser implementada sempre que um novo tratador de evento for criado. Ela define apenas o método onApplicationEvent, que é onde você vai colocar a implementação do seu evento. Apenas para fim de curiosidade, ela herda de uma interface chamada java.util.EventListener, que já vem disponível dentro do próprio Java. A EventListener só serve para marcar que a classe é uma tratadora de eventos, esse tipo de interface é comumente chamada de interface de marcação. Quem for usar os observers do CDI pode usar ela para conseguir encontrar as classes depois.

Analisando o código, conseguimos chegar numa solução bem desacoplada. O local onde o evento é disparado não sabe nada sobre quem está tratando o mesmo. Também conseguimos adicionar novos tratadores sem precisar alterar a lógica do fechamento do pagamento. Uma última coisa que de vez em quando é solicitado é que os eventos aconteçam numa determinada ordem. Na teoria, os eventos não deviam se conhecer e, portanto, não deveria existir ordem. Só que sabemos que na prática, a teoria é outra :). Caso você se encontre nessa situação, basta que seus listeners implementem a interface org.springframework.core.Ordered.

   public class PurchaseSaveDatabaseEventListener implements ApplicationListener<NewPurchaseEvent>,Ordered{

	@Autowired
	private PaymentDAO paymentoDAO;

	@Override
	public void onApplicationEvent(NewPurchaseEvent event) {
               Payment payment = event.buildPayment();
               paymentDAO.persist(payment);
	}

	@Override
	public int getOrder() {
		return 0;
	}

   }

Eles vão ser executados começando pelo número mais baixo, nesse caso você sempre vai gravar a compra antes. A parte ruim é que se tiver muitos eventos você pode acabar se perdendo um pouco, mas de todo jeito pode ser útil para determinados cenários.

Por hoje é isso, espero que os posts estejam sendo úteis para quem está lendo. Ainda tem muitos por vir!

Advertisements

3 thoughts on “Lidando com eventos dentro do Spring

  1. Alberto, muito bom o post!

    O recurso de eventos do Spring é muito poderoso e se usado no local certo só temos a ganhar. Como você bem relatou, o problema da ordem em que os listeners são executados é problemático numa aplicação corporativa, tanto é que o pessoal do CDI está se rebolando para definir o esquema de ordenação na nova versão da spec. Outro ponto é que o listeners são executados de forma sincrona, o que nem sempre é o que desejamos; some a isso problemas transacionais e a coisa fica ainda mais complicado 🙂

    Pessoal da SpringSource está prometendo muita coisa boa para o tratamento de eventos na versão 4.2 do framework, como pode ser visto nesse artigo: https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2

    Um abraço e parabéns por blogar sobre o mundo do Spring ❤

    Like

    • Opa Rafael, as coisas novas parecem ser bem legais mesmo. Minha única preocupação é o suporte baseado em annotations. Este é típico caso que uma interface cai bem :). Sem contar o suporte que temos da IDE para buscarmos os listeners depois. Tomara que o povo lembre da interface EventListener. Também achei legal a annotation para indicar o momento que o evento deve ser executado, pensando na transação :).

      Like

  2. Muito legal esse post sobre eventos no Spring. Achei seu blog agora e gostei bastante. Estava precisando entender melhor essa parte dos eventos. Obrigado! 🙂

    por favor, continue fazendo esses posts sobre pontos especificos do spring. Está bem didático, parabens!!

    Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s