Reatividade FullStack com Spring WebFlux e Angular

Loiane Groner

Java, JavaScript + HTML5, Sencha, Cordova/Ionic, Angular, RxJS + all things reactive

Nossa Stack

Programação Reativa no

Em poucas palavras, a programação reativa é sobre aplicações não-bloqueadoras, orientadas a eventos, que escalam com um pequeno número de threads com contrapressão como ingrediente chave que continua a garantir que os produtores não dominem os consumidores

- Rossen Stoyanchev

(Spring team)

Programação Reativa

  • Usada para descrever sistemas orientados a eventos
  • Reatividade é mais usada para escalabilidade e estabilidade do que velocidade
  • Reage à disponibilidade dos recursos

Programação

Funcional

Reativa

Programação Imperativa

@RestController
public class TaskController {

    @Autowired
    private TaskRepository repository;

    @GetMapping("/tasks")
    List<Task> getAll() {
        return repository.findAll();
    }
}
List<Task> tasks = taskDao.getTasks();

List<String> taskTitles = new ArrayList<String>();

for (int i = 0; i < tasks.size(); ++i) {
  taskTitles.add(tasks.get(i).getTitle());
}

Operações Assíncronas e Bloqueadoras

HTTP Request

Dados

Controller

Service

Repository (DAO)

Request

Query

3 registros
5 registros
2 registros

Response

10 segundos

Programação funcional

List<String> taskTitles = taskDao.getTasks().stream()
                              .map(task -> task.getTitle())
                              .collect(Collectors.toList());

Programação Funcional reativa

Flux<String> taskTitles = reactiveTaskDao.getTasks()
                              .map(task -> task.getTitle());

reactive streams

Operações Assíncronas e

Não Bloqueadoras

Request

Query

3 registros
3 registros
5 registros
5 registros
2 registros
2 registros

Operações Assíncronas e

Não Bloqueadoras e sem contrapressão

Spring 5

Spring 5

  • Um dos primeiros frameworks à oferecerem funcionalidade reativa completa no backend!
  • Atualmente versão M7 (01/Dezembro/2017)

Spring webflux

Como se diferencia do Java 8?

  • Java 8: Programação Funcional para Java
  • Streams podem potencialmente produzir um número infinito de valores
  • Operações não bloqueadoras

Reactor Streams são reutilizáveis

Flux<Integer> j = Flux.just(1, 2, 3, 4, 5);

j.map(i -> i * 10)
  .subscribe(System.out::println);

j.map(i -> i + 5)
  .subscribe(System.out::println);

Java 8: IllegalStateException

Stream<Integer> j = Arrays.asList(1, 2, 3, 4, 5).stream();

j.map(i -> i * 10)
  .forEach(System.out::println);

j.map(i -> i + 5)
  .forEach(System.out::println);
Mono<T>

Flux<T>

 

Stream de um Objeto

Stream de uma coleção de Objetos

Imperativo Reactor / Spring
Object Mono<Object>
List<T> Flux<T>

Spring Data

public interface ReactiveMongoRepository<T, ID> {

    <S extends T> Mono<S> insert(S var1);

    <S extends T> Flux<S> insert(Iterable<S> var1);

    <S extends T> Flux<S> insert(Publisher<S> var1);

    <S extends T> Flux<S> findAll(Example<S> var1);

    <S extends T> Flux<S> findAll(Example<S> var1, Sort var2);
}

DEMO

@AllArgsConstructor
@NoArgsConstructor
@Data
@Document(collection = "tasks")
public class Task {

    @Id
    private String id;

    @NotBlank
    @Size(max = 200)
    private String title;

    private boolean completed = false;
}
@Repository
public interface TaskRepository extends ReactiveMongoRepository<Task, String> { }
@RestController
public class TaskController {

    @Autowired
    private TaskRepository taskRepository;

    @GetMapping("tasks")
    public Flux<Task> getAll() {
        return taskRepository.findAll();
    }

