Entrevista com Arjen Poutsma, commiter do Spring!

Eu estava esperando ansiosamente para escrever este post, ele contém uma rápida entrevista com um dos principais commiters do Spring, Arjen! Ele respondeu algumas perguntas que fiz sobre o mundo que nos espera com a nova versão do framework, com o suporte integral ao conceito de Reactive Programming. Já vou deixar aqui a versão em inglês, depois posto a tradução.

Alberto: Just to introduce to our readers, could you talk a little bit about you and your role in the Spring Framework?

Arjen:  My name is Arjen Poutsma. I have been working on Spring for over ten years. I started out with Spring Web Services, then worked on Spring MVC – specifically the REST features, then Spring Scala, and for the last two years I have been working on Spring Reactive, together with Brian Clozel, Sebastien Deleuze, Stephane Maldini, abd Rossen Stoyanchev.

In this project, our team has been creating a new reactive foundation for Spring. We have essentially looked at the Spring MVC module, evaluated the code therein, and rewrote those pieces we need over to a non-blocking, event-driven style. We used Reactor as a foundation for this. The resulting code lives in the new spring-web-reactive module, next to the existing spring-webmvc module. The two modules share many concepts, but do not share any code. This is because Spring Web Reactive runs on a Reactive Streams HTTP adapter layer that’s fully non-blocking and reactive all the way down to the HTTP runtime. So while Spring MVC is built for and runs on Servlet containers, Spring Web Reactive runs also on non-Servlet runtimes such as Netty.

On top of the Spring Web Reactive module, we have two programming models. One is the existing annotation-based model, with @Controller, @RequestMapping and so on. These are the exact same annotations you use in Spring Web MVC. The other model is a recently introduced functional model, with RouterFunctions and HandlerFunctions.

I’ve attached a picture which hopefully makes this clearer.

architecture

Alberto: So, we are all excited about the new version of Spring, with the Reactive support. Could you tell us how this update will affect our way of programming?

Arjen: Just to be clear: upgrading to Spring 5 alone will not require any changes any changes to your code. As with previous major version upgrades, we have made sure that Spring 5 is backward compatible with Spring 4. So if you have a Spring 4 MVC application running on a Servlet engine, you will not have to change anything.

However, if you choose to use the new Reactive features, you will have to reconsider your architecture, as there any many differences. For instance, the guarantee that a HTTP request will be handled in a single thread is no longer the case: request will be handled by many threads, each doing a piece of the work when there is need for it (i.e. backpressure). Essentially, you are switching from a model a big thread pool, where most threads are waiting (either for incoming HTTP request, or waiting on a blocking datastore) to a model with a smaller thread pool, where each thread switches between requests many times and is effectively . Fortunately, you will not have to deal with these threads yourself, as this is where Reactor comes in. Reactor allows you to define your request handling pipeline in a functional, high-level abstraction through Flux and Mono. So rather than returning a List<Person> from your controller, for instance, you would return a Flux<Person>. However, this does also mean that relying on ThreadLocals will no longer work, and there are quite a number of frameworks and libraries (including Spring) that rely on these for some of their functionality.

Alberto: A lot of frameworks and libraries do not support this style of programming, Hibernate is an example. How can we combine our most used frameworks and Reactive Programming? The stack will evolve to support? I really like Spring Data JPA and I was thinking about that.

Arjen: Indeed, another difference between a traditional architecture and a reactive one is data stores. Obviously, in Reactive environments, there is a strong preference for non-blocking, asynchronous data stores. For some data stores, this is a great fit; for others it is not. There are a couple of NoSQL data stores that do reactive support: Couchbase, and MongoDB come to mind. For other, more traditional data access APIs, such as JDBC and JPA, there is no reactive support yet, so if you rely on them, there is no other choice than to wrap these synchronous, blocking APIs, and give them access to their own thread pool. Oracle is actually working on a non-blocking version of JDBC, but details are very sparse at this point. For JPA, the question is whether a session-based model actually makes sense in a reactive world.

Alberto: Could you explain how the Reactive Programming will help our applications to scale? Is there this relation between Reactive Programming and scalibility?

