Java, JavaScript + HTML5, Sencha, Cordova/Ionic, Angular, RxJS + all things reactive
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)
@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());
}
HTTP Request
Dados
Controller
Service
Repository (DAO)
Request
Query
3 registros
5 registros
2 registros
Response
10 segundos
List<String> taskTitles = taskDao.getTasks().stream()
.map(task -> task.getTitle())
.collect(Collectors.toList());
Flux<String> taskTitles = reactiveTaskDao.getTasks()
.map(task -> task.getTitle());
Request
Query
3 registros
3 registros
5 registros
5 registros
2 registros
2 registros
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);
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>
Imperativo | Reactor / Spring |
---|---|
Object | Mono<Object> |
List<T> | Flux<T> |
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);
}
@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));
}
@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);
}
// 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));
}
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}`);
}
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);
});
}
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))
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 ->
)
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))
)
→ 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);
});
});
https://github.com/loiane/fullstack-reactive-spring-angular