    @PostMapping("tasks")
    public Mono<Task> create(@Valid @RequestBody Task task) {
        return taskRepository.save(task);
    }
}
@PutMapping("/tasks/{id}")
public Mono<ResponseEntity<Task>> update(
    @PathVariable String id, @Valid @RequestBody Task task) {

    return taskRepository.findById(id)
            .flatMap(record -> {
                record.setCompleted(task.isCompleted());
                record.setTitle(task.getTitle());
                return taskRepository.save(record);
            })


}
@PutMapping("/tasks/{id}")
public Mono<ResponseEntity<Task>> update(
    @PathVariable String id, @Valid @RequestBody Task task) {

    return taskRepository.findById(id)
            .flatMap(record -> {
                record.setCompleted(task.isCompleted());
                record.setTitle(task.getTitle());
                return taskRepository.save(record);
            })
            .map(updatedRecord -> new ResponseEntity<>(updatedRecord, HttpStatus.OK))
            .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}

Spring Router

@Component
public class TaskRouter {

    @Autowired
    private TaskService taskService;

    public Mono<ServerResponse> getAll(ServerRequest serverRequest) {
        return ServerResponse.ok()
                .body(taskService.getAll(), Task.class)
                .doOnError(throwable -> new IllegalStateException(":("));
    }
}
@Bean
RouterFunction<?> taskRoutes(TaskRouter taskRouter) {
    return RouterFunctions.route(
	RequestPredicates.GET("/async/tasks"), taskRouter::getAll)
	.andRoute(RequestPredicates.GET("/async/stream/tasks/{id}"), taskRouter::events);
}

Contrapressão

// text/eventstream ou json/eventstream
@GetMapping(value = "/stream/tasks", 
    produces = MediaType.APPLICATION_STREAM_JSON_VALUE)

public Flux<Task> getAllStream() {

    return taskRepository.findAll().delayElements(Duration.ofSeconds(2));
}

Angular

Angular + Reatividade

  • Programação reativa com RxJS
  • RxJS é dependência do Angular
  • RxJS presente no HTTP, Forms, Rotas
  • Arquitetura reativa com ngRx (Redux)

Angular HTTP -> Spring

load(): Observable<Task[]> {
    return this.http.get<Task[]>(this.API_TASKS_URL);
}

create(record: Task) {
    return this.http.post<Task>(this.API_TASKS_URL, record);
}

update(record: Task) {
    return this.http.put<Task>(`${this.API_TASKS_URL}/${record.id}`, record);
}

remove(id: string) {
    return this.http.delete<Task>(`${this.API_TASKS_URL}/${id}`);
}

Contrapressão com RxJS

load(): Observable<Task[]> {
  return Observable.create((observer) => {
    const eventSource = new EventSource('http://localhost:8080/stream/tasks');
    eventSource.onmessage = (event) => {
      console.log('Received event: ', event);
      const json = JSON.parse(event.data);
      observer.next([json]);
    };
    eventSource.onerror = (error) => observer.error('EventSource error: ' + error);
  });
}

Spring

web client

WebClient.create("https://deckofcardsapi.com/api/deck")
WebClient.create("https://deckofcardsapi.com/api/deck")
  .get()
  .uri("/new/shuffle?deck_count=1")
  .accept(MediaType.APPLICATION_JSON)
  .exchange() // troca Request por Mono<ClientResponse>
{
  "success": true,
  "deck_id": "3p40paa87x90",
  "shuffled": true,
  "remaining": 52
}
WebClient.create("https://deckofcardsapi.com/api/deck")
  .get()
  .uri("/new/shuffle/?deck_count=1")
  .accept(MediaType.APPLICATION_JSON)
  .exchange()
  .flatMap(response -> response.bodyToMono(Map.class))

Só queremos o deck_id

WebClient.create("https://deckofcardsapi.com/api/deck")
  .get()
  .uri("/new/shuffle/?deck_count=1")
  .accept(MediaType.APPLICATION_JSON)
  .exchange()
  .flatMap(response -> response.bodyToMono(Map.class))
  .map(response -> response.get("deck_id"))
WebClient webClient = WebClient.create("https://deckofcardsapi.com/api/deck");
Mono<Map> card = webClient.get()
  .uri("/new/shuffle/?deck_count=1")
  .accept(MediaType.APPLICATION_JSON)
  .exchange()
  .flatMap(response -> response.bodyToMono(Map.class))
  .map(response -> response.get("deck_id"))
  .flatMap(deckId ->
    




  )