Arjen: I like to compare the two programming styles with an analogy. Imagine you are eating in a restaurant, and want to order food. The moment you make your order with the waiter, he goes off to the kitchen and prepare it. When the food is done, he will come back from the kitchen to serve it to you. After that, he will look around to see if there are any other customers in need of his attention. Now imagine the owner of the restaurant wants to have more tables, i.e. he wants to scale up. If he wants to make sure every table does not have to wait for too long, he would have to hire as many waiters as he added tables, adding a lot of cost. This is the traditional, one-thread-per-request Servlet model, where the customers are HTTP clients, and the waiters represent threads.

A reactive model would operate more like a real-life restaurant, where the waiters just relay the orders to kitchen staff, and many people (i.e. threads) are working on your order before it is served to you. Looking at it this way, it becomes quite obvious why the reactive model scales better: you are using the resources available more efficiently.

That said, the reactive model does come with a certain cost. It is harder to write asynchronous code, and it is harder to debug. So Reactive Programming will definitely help *certain* application to scale; for others I would simply say that the effort is simply not worth it. There will always be many cases where imperative is just fine for the task at hand and others where reactive and non-blocking are a must. And with Spring 5, you can do both.

Espero que você tenha gostado da entrevista! Arjen foi muito solícito e acho que nos forneceu boas respostas.

Advertisements

Spring 5 e Reactive programming parte 1

Antes que você continue a ler este post, preciso deixar um aviso, ainda estou formando a minha opinião sobre o real uso do conceito da programação reativa em nosso contexto de trabalho. Por mais que eu ache super importante saber as teorias, pelo menos para mim, elas não são tão relevantes se eu não consigo encaixar no meu dia a dia. Caso eu escreva alguma coisa que você não concorde, ficarei mais do que feliz de debater na parte dos comentários :).

Dada a condição que eu gosto de tentar aplicar as teorias nos projetos que eu participo, vamos pegar um exemplo real aqui. No meu tempo extra, tenho pego alguns projetos com Rasa, inclusive já mencionei ele num post anterior, e um desses projetos é um site que centraliza a venda de outros produtos.  Basicamente, no fluxo final da compra do usuário os seguintes passos são executados:

  1. Gravo um objeto que representa a compra do usuário no banco
  2. Faço uma requisição HTTP para o sistema do produto sendo comprado, avisando do novo usuário
  3. Mando um email para o usuário informando os dados de acesso para o produto que foi adquirido

Abaixo segue um exemplo de código invocado dentro do controller:

	@RequestMapping(value = "/confirma/{idCompra}")
	public String callbackPagseguro(
			@PathVariable("idCompra") String idCompra,
			@RequestParam("code") String notificationCode,
			HttpServletRequest request) {
		
		Compra compra = tentaAcharACompraPeloIdCustomizado(idCompra);
		
		customEnv.indicaAmbientePagSeguro();

		RespostaNotificacaoAssinatura resposta = customEnv.
			getRespostaNotificacaoTransacao(notificationCode);
		
		finalizaPagamento.finaliza(compra,resposta);
		

		return "redirect:/pagseguro/fim/"+compra.getTransacaoId();
	}

E aqui temos o nosso ponto a ser resolvido. Até a invocação do método no controller, o Spring tem o controle sobre tudo. Não precisamos nem ficar imaginando o que acontece lá dentro, mas sabemos que entre chegar um request no Servlet do Spring MVC e ele invocar o método correto no seu controller, muita coisa pode acontecer, seguem alguns passos.

  1. Descoberta do classe/método que deve ser invocado
  2. Conversão de parâmetros do request para os tipos esperados pelos parâmetros
  3. Execução da validação da entrada dos dados convertidos

Depois que seu método é invocado, ele ainda tem trabalho a fazer.

  1. Caso você não use o @ResponseBody, ele vai te jogar para a view em questão
  2. Caso você use o @ResponseBody no método, ele vai pegar o retorno e serializar, usando alguma biblioteca em questão, como o Jackson.

Todos esses passos são implementados do jeito que ele quiser. Atualmente a maioria deles, pelo menos eu acho, ocorre de forma serial. Tudo é executado em apenas um core do seu servidor, mesmo que ele tenha 4, 8 ou 16 núcleos. Além disso, tudo também é implementado de forma síncrona, com um passo sempre esperando o outro, antes do fluxo evoluir.

Essa vai ser uma das grandes mudanças na próxima versão do framework! Todo o core vai ser remodelado para que o framework possa tirar proveito da super infraestrutura provida pelos servidores. Execução de código em paralelo(já provido pelos parallel streams no Java 8), assíncrono, lazy etc. A ideia é que consigamos escalar o máximo nossa app dentro de uma máquina só!

