Caractéristiques : avantages et inconvénients
Le module WebFlux est apparu dans la 5ème version du framework Spring. Ce micro-framework est une alternative à Spring MVC et représente une approche réactive pour l'écriture de services web. Au cœur de WebFlux se trouve la bibliothèque Project Reactor, qui facilite la programmation de flux non bloquants (asynchrones) qui gèrent les entrées/sorties de données.
Notez que WebFlux nécessite le serveur Netty intégré de Spring pour fonctionner. Tomcat et Jetty intégrés ne conviennent pas. Le schéma suivant illustre les spécificités de l'environnement dans lequel WebFlux s'exécute [ 1 ].
Le graphique ci-dessous montre l'avantage en termes de performances des services Web réactifs [ 2 ] par rapport aux services bloquants classiques. Avec une charge de serveur de 300 utilisateurs ou plus, Netty commence à dépasser Tomcat en nombre de requêtes traitées simultanément. Avec la charge maximale du serveur en mode réactif, deux fois plus d'utilisateurs peuvent être desservis simultanément. Selon d'autres sources, l'avantage n'est pas si impressionnant, mais perceptible. La programmation réactive est plus efficace lors d'une mise à l'échelle verticale.
, . , , . , - , . .
. , - . Project Reactor, WebFlux, RxJava (, , Android) , . , , .
. , JPA- Hibernate EclipseLink . Java , @Entity, @OneToMany, @ManyToMany, @JoinTable ... . . @Table @Column. JPQL (HQL) . SQL. JDBC R2DBC. .
REST- REST-
pom.xml spring-boot-starter-webflux, spring-boot-starter-web Spring MVC.
<dependency>
|
REST Spring WebFlux Spring MVC. . WebFlux Mono ( Flux, ). , sendSms SmsController
@RestController
@Api(tags = " -")
public class SmsController extends Controller {
private static final Logger logger = LoggerFactory.getLogger(SmsController.class);
private final SmsService smsService;
public SmsController(SmsService smsService) {
this.smsService = smsService;
}
@GetMapping(value = "/sms/{to}/{text}")
@ApiOperation(value = " ")
@ApiResponse(code = 200, message = " ")
Mono<ResponseEntity<ResultDto>> sendSms(
@ApiParam(value = " ", required = true) @PathVariable(name = "to") String to,
@ApiParam(value = " ", required = true) @PathVariable(name = "text") String text
) {
return smsService.sendSms(to, text);
}
}
sendSms SmsService:
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@Service
public class SmsService {
private static final Logger logger = LoggerFactory.getLogger(SmsService.class);
private static final String PHONE_PREFIX = "7";
private final WebClient smsClient;
@Value("${sms.callingSystem}")
private String callingSystem;
@Value("${server.site}")
private String site;
@Value("${sms.callbackUrl}")
private String callbackUrl;
public SmsService(WebClient smsClient) {
this.smsClient = smsClient;
}
public Mono<ResponseEntity<ResultDto>> sendSms(String to, String text) {
MessageDto message = new MessageDto(callingSystem, PHONE_PREFIX + to, text);
message.setCallbackUrl(site + callbackUrl);
MessagesDto messages = new MessagesDto(message);
return smsClient.post()
.bodyValue(messages)
.exchange()
.flatMap(res -> res.bodyToMono(ResultsDto.class)
.map(rs -> {
List<ResultDto> results = rs.getResults();
return ResponseEntity.ok().body(results.get(0));
})
);
}
smsClient.post().bodyValue() retrieve().bodyToMono(), . , , exchange().flatMap(<->).
@Configuration
public class SmsConfig {
private static final Logger logger = LoggerFactory.getLogger(SmsConfig.class);
private static final String CONTENT_TYPE_HEADER = "Content-Type";
private static final String AUTH_HEADER = "Authorization";
@Value("${sms.url}")
private String url;
@Value("${sms.token}")
private String token;
@Bean
public WebClient smsClient() {
HttpClient httpClient = HttpClient
.create()
.tcpConfiguration(
tc -> tc.bootstrap(
b -> BootstrapHandlers.updateLogSupport(b, new CustomLogger(HttpClient.class))));
WebClient webClient = WebClient.builder()
.baseUrl(url)
.defaultHeader(CONTENT_TYPE_HEADER, MediaType.APPLICATION_JSON.toString())
.defaultHeader(AUTH_HEADER, token)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add(logRequest());
exchangeFilterFunctions.add(logResponse());
})
.build();
return webClient;
}
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
logger.info("Sms REST CLIENT REQUEST: **********");
return Mono.just(clientRequest);
});
}
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
logger.info("********** Sms REST CLIENT RESPONSE");
return Mono.just(clientResponse);
});
}
}
Spring WebClient. RestTemplate ( Spring MVC) . REST- [3].
:
Maven pom.xml . PostgreSQL.
<dependency>
|
JPA R2DBC , , . spring-boot-starter-data-jpa , JPA.
CRUD-
import org.springframework.data.r2dbc.repository.Query;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;
import ru.sksbank.privilege.demo.model.dc.Client;
@Repository
public interface ClientRepository extends ReactiveCrudRepository<Client, Long> {
@Query("SELECT * FROM client WHERE phone_number = :phone AND passive = 0")
Mono<Client> findByPhoneActive(String phone);
}
, , Client :
@Table("client")
public class Client {
@Id @Column("id") private Long id;
@Column("user_id") private Long userId;
@Column("person_id") private Long personId;
@Column("phone_number") private String phone;
}
. User PersonData. userId personId. . , lazy loading, JPA, , .
, . R2DBC ( JDBC) application.yml.
spring :
|
Liens
1 Programmation réactive : Reactor et Spring WebFlux
2 Programmation réactive en Java : comment, pourquoi et ça vaut le coup ?