Agora vamos tirar uma carta da pilha

WebClient webClient = WebClient.create("https://deckofcardsapi.com/api/deck");
Mono<Map> card = webClient.get()
  .uri("/new/shuffle/?deck_count=1")
  .accept(MediaType.APPLICATION_JSON)
  .exchange()
  .flatMap(response -> response.bodyToMono(Map.class))
  .map(response -> response.get("deck_id"))
  .flatMap(deckId ->
    webClient.get()
      .uri("/{deckId}/draw", Collections.singletonMap("deckId", deckId))
      .accept(MediaType.APPLICATION_JSON)
      

  )
WebClient webClient = WebClient.create("https://deckofcardsapi.com/api/deck");
Mono<Map> card = webClient.get()
  .uri("/new/shuffle/?deck_count=1")
  .accept(MediaType.APPLICATION_JSON)
  .exchange()
  .flatMap(response -> response.bodyToMono(Map.class))
  .map(response -> response.get("deck_id"))
  .flatMap(deckId ->
    webClient.get()
      .uri("/{deckId}/draw", Collections.singletonMap("deckId", deckId))
      .accept(MediaType.APPLICATION_JSON)
      .exchange()
      .flatMap(response -> response.bodyToMono(Map.class))
  )

Casos de uso

Casos de Uso

  • Queries na base de forma contínua (tempo real)
  • Tratar eventos na UI (Android)
  • Big Data 
  • Análise em tempo real (Google Analytics)
  • HTTP/2 (controle de fluxo)
  • Melhoria de leitura do código

Exemplo

Netflix / Hulu

 Obtém dados do usuário

 Obtém lista de filmes  para cada lista

→  para cada filme

→  →  Obtém favoritos

→  →  Obtém estrelas (notas)

→  →  Obtém metadados →  filmes similares, etc

// obtém dados do usuário
Mono<Map<String, Object>> dataToFrontEnd = 
    userRespository.findById(userId).flatMap(user -> {

    // obtém dados do catálogo pessoal
    Mono<Map<String, Object>> catalog = getPersonalCatalog(user)
        .flatMap(catalogList -> {
	    catalogList.getVideos()
		.flatMap(video -> {
		    Flux<Bookmark> bookmark = getBookmark(video);
    		    Flux<Rating> rating = getRating(video);
		    Flux<VideoMetadata> metadata = getMetadata(video);
	            return Flux.zip(bookmark, rating, metadata,
			(b, r, m) -> combineVideoData(video, b, r, m));
	        });
    	});

    // obtém dados pessoais em paralelo
    Mono<Map<String, Object>> social = getSocialData(user)
	.map(socialData -> socialData.getDataAsMap());

    return Mono.merge(catalog, social);
});
// exemplo e-commerce
List<Order> getOrders(int customerId)
List<Product> getProducts(Order order)
ShippingStatus getShippingStatus(Order o, Product p)


getOrdersAsync(1)
    .limit(1)
    .flatMap(o -> {
        return getProductsAsync(o)
            .flatMap(p -> {
                return getShippingStatusAsync(o, p);
       });
    });

Ainda temos alguns desafios...

JPA?

Preciso mudar meu código?

https://github.com/loiane/fullstack-reactive-spring-angular

Obrigada!

Reatividade FullStack com Angular e Spring WebFlux

By Loiane Groner

Reatividade FullStack com Angular e Spring WebFlux

Arquitetura reativa do front ao back-end com Angular e Spring WebFlux. Nessa talk mostro a novidade mais legal do Spring 5: Spring WebFlux, um módulo novo baseado no projeto Reactor e RxJava, que oferece um design orientado a streams não bloqueadores. Vamos aprender como usar o fluxo 100% assíncrono com WebFlux e Spring Reactive MongoDB. E para termos uma stack completamente reativa, vamos aprender como consumir esses dados reativos no Angular, também usando RxJS e ngRx (Redux) como arquitetura. E um exemplo especial de como tratar contrapressão no Angular com RxJS. Além disso, alguns exemplos reais de como podemos usar WebFlux e programação funcional reativa nos nossos projetos!

  • 3,025