Só que para sua aplicação tirar realmente proveito desse modelo, a ideia é que ela também seja escrita seguindo a mesma ideia. Para dar suporte a tudo isso, é que um dos alicerces da próxima versão do Spring vai ser o Project Reactor. Ele traz implementado as ideias de Reactive Programming e você vai ter que gastar um tempo para aprender, se quiser embarcar na aventura :P.

Agora eu vou tentar resumir ao máximo o princípio. A ideia é que tudo seja baseado em eventos, basicamente uma implementação de um Observer. Quem já conhece o pattern, talvez fique um pouco mais confortável. Vamos pegar o mesmo fluxo que tinha escrito lá em cima e fazer usando o novo modelo.

	@RequestMapping(value = "/confirma/{idCompra}")
	public Mono<ModelAndView> callBackPagseguro(@PathVariable("idCompra") 
			String idCompra,
			@RequestParam("code") String notificationCode, 
			HttpServletRequest request) {

		Mono<Compra> eventoBuscaCompra = 
			tentaAcharACompraPeloIdCustomizado(idCompra);
		
			
		Mono<Mono<NotificacaoLiberacao>> eventoNotificaLiberacao = 
			eventoBuscaCompra.map(c -> {
				customEnv.indicaAmbientePagSeguro();
				RespostaNotificacaoAssinatura resposta = customEnv
						.getRespostaNotificacaoTransacao(notificationCode);
				return finalizaPagamento.finaliza(c, resposta);
		});
		
		
		Mono<ModelAndView> eventoGeracaoModelAndView = eventoNotificaLiberacao.
			map(notificacao -> {
				return new ModelAndView("redirect:/pagseguro/fim/");			 
			});

		return eventoGeracaoModelAndView;
	}

Antes de entrar no detalhe da implementação, o que precisamos entender é o seguinte: quando o Spring MVC chamar nosso método, ainda não vamos executar nenhuma chamada para o banco de dados, nem para o outro sistema e muito menos mandar um email. Simplesmente criamos um fluxo de eventos que devem ser executadas nessa ordem e gerar o resultado esperado.

E quem vai executar? Esse é o pulo do gato, o framework! Sei lá se ele vai executar de maneira serial, paralela, síncrona ou assíncrona. Aqui você pode pensar do mesmo jeito que já pensamos em relação a chamada do Garbage Collector. Deixamos a JVM invocar ele, porque ela sabe qual é o melhor momento! Na apresentação do Spring One(instante 21:26) é comentado justamente isso, Reactive Programming não significa, necessariamente, executar tudo de maneira assíncrona, mesmo que o manifesto até passe essa ideia.

Para fechar, vamos olhar de novo o código para tentar entender como que isso foi implementado.

	@RequestMapping(value = "/confirma/{idCompra}")
	public Mono<ModelAndView> callBackPagseguro(@PathVariable("idCompra") 
			String idCompra,
			@RequestParam("code") String notificationCode, 
			HttpServletRequest request) {

		Mono<Compra> eventoBuscaCompra = 
			tentaAcharACompraPeloIdCustomizado(idCompra);
		
			
		Mono<Mono<NotificacaoLiberacao>> eventoNotificaLiberacao = 
			eventoBuscaCompra.map(c -> {
				customEnv.indicaAmbientePagSeguro();
				RespostaNotificacaoAssinatura resposta = customEnv
						.getRespostaNotificacaoTransacao(notificationCode);
				return finalizaPagamento.finaliza(c, resposta);
		});
		
		
		Mono<ModelAndView> eventoGeracaoModelAndView = eventoNotificaLiberacao.
			map(notificacao -> {
				return new ModelAndView("redirect:/pagseguro/fim/");			 
			});

		return eventoGeracaoModelAndView;
	}

Olhando com atenção, você vai perceber que o nome das minhas variáveis sempre tem a palavra evento. Como eu tinha dito, essa é a ideia. Precisamos criar vários eventos que, em algum momento do tratamento da requisição, vão ser executados. A classe Mono, representa a idea de um evento que gera(emite) apenas uma saída.

	public abstract class Mono<T> implements Publisher<T>,Backpressurable, 
	Introspectable,Completable {

	...

	}

