V minulém díle seriálu o Spring Framework Pavel Šindelář psal o HTTP/2 a web containerech na Java 8. Tentokrát bude Lukáš Marek podrobněji věnovat reaktivnímu programování. Podíváme se, k čemu je reaktivní přístup dobrý, jaké jsou jeho nevýhody a ukážeme si i to, jak vytvořit jednoduchou reaktivní aplikaci.
Spring Reactive Programming
Většina webových Java aplikací je postavena na servlet containeru, který zpracovává požadavky na serveru a vrací data až po zpracování celého requestu. V podstatě se jedná o synchronní a blokující zpracování, protože klient musí počkat, dokud server nezprocesuje požadavek a nepošle všechna data naráz. Reaktivní přístup umožňuje zpracovávat a posílat data postupně, tzn asynchronně bez blokování. V reaktivní aplikaci je nutné změnit přístup k datům a zdrojům v celé architektuře aplikace, protože žádné zpracování a načítaní dat nesmí probíhat blokujícím requestem.
Jak to funguje
Spring Web Reactive beží na vrstvě Reactive Streams HTTP adapter, která je kompletně neblokující a nemůže komunikovat s blokujícím voláním, bežícím nad standardním servlet containerem. Requesty jsou tedy zpracovávány asynchronně. Každý request je rozdělen na pipelinu, která se rozdělí na jednotlivé části a vloží do event-loop. Každá část pipeliny je zpracovávána nezávisle, nic tedy neblokuje zpracování a jednotlivé části je možné zpracovávat paralelně ve více vláknech.
Jednoduchý teoretický příklad zpracovávání:
- chci načítat knihy z databáze a mohu použít čtyři vlákna
- první vlákno načítá data z databáze, druhé nad každou knihou provádí nějakou operaci, třetí např. převádí entitu na dto a čtvrté vlákno vrací data klientovi v response
- v reaktivní aplikaci: první kniha je už zpracovaná a posílá se na klienta, druhá kniha už se může převádět na dto, nad třetí knihou se provádí operace a zároveň se čtvrtá kniha načítá z databáze
Proč to vzniklo
Integrace se Springem nevznikla proto, že by to bylo nejrychlejší nebo nejjednoduší. Vznikla proto, aby developer mohl využít Spring, pokud by chtěl requesty zpracovávat reaktivně. Zároveň by nemusel využívat jiné technologie a jazyky, pokud by sám nechtěl.
Na co se to hodí
Reaktivní přístup se hodí spíše na menší microservices. Navíc pouze v případě, že každá část flow, přes kterou prochází request, musí být zpracována reaktivně. Je důležité využít i databázový driver, který umí číst data reaktivně (v ukázkové aplikaci je využit mongodb-driver-reactivestreams).
Jak vytvořit jednoduchou reaktivní aplikaci
Konfigurace pom.xml
< ?xml version="1.0" encoding="UTF-8"?> <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelversion>4.0.0</modelversion> <name>Spring Reactive</name> <groupid>cz.morosystems.reactive</groupid> <artifactid>reactive-example</artifactid> <version>0.1.0-SNAPSHOT</version> <packaging>jar</packaging> <parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version>2.0.0.BUILD-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-webflux</artifactid> </dependency> <dependency> <groupid>org.springframework.data</groupid> <artifactid>spring-data-commons</artifactid> </dependency> <dependency> <groupid>org.springframework.data</groupid> <artifactid>spring-data-mongodb</artifactid> </dependency> <dependency> <groupid>org.mongodb</groupid> <artifactid>mongodb-driver-reactivestreams</artifactid> </dependency> </dependencies> <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/libs-snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </project>
Ukázková aplikace používá databázi mongoDB a pro komunikaci s ní využívá driver mongodb-driver-reactivestreams pro neblokující načítání dat.
Konfigurace Spring aplikace
@SpringBootApplication @EnableReactiveMongoRepositories public class Application { public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); } @Bean public MongoClient mongoClient() { return MongoClients.create("mongodb://localhost"); } @Bean public ReactiveMongoTemplate reactiveMongoTemplate() { return new ReactiveMongoTemplate(mongoClient(), "account"); } }
Aplikaci je pouze nutné nastavit přístup do databáze
Implementace controlleru
@RestController public class SampleController { @Autowired private BookRepository repository; @GetMapping(value = "/book/user/{username}") public Flux<bookdto> findByUser(@PathVariable("username") String username) { return repository.findByUser(username) .map(this::convertToDto); } @GetMapping(value = "/book") public Flux</bookdto><bookdto> findAll() { return repository.findAll() .map(this::convertToDto); } @GetMapping(value = "/book/{id}") public Mono</bookdto><bookdto> findById(@PathVariable("id") String id) { return repository.findById(id) .map(this::convertToDto); } @PostMapping("/book") public Mono</bookdto><bookdto> create(@RequestBody Publisher</bookdto><bookdto> bookStream) { return repository .save(Mono.from(bookStream) .map(this::convertFromDto)) .map(this::convertToDto); } private BookDto convertToDto(Book book) { return new BookDto(book.getId(), book.getUsername(), book.getName()); } private Book convertFromDto(BookDto bookDto) { Book book = new Book(); book.setId(bookDto.getId()); book.setUsername(bookDto.getUsername()); book.setName(bookDto.getName()); return book; } }
Spring Framework interně využívá Project Reactor jako core pro reaktivní zpracovávání. Reactor je implementace reaktivního streamování, která využívá třídy Flux a Mono jako Reactive Streams Publisher. Jedná se vlastně o třídy, které se používají přes celou pipeline zpracovávání a jejichž prostřednictvím se streamují zpracované objekty.
Repozitory na práci s databází
@Repository public class BookRepository { @Autowired private ReactiveMongoTemplate template; public Mono<book> findById(String id) { return template.findById(id, Book.class); } public Flux</book><book> findAll() { return template.findAll(Book.class); } public Flux</book><book> findByUser(String username) { return template.find(query(where("username").is(username)), Book.class); } public Mono</book><book> save(Mono</book><book> book) { return template.insert(book); } }
Z databáze se vrací stejné streamované objekty jako z controlleru ven přes REST API.
Entita pro ukázkový objekt
Ještě byla využita podobná třída pro komunikaci skrze REST API, ale ta má stejné parametry:
@Document public class Book { @Id private String id; private String username; private String name; // getters and setters }
Tento článek vychází z části přednášky Juergena Hoellera o Springu během meet-upu v kancelářích MoroSystems v Brně. Konkrétní část přednášky viz ve videu níže. Celou přednášku s Juergenem Hoellerem je možné shlédnout zde (video na YouTube).
Zajímají tě nejlepší technologie na zajímavých projektech?
Aktuálně hledáme seniorní Java / JEE Developery do kanceláří v Hradci Králové, Brně, Bratislavě ale i na home-office.
Zajímá tě něco jiného? Mrkni na všechny naše otevřené pozice a dej nám o sobě vědět, bez ohledu na to, zda máš rád front-end nebo back-end, jestli nějakou technologii umíš nebo ne. Pokud budeš ty sám chtít, dostaneš u nás příležitost naučit se, co tě zajímá nebo udělat něco výjimečného.
Napsat komentář