Dentre as várias interfaces que ela implementa, está a Publisher<T>. Ela indica que determinado objeto é um produtor de eventos(sinais). Essa é uma das interfaces principais da especificação Reactor Streams. A ideia é que o tempo inteiro você esteja criando produtores de eventos. Aqui podemos dar um zoom em outro pedaço de código específico:

		Mono<Mono<NotificacaoLiberacao>> eventoNotificaLiberacao = 
			eventoBuscaCompra.map(c -> {
				customEnv.indicaAmbientePagSeguro();
				RespostaNotificacaoAssinatura resposta = customEnv
						.getRespostaNotificacaoTransacao(notificationCode);
				return finalizaPagamento.finaliza(c, resposta);
		});

Usamos o método map, tipicamente usado, nas aplicações tradicionais, para transformar coleções de um tipo para coleções de outro tipo. Só que pensando pelo lado funcional da coisa, a ideia é que o map seja uma função que recebe uma entrada X e retorna uma saída Y. No caso aqui vamos transformar um evento que gera uma compra em um evento de evento(Mono<Mono<…) que gera uma NotificacaoLiberacao.  Evento de evento? É o que? Quando temos uma sequência de produtores de eventos, dizemos que temos uma Flux! Para obter o Flux, podemos usar o flatMap :).

	Flux<NotificacaoLiberacao> eventosQueVaoGerarUmaNotificaco = 
		eventoBuscaCompra.flatMap(c -> {
			RespostaNotificacaoAssinatura resposta = customEnv
					.getRespostaNotificacaoTransacao(notificationCode);
			return finalizaPagamento.finaliza(c, resposta);
	});
	
	
	Flux<ModelAndView> eventosQueVaoGerarOModelAndView = 
		eventosQueVaoGerarUmaNotificaco.map(notificacao -> {
			return new ModelAndView("redirect:/pagseguro/fim/");			 
	});

Um outro exemplo de geração de Flux, que vai ficar para o próximo post, é quando você quiser retornar uma lista de objetos do seu DAO :).

Nesse ponto você pode estar pensando, já tem o produtor do evento, mas cadê o consumidor!? O seu pensamento não podia estar mais certo.

	Flux<ModelAndView> eventosQueVaoGerarOModelAndView = 
		eventosQueVaoGerarUmaNotificaco.map(notificacao -> {
			return new ModelAndView("redirect:/pagseguro/fim/");			 
	});
	
	ConsumerSubscriber<ModelAndView> consumer = 
		new ConsumerSubscriber<ModelAndView>();
	eventosQueVaoGerarOModelAndView.subscribe(consumer);

A classe ConsumerSubscriber é apenas uma das zilhares de implementações da interface Subscriber. Você pode associar quantos consumidores quiser, a um produtor de evento, exatamente do mesmo jeito que o Observer nos ensina. Inclusive o ConsumerSubscriber, possui um construtor para você passar os callbacks de tratamento dos eventos.

	public ConsumerSubscriber(Consumer<? super T> consumer,
			Consumer<? super Throwable> errorConsumer,
			Runnable completeConsumer) {
		this.consumer = consumer;
		this.errorConsumer = errorConsumer;
		this.completeConsumer = completeConsumer;
	}

Só que fique atento. Em geral, quando você tiver dentro do Spring 5, você não vai criar os subscribers, já que isso será trabalho do framework. Do mesmo jeito que hoje não é você que injeta as dependências nos seus objetos, e sim o container. Para fechar as interfaces importantes, quando você liga um interessado no evento(Subscriber) em um gerador de eventos(Publisher) você cria a chamada Subscription. 

Este post foi para tentar passar a ideia do novo modelo que muitos de nós teremos que lidar. Como disse no inicio, o meu entendimento ainda está em construção, o uso exagerado do método map até indica isso :).  Para facilitar o seu estudo, segue alguns links que eu usei.

  1. http://www.reactivemanifesto.org/
  2. https://www.youtube.com/watch?v=fec9nEIybp0&list=PLgGXSWYM2FpPuIFvbv6U_5cKLJB6PQbN4&index=35
  3. https://github.com/spring-projects/spring-reactive
  4. http://projectreactor.io/core/docs/reference/
  5. http://spring.io/blog/2016/02/09/reactive-spring
  6. https://github.com/reactor/lite-rx-api-hands-on

Nos próximos posts, eu vou tentar dar um zoom em outras partes do Project Reactor, além de associar com os casos de uso do nosso dia a dia. Apenas para fim de estudo, também criei um projeto no github que tenta demonstrar esse modelo de programação.