Реактивное программирование со Spring, часть 3 WebFlux

Это третья часть серии блогов о реактивном программировании, в которой я познакомлю вас с WebFlux — реактивным веб-фреймворком Spring.

1. ВВЕДЕНИЕ В SPRING WEBFLUX

Исходный веб-фреймворк для Spring — Spring Web MVC — был построен для Servlet API и контейнеров Servlet.

WebFlux был представлен как часть Spring Framework 5.0. В отличие от Spring MVC, он не требует Servlet API. Он полностью асинхронный и неблокирующий, реализует спецификацию Reactive Streams через проект Reactor (см. предыдущий пост в блоге ).

WebFlux требует Reactor в качестве основной зависимости, но он также может взаимодействовать с другими реактивными библиотеками через Reactive Streams.

1.1 МОДЕЛИ ПРОГРАММИРОВАНИЯ

Spring WebFlux поддерживает две разные модели программирования: на основе аннотаций и функциональную.

1.1.1 АННОТИРОВАННЫЕ КОНТРОЛЛЕРЫ

Если вы работали со Spring MVC, модель на основе аннотаций будет выглядеть довольно знакомой, поскольку в ней используются те же аннотации из веб-модуля Spring, что и в Spring MVC. Основное отличие состоит в том, что теперь методы возвращают реактивные типы Mono и Flux. См. Следующий пример RestController с использованием модели на основе аннотаций:

@RestController @RequestMapping("/students") public class StudentController {      @Autowired     private StudentService studentService;       public StudentController() {     }      @GetMapping("/{id}")     public Mono<ResponseEntity<Student>> getStudent(@PathVariable long id) {         return studentService.findStudentById(id)                 .map(ResponseEntity::ok)                 .defaultIfEmpty(ResponseEntity.notFound().build());     }      @GetMapping     public Flux<Student> listStudents(@RequestParam(name = "name", required = false) String name) {         return studentService.findStudentsByName(name);     }      @PostMapping     public Mono<Student> addNewStudent(@RequestBody Student student) {         return studentService.addNewStudent(student);     }      @PutMapping("/{id}")     public Mono<ResponseEntity<Student>> updateStudent(@PathVariable long id, @RequestBody Student student) {         return studentService.updateStudent(id, student)                 .map(ResponseEntity::ok)                 .defaultIfEmpty(ResponseEntity.notFound().build());     }      @DeleteMapping("/{id}")     public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable long id) {         return studentService.findStudentById(id)                 .flatMap(s ->                         studentService.deleteStudent(s)                                 .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))                 )                 .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));     } }

Некоторые пояснения к функциям, использованным в примере:

  • map функция используется для преобразования элемента, испускаемого Mono, применяя функцию синхронной к нему.

  • flatMap функция используется для преобразования элемент, испускаемый Mono асинхронно, возвращая значение, излучаемого другим Mono.

  • defaultIfEmpty функция обеспечивает значение по умолчанию, если Mono завершается без каких — либо данных.

1.1.2 ФУНКЦИОНАЛЬНЫЕ КОНЕЧНЫЕ ТОЧКИ

Модель функционального программирования основана на лямбда-выражении и оставляет за приложением полную обработку запроса. Он основан на концепциях HandlerFunctions и RouterFunctions.

HandlerFunctions используются для генерации ответа на данный запрос:

@FunctionalInterface public interface HandlerFunction<T extends ServerResponse> {     Mono<T> handle(ServerRequest request); } 

RouterFunction используется для маршрутизации запросов к HandlerFunctions:

@FunctionalInterface public interface RouterFunction<T extends ServerResponse> {     Mono<HandlerFunction<T>> route(ServerRequest request);     ... } 

Продолжая с тем же примером ученика, мы получим что-то вроде следующего, используя функциональный стиль.

A StudentRouter:

@Configuration public class StudentRouter {      @Bean     public RouterFunction<ServerResponse> route(StudentHandler studentHandler){         return RouterFunctions             .route(                 GET("/students/{id:[0-9]+}")                     .and(accept(APPLICATION_JSON)), studentHandler::getStudent)             .andRoute(                 GET("/students")                     .and(accept(APPLICATION_JSON)), studentHandler::listStudents)             .andRoute(                 POST("/students")                     .and(accept(APPLICATION_JSON)),studentHandler::addNewStudent)             .andRoute(                 PUT("students/{id:[0-9]+}")                     .and(accept(APPLICATION_JSON)), studentHandler::updateStudent)             .andRoute(                 DELETE("/students/{id:[0-9]+}")                     .and(accept(APPLICATION_JSON)), studentHandler::deleteStudent);     } }

И StudentHandler:

@Component public class StudentHandler {      private StudentService studentService;      public StudentHandler(StudentService studentService) {         this.studentService = studentService;     }      public Mono<ServerResponse> getStudent(ServerRequest serverRequest) {         Mono<Student> studentMono = studentService.findStudentById(                 Long.parseLong(serverRequest.pathVariable("id")));         return studentMono.flatMap(student -> ServerResponse.ok()                 .body(fromValue(student)))                 .switchIfEmpty(ServerResponse.notFound().build());     }      public Mono<ServerResponse> listStudents(ServerRequest serverRequest) {         String name = serverRequest.queryParam("name").orElse(null);         return ServerResponse.ok()                 .contentType(MediaType.APPLICATION_JSON)                 .body(studentService.findStudentsByName(name), Student.class);     }      public Mono<ServerResponse> addNewStudent(ServerRequest serverRequest) {         Mono<Student> studentMono = serverRequest.bodyToMono(Student.class);         return studentMono.flatMap(student ->                 ServerResponse.status(HttpStatus.OK)                         .contentType(MediaType.APPLICATION_JSON)                         .body(studentService.addNewStudent(student), Student.class));      }      public Mono<ServerResponse> updateStudent(ServerRequest serverRequest) {         final long studentId = Long.parseLong(serverRequest.pathVariable("id"));         Mono<Student> studentMono = serverRequest.bodyToMono(Student.class);          return studentMono.flatMap(student ->                 ServerResponse.status(HttpStatus.OK)                         .contentType(MediaType.APPLICATION_JSON)                         .body(studentService.updateStudent(studentId, student), Student.class));     }      public Mono<ServerResponse> deleteStudent(ServerRequest serverRequest) {         final long studentId = Long.parseLong(serverRequest.pathVariable("id"));         return studentService                 .findStudentById(studentId)                 .flatMap(s -> ServerResponse.noContent().build(studentService.deleteStudent(s)))                 .switchIfEmpty(ServerResponse.notFound().build());     } }

Некоторые пояснения к функциям, использованным в примере:

  • switchIfEmpty функция имеет ту же цель, defaultIfEmpty, но вместо того, чтобы обеспечить значение по умолчанию, она используется для обеспечения альтернативного Mono.

Сравнивая две модели, мы видим, что:

  • Для использования функционального варианта требуется еще немного кода для таких вещей, как получение входных параметров и синтаксический анализ до ожидаемого типа.

  • Не полагаясь на аннотации, но написание явного кода предлагает некоторую большую гибкость и может быть лучшим выбором, если нам, например, нужно реализовать более сложную маршрутизацию.

1.2 ПОДДЕРЖКА СЕРВЕРА

WebFlux работает в средах выполнения, отличных от сервлетов, таких как Netty и Undertow (неблокирующий режим), а также в средах выполнения сервлетов 3.1+, таких как Tomcat и Jetty.

По умолчанию стартер Spring Boot WebFlux использует Netty, но его легко переключить, изменив зависимости Maven или Gradle.

Например, чтобы переключиться на Tomcat, просто исключите spring-boot-starter-netty из зависимости spring-boot-starter-webflux и добавьте spring-boot-starter-tomcat:

<dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-webflux</artifactId>     <exclusions>         <exclusion>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-netty</artifactId>         </exclusion>     </exclusions> </dependency> <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-tomcat</artifactId> </dependency>

1.3 КОНФИГУРАЦИЯ

Spring Boot обеспечивает автоматическую настройку Spring WebFlux, которая хорошо работает в общих случаях. Если вам нужен полный контроль над конфигурацией WebFlux, можно использовать аннотацию @EnableWebFlux (эта аннотация также потребуется в простом приложении Spring для импорта конфигурации Spring WebFlux).

Если вы хотите сохранить конфигурацию Spring Boot WebFlux и просто добавить дополнительную конфигурацию WebFlux, вы можете добавить свой собственный класс @Configuration типа WebFluxConfigurer (но без @EnableWebFlux).

Подробные сведения и примеры см. в документации по конфигурации WebFlux.

2. ЗАЩИТА ВАШИХ КОНЕЧНЫХ ТОЧЕК

Чтобы получить поддержку Spring Security WebFlux, сначала добавьте в свой проект зависимость spring-boot-starter-security. Теперь вы можете включить его, добавив @EnableWebFluxSecurity аннотацию в свой класс Configuration (доступно с Spring Security 5.0).

В следующем упрощенном примере будет добавлена ​​поддержка двух пользователей, один с ролью USER, а другой с ролью ADMIN, принудительно применить базовую аутентификацию HTTP и потребовать роль ADMIN для любого доступа к пути /student/admin:

@EnableWebFluxSecurity public class SecurityConfig {      @Bean     public MapReactiveUserDetailsService userDetailsService() {          UserDetails user = User                 .withUsername("user")                 .password(passwordEncoder().encode("userpwd"))                 .roles("USER")                 .build();          UserDetails admin = User                 .withUsername("admin")                 .password(passwordEncoder().encode("adminpwd"))                 .roles("ADMIN")                 .build();          return new MapReactiveUserDetailsService(user, admin);     }      @Bean     public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {         return http.authorizeExchange()                 .pathMatchers("/students/admin")                 .hasAuthority("ROLE_ADMIN")                 .anyExchange()                 .authenticated()                 .and().httpBasic()                 .and().build();     }      @Bean     public PasswordEncoder passwordEncoder() {         return new BCryptPasswordEncoder();     }  }

Также можно защитить метод, а не путь, сначала добавив аннотацию @EnableReactiveMethodSecurity к вашей конфигурации:

@EnableWebFluxSecurity @EnableReactiveMethodSecurity public class SecurityConfig {     ... }

А затем добавляем @PreAuthorize аннотацию к защищаемым методам. Например, мы можем захотеть, чтобы наши методы POST, PUT и DELETE были доступны только для роли ADMIN. Затем к этим методам можно применить аннотацию PreAuthorize, например:

@DeleteMapping("/{id}") @PreAuthorize("hasRole('ADMIN')") public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable long id) {     ... } 

Spring Security предлагает дополнительную поддержку, связанную с приложениями WebFlux, например защиту CSRF, интеграцию OAuth2 и реактивную аутентификацию X.509. Для получения дополнительной информации прочтите следующий раздел в документации Spring Security: Реактивные приложения

3. ВЕБ-КЛИЕНТ

Spring WebFlux также включает реактивный, полностью неблокирующий веб-клиент. У него есть функциональный, свободный API, основанный на Reactor.

Давайте рассмотрим (еще раз) упрощенный пример того, как WebClient можно использовать для запроса нашего StudentController:

public class StudentWebClient {      WebClient client = WebClient.create("http://localhost:8080");          public Mono<Student> get(long id) {             return client                     .get()                     .uri("/students/" + id)                     .headers(headers -> headers.setBasicAuth("user", "userpwd"))                     .retrieve()                     .bodyToMono(Student.class);         }              public Flux<Student> getAll() {             return client.get()                     .uri("/students")                     .headers(headers -> headers.setBasicAuth("user", "userpwd"))                     .retrieve()                     .bodyToFlux(Student.class);         }              public Flux<Student> findByName(String name) {             return client.get()                     .uri(uriBuilder -> uriBuilder.path("/students")                     .queryParam("name", name)                     .build())                     .headers(headers -> headers.setBasicAuth("user", "userpwd"))                     .retrieve()                     .bodyToFlux(Student.class);         }              public Mono<Student> create(Student s)  {             return client.post()                     .uri("/students")                     .headers(headers -> headers.setBasicAuth("admin", "adminpwd"))                     .body(Mono.just(s), Student.class)                     .retrieve()                     .bodyToMono(Student.class);         }              public Mono<Student> update(Student student)  {             return client                     .put()                     .uri("/students/" + student.getId())                     .headers(headers -> headers.setBasicAuth("admin", "adminpwd"))                     .body(Mono.just(student), Student.class)                     .retrieve()                     .bodyToMono(Student.class);         }              public Mono<Void> delete(long id) {             return client                     .delete()                     .uri("/students/" + id)                     .headers(headers -> headers.setBasicAuth("admin", "adminpwd"))                     .retrieve()                     .bodyToMono(Void.class);         } }

4. ТЕСТИРОВАНИЕ

Для тестирования вашего реактивного веб-приложения WebFlux предлагает WebTestClient, который поставляется с API, аналогичным WebClient.

Давайте посмотрим, как мы можем протестировать наш StudentController с помощью WebTestClient:

@ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class StudentControllerTest {     @Autowired     WebTestClient webClient;      @Test     @WithMockUser(roles = "USER")     void test_getStudents() {         webClient.get().uri("/students")                 .header(HttpHeaders.ACCEPT, "application/json")                 .exchange()                 .expectStatus().isOk()                 .expectHeader().contentType(MediaType.APPLICATION_JSON)                 .expectBodyList(Student.class);      }      @Test     @WithMockUser(roles = "ADMIN")     void testAddNewStudent() {         Student newStudent = new Student();         newStudent.setName("some name");         newStudent.setAddress("an address");          webClient.post().uri("/students")                 .contentType(MediaType.APPLICATION_JSON)                 .accept(MediaType.APPLICATION_JSON)                 .body(Mono.just(newStudent), Student.class)                 .exchange()                 .expectStatus().isOk()                 .expectHeader().contentType(MediaType.APPLICATION_JSON)                 .expectBody()                 .jsonPath("$.id").isNotEmpty()                 .jsonPath("$.name").isEqualTo(newStudent.getName())                 .jsonPath("$.address").isEqualTo(newStudent.getAddress());     }      ... } 

5. WEBSOCKETS И RSOCKET

5.1 ВЕБ-СОКЕТЫ

В Spring 5 WebSockets также получает дополнительные реактивные возможности. Чтобы создать сервер WebSocket, вы можете создать реализацию WebSocketHandler интерфейса, которая содержит следующий метод:

Mono<Void> handle(WebSocketSession session)

Этот метод вызывается при установке нового соединения WebSocket и позволяет обрабатывать сеанс. Он принимает в WebSocketSession качестве входных данных и возвращает Mono <Void>, чтобы сигнализировать о завершении обработки сеанса приложением.

WebSocketSession имеет методы, определенные для обработки входящих и исходящих потоков:

Flux<WebSocketMessage> receive() Mono<Void> send(Publisher<WebSocketMessage> messages)

Spring WebFlux также предоставляет WebSocketClient реализации для Reactor Netty, Tomcat, Jetty, Undertow и стандартной Java.

Для получения дополнительной информации прочтите следующую главу в документации Spring’s Web on Reactive Stack: WebSockets

5.2 RSOCKET

RSocket — это протокол, моделирующий семантику реактивных потоков по сети. Это двоичный протокол для использования в транспортных потоках байтовых потоков, таких как TCP, WebSockets и Aeron. В качестве введения в эту тему я рекомендую следующий пост в блоге, который написал мой коллега Pär: An introduction to RSocket

А для получения дополнительной информации о поддержке Spring Framework протокола RSocket

6. ПОДВОДЯ ИТОГ…

Это сообщение в блоге продемонстрировало, как WebFlux можно использовать для создания реактивного веб-приложения. В следующем и последнем посте этой серии будет показано, как мы можем сделать весь наш стек приложений полностью неблокирующим, также реализовав неблокирующую связь с базой данных — с помощью R2DBC (Reactive Relational Database Connectivity)!

ССЫЛКИ

Spring Framework documentation — Web on Reactive Stack

Spring Boot Features — The Spring WebFlux framework

Spring Security — Reactive Applications

ссылка на оригинал статьи https://habr.com/ru/post/565056/

Реактивное программирование со Spring, часть 4 R2DBC

Это четвертая часть серии заметок о реактивном программировании, в которой будет представлено введение в R2DBC и описано, как мы можем использовать Spring Data R2DBC для создания полностью реактивного приложения.

1. ЧТО ТАКОЕ R2DBC?

Если вы еще не знакомы с реактивным программированием и реактивными потоками, я рекомендую вам сначала прочитать введение в реактивное программирование, в котором описывается мотивация, лежащая в основе этой парадигмы программирования.

При разработке реактивного приложения, которое должно включать доступ к реляционной базе данных, JDBC не подходит, поскольку это блокирующий API.

R2DBC означает Reactive Relational Database Connectivity и предназначен для обеспечения возможности работы с базами данных SQL с использованием полностью реактивного неблокирующего API. Он основан на спецификации Reactive Streams и в первую очередь представляет собой SPI (Service Provider Interface — интерфейс поставщика услуг) для разработчиков драйверов баз данных и авторов клиентских библиотек, то есть не предназначен для использования непосредственно в коде приложения.

На данный момент существуют реализации драйверов для Oracle, Microsoft SQL Server, MySQL, PostgreSQL, H2, MariaDB и Google Cloud Spanner.

2. SPRING DATA R2DBC

Spring Data предлагает клиент R2DBC — Spring Data R2DBC.

Это не полный ORM, как JPA — он не предлагает таких функций, как кеширование или отложенная загрузка. Но он обеспечивает функциональность отображения объектов и абстракцию репозитория.

Чтобы продемонстрировать, как его можно использовать, давайте вернемся к примеру StudentController из предыдущей заметки о WebFlux:

@RestController @RequestMapping("/students") public class StudentController {      @Autowired     private StudentService studentService;       public StudentController() {     }      @GetMapping("/{id}")     public Mono<ResponseEntity<Student>> getStudent(@PathVariable long id) {         return studentService.findStudentById(id)                 .map(ResponseEntity::ok)                 .defaultIfEmpty(ResponseEntity.notFound().build());     }      @GetMapping     public Flux<Student> listStudents(@RequestParam(name = "name", required = false) String name) {         return studentService.findStudentsByName(name);     }      @PostMapping     public Mono<Student> addNewStudent(@RequestBody Student student) {         return studentService.addNewStudent(student);     }      @PutMapping("/{id}")     public Mono<ResponseEntity<Student>> updateStudent(@PathVariable long id, @RequestBody Student student) {         return studentService.updateStudent(id, student)                 .map(ResponseEntity::ok)                 .defaultIfEmpty(ResponseEntity.notFound().build());     }      @DeleteMapping("/{id}")     public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable long id) {         return studentService.findStudentById(id)                 .flatMap(s ->                         studentService.deleteStudent(s)                                 .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))                 )                 .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));     } }

Этот контроллер содержит несколько различных методов для выполнения действий над учащимися. Мы видим, что он использует StudentService для выполнения этих действий. Теперь мы рассмотрим эту функциональность, лежащую в основе контроллера REST, и то, как мы можем реализовать доступ к базе данных с помощью R2DBC.

2.1 ПРИМЕР РЕАЛИЗАЦИИ

2.1.1 ЗАВИСИМОСТИ

Во-первых, нам нужно добавить в наш проект пару новых зависимостей:

<dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-r2dbc</artifactId>         </dependency>          <dependency>             <groupId>io.r2dbc</groupId>             <artifactId>r2dbc-postgresql</artifactId>             <scope>runtime</scope>         </dependency>         ... </dependencies> 

Нам нужно включить spring-boot-starter-data-r2dbc, чтобы включить spring-data-r2dbc. В этом примере мы будем использовать базу данных postgresql, поэтому нам нужно добавить r2dbc-postgresql, чтобы получить необходимую реализацию драйвера r2dbc.

2.1.2 КОНФИГУРАЦИЯ БАЗЫ ДАННЫХ

Мы можем либо добавить детали подключения к нашей базе данных в application.properties:

spring.r2dbc.url=r2dbc:postgresql://localhost/studentdb spring.r2dbc.username=user spring.r2dbc.password=secret

или используйте конфигурацию на основе Java:

import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  import static io.r2dbc.spi.ConnectionFactoryOptions.*;  @Configuration public class R2DBCConfig {      @Bean     public ConnectionFactory connectionFactory() {         return ConnectionFactories.get(                 ConnectionFactoryOptions.builder()                         .option(DRIVER, "postgresql")                         .option(HOST, "localhost")                         .option(USER, "user")                         .option(PASSWORD, "secret")                         .option(DATABASE, "studentdb")                         .build());     } }

2.1.3 STUDENTSERVICE

Теперь давайте посмотрим на StudentService, который использует StudentController:

@Service public class StudentService {      @Autowired     private StudentRepository studentRepository;      public StudentService() {     }      public Flux<Student> findStudentsByName(String name) {         return (name != null) ? studentRepository.findByName(name) : studentRepository.findAll();     }      public Mono<Student> findStudentById(long id) {         return studentRepository.findById(id);     }      public Mono<Student> addNewStudent(Student student) {         return studentRepository.save(student);     }      public Mono<Student> updateStudent(long id, Student student) {         return studentRepository.findById(id)                 .flatMap(s -> {                     student.setId(s.getId());                     return studentRepository.save(student);                 });      }      public Mono<Void> deleteStudent(Student student) {         return studentRepository.delete(student);     }  }

Как видите, он использует StudentRepository для выполнения различных операций с базой данных над учащимися. Итак, теперь давайте взглянем на этот репозиторий.

2.1.4 STUDENTREPOSITORY

StudentRepository — это реализация ReactiveCrudRepository. Это интерфейс из Spring Data R2DBC для общих операций CRUD с использованием типов Project Reactor. Поскольку ReactiveCrudRepository уже содержит определения для большинства методов репозитория, которые мы используем в StudentService (findAll, findById, save и delete), нам нужно объявить следующее:

public interface StudentRepository extends ReactiveCrudRepository<Student, Long> { <span class="token keyword" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(0, 119, 170);">public</span> Flux<span class="token operator" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(166, 127, 89); background: rgba(255, 255, 255, 0.5);">&lt;</span>Student<span class="token operator" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(166, 127, 89); background: rgba(255, 255, 255, 0.5);">&gt;</span> <span class="token function" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline;">findByName<span class="token punctuation" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(153, 153, 153);">(</span></span>String name<span class="token punctuation" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(153, 153, 153);">)</span><span class="token punctuation" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(153, 153, 153);">;</span>  }

Более сложные запросы также можно определить, добавив аннотацию @Query к методу и указав фактический sql.

Помимо ReactiveCrudRepository, существует также расширение ReactiveSortingRepository, которое предоставляет дополнительные методы для извлечения отсортированных сущностей.

2.1.5 STUDENT

Наконец, давайте посмотрим на реализацию Student:

import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; import org.springframework.data.relational.core.mapping.Table;  @Data @AllArgsConstructor @NoArgsConstructor @Table public class Student {      @Id     private Long id;     private String name;     private String address;  }

Несколько замечаний:

  • Идентификатор объекта должен быть аннотирован аннотацией Spring Data @Id.

  • Аннотации @Table не обязательны, но ее добавление позволяет сканеру пути к классам находить и предварительно обрабатывать объекты для извлечения связанных метаданных. Если вы не добавите его, это произойдет при первом сохранении объекта, что может немного отрицательно повлиять на производительность.

  • Lombok рекомендуется использовать, чтобы избежать шаблонного кода.

  • Есть также некоторые другие рекомендации для обеспечения оптимальной производительности, вы можете найти подробности в справочной документации.

2.1.6 ДРУГИЕ ВАРИАНТЫ ЗАПРОСОВ

Вместо использования репозитория вы можете выполнить инструкцию SQL напрямую, используя DatabaseClient.

Например, чтобы получить всех студентов:

 public Flux<Student> findAll() {         DatabaseClient client = DatabaseClient.create(connectionFactory);         return client.sql("select * from student")                 .map(row -> new Student(row.get("id", Long.class),                         row.get("name", String.class),                         row.get("address", String.class))).all();  }

Также можно использовать R2dbcEntityTemplate для выполнения операций с сущностями. Например:

@Autowired private R2dbcEntityTemplate template; public Flux<Student> findAll() {     return template.select(Student.class).all(); } public Mono<Void> delete(Student student) {     return template.delete(student).then(); } 

2.2 ДРУГИЕ ОСОБЕННОСТИ

2.2.1 ОПТИМИСТИЧЕСКАЯ БЛОКИРОВКА

Подобно JPA, можно применить аннотацию @Version на уровне поля, чтобы гарантировать, что обновления применяются только к строкам с соответствующей версией — если версия не соответствует, генерируется исключение OptimisticLockingFailureException.

2.2.2 ТРАНЗАКЦИИ

Spring поддерживает управление реактивными транзакциями через SPI ReactiveTransactionManager. Аннотации @Transactional можно наносить на реактивных методах возвращающихся типов Publisher и программное управление транзакциями может быть применено с использованием TransactionalOperator.

2.2.3 РЕАКТИВНЫЕ БИБЛИОТЕКИ

Как и WebFlux, Spring Data R2DBC требует Project Reactor в качестве основной зависимости, но он совместим с другими реактивными библиотеками, реализующими спецификацию Reactive Streams. Репозитории существуют также для RxJava2 и RxJava3 (см. обзор пакета).

2.2.4 ПУЛ СОЕДИНЕНИЙ

Для пула соединений доступна библиотека под названием r2dbc-pool. Подробнее о том, как его использовать, читайте здесь.

3. ГОТОВНОСТЬ ДЛЯ ПРОДАКШН

R2DBC — все еще довольно новая технология. Последние версии релиза на данный момент:

  • Спецификация R2DBC: 0.8.5

  • Spring Data R2DBC: 1.3.1

  • r2dbc-postgresql: 0.8.8

  • r2dbc-pool: 0.8.7

Прежде чем принять решение об их использовании для вашего приложения, конечно, рекомендуется более внимательно изучить текущее состояние драйвера базы данных и реализаций пула в соответствии с вашими требованиями. Есть некоторые нерешенные проблемы, которые могут помешать вам сделать этот шаг на данный момент, но улучшения продолжаются.

4. ПОДВОДЯ ИТОГ…

Это сообщение в блоге продемонстрировало, как Spring Data R2DBC можно использовать в приложении WebFlux. Таким образом, мы создали полностью реактивное приложение и подошли к концу этой серии статей о реактивном программировании.

Еще одна интересная инициатива, о которой стоит упомянуть, — это Project Loom. Это проект OpenJDK, который стартовал еще в 2017 году и направлен на обеспечение облегченного параллелизма, включая новый тип потоков Java, которые напрямую не соответствуют выделенным потокам ОС. Такой тип виртуальных потоков было бы намного дешевле создавать и блокировать.

Как вы, возможно, помните из первого сообщения в блоге, ключевыми факторами, лежащими в основе модели реактивного программирования, являются следующие:

  • отход от потока на модель запроса и может обрабатывать больше запросов с небольшим количеством потоков

  • предотвращение блокировки потоков при ожидании завершения операций ввода-вывода

  • упрощение параллельных вызовов

  • поддержка «обратного давления», давая клиенту возможность сообщить серверу, с какой нагрузкой он может справиться

Project Loom кажется очень многообещающим, когда дело доходит до помощи с первыми двумя элементами в этом списке — тогда об этом позаботится сама JVM без какой-либо дополнительной инфраструктуры.

Еще не решено, когда изменения будут внесены в официальный выпуск Java, но двоичные файлы раннего доступа доступны для загрузки.

ССЫЛКИ

R2DBC

Spring Data R2DBC Reference Documentation

r2dbc-postgresql

r2dbc-pool

Project Loom

Going inside Java’s Project Loom and virtual threads

ссылка на оригинал статьи https://habr.com/ru/post/565060/

Реактивное программирование со Spring

Эта серия заметок служит введением в разработку реактивных веб-приложений с использованием Spring Boot, Project Reactor, WebFlux и R2DBC. Это руководство по реактивному миру для новичков, но предполагается, что читатель уже знаком с Java и Spring Boot.

В первой части представлен обзор различных концепций реактивного программирования и их истории. 

Вторая часть представляет собой введение в Project Reactor с большим количеством коротких примеров кода. 

В третьей части рассматривается WebFlux — реактивный веб-фреймворк Spring. 

И, наконец, четвертая часть посвящена R2DBC — Reactive Relational Database Connectivity.

СООБЩЕНИЯ В БЛОГЕ

ссылка на оригинал статьи https://habr.com/ru/post/565000/

Руководство по цвету в UX/UI-дизайне

Советы, теория и опробованные методы от moonlearning.io — для новичков и профессионалов.

Цвет — одна из важнейших составляющих UI-дизайна. Благодаря цвету один и тот же продукт может казаться сложным и крутым или сумасшедшим. И задача дизайнера здесь — не найти и смешать красивые цвета, а создать определенную систему. В этой статье мы начнем с основ и постепенно дойдем до профессионального уровня.

Системы записи цвета. Что, где и когда использовать

Цвета можно записывать по-разному. Самые распространенные методы записи, с которыми вы можете столкнуться, — это Pantone, CMYK, HEX и RGB. В дизайне экранных интерфейсов используются только HEX и RGB, однако важно понимать разницу между всеми системами, ведь вы наверняка будете иметь дело и с онлайн-, и с офлайн-материалами бренда.

Pantone → для ПЕЧАТИ

Палитры Pantone. Источник: https://www.pantone.com/
Палитры Pantone. Источник: https://www.pantone.com/

Точная смесь чернил — один и тот же цвет в любом уголке мира. Печатать в цветах Pantone на домашнем принтере нельзя, но можно использовать официальную цветовую документацию Pantone в качестве справки. В профессиональной типографии для вас могут подобрать и использовать конкретный цвет из палитры Pantone. Поэтому печатать в цветах Pantone обычно дороже — они в основном используются для логотипов или элементов бренда, которые должны одинаково выглядеть на самых разных носителях (остальное делается в CMYK).

CMYK → для ПЕЧАТИ

CMYK
CMYK

Смесь четырех цветов: голубой (cyan), пурпурный (magenta), желтый (yellow) и черный (key — ключевой) — основа для остальных цветов. Именно эти цвета используются в бытовых принтерах и профессиональных типографиях.

RGB → для UI-дизайна

RGB
RGB

RGB — это красный (R), зеленый (G) и синий (B) цвета, составляющие изображение на мониторе. Эта модель относится к непосредственному смешению лучей света, а не к смешению красок (чернил). Цветовой спектр света шире возможного при печати. В силу различий этих моделей цвета при печати и на экране никогда не совпадают на 100%. Это не проблема, если палитры гармоничны сами по себе — просто об этом нужно помнить.

RGB-цвета в UI-дизайне указываются с помощью значений в диапазоне от 0 до 255, например: R = 255, G = 255, B = 255 (или RGB = 255, 255, 255) — белый, а RGB = 0, 0, 0 — черный.

RGBA → для UI-дизайна

Это тот же RGB, но с добавлением альфа-канала — A, который отвечает за прозрачность и принимает значения от 0,0 (полная прозрачность) до 1,0 (полная непрозрачность).

То есть, например, RGBA = 255, 255, 255, 0,5 — это белый с прозрачностью 50%.

HEX → для UI-дизайна ​

Используйте именно эту модель! RGB — это прекрасно, но его неудобно использовать, поэтому применяется HEX — краткая форма записи, которая в точности соответствует своему RBG-аналогу. Благодаря строковому формату HEX-значения проще использовать, копировать, вставлять и показывать другим.

В этой модели цвет задается тремя двузначными шестнадцатеричными значениями с решеткой спереди: первое значение соответствует каналу R, второе — G, третье — B (поэтому RGB и HEX идентичны).

Примечание. Цвета при печати и на экране никогда идеально не совпадают, поскольку они создаются разными способами: при печати смешиваются чернила, а на экране — световые лучи. Главное здесь — чтобы каждая палитра была гармонична сама по себе.

Преобразование цветов для печати и отображения на экране

Иногда бывает необходимо «печатный» цвет преобразовать для использования в интерфейсе. Ну или вы просто от широты душевной хотите помочь дизайнерам типографии с обратным преобразованием.

Есть много онлайн-конвертеров, но среди них особо выделяется официальный конвертер Pantone, который использует так называемый цветовой мост Pantone. Его цифровую версию можно найти на странице поиска цветов на сайте Pantone.

Инструмент поиска цвета на сайте Pantone
Инструмент поиска цвета на сайте Pantone

Здесь можно ввести HEX, RGB или CMYK — и вы получите соответствующий цвет в системе Pantone. Нажав на него, вы узнаете все необходимые цветовые значения. Чтобы преобразовать Pantone в HEX, откройте раздел «Pantone to Pantone» в меню слева (звучит не очень логично, но так оно работает).

​ Совет. Если вы переводите экранные цвета в печатные, полезно иметь под рукой физическую палитру Pantone, чтобы сравнить цвета перед занесением их в документацию — у дизайнеров или в типографии такая обязательно найдется.

Сколько цветов использовать в UI-дизайне?

Концепция «три цвета» также используется в дизайне интерьеров
Концепция «три цвета» также используется в дизайне интерьеров

В UI-дизайне технических ограничений на количество цветов нет, но лучше всего ограничиться двумя-тремя.

В вашем распоряжении будут вариации этих цветов (подробнее об этом — далее). Чуть ниже я расскажу, почему при создании яркого дизайна гораздо важнее сочетание цветов, а не их количество.

При этом, если у вас есть идея, для которой нужно много цветов, — смело реализуйте ее: некоторые правила можно и нужно нарушать.

Как выбирать, смешивать и сочетать цвета в UI-дизайне

Если у вас природное чутье на подбор и смешивание цветов — смело полагайтесь на него. Однако если при выборе цветов вы не чувствуете себя уверенно, можете воспользоваться специальные приемами.

Цветовой круг RGB
Цветовой круг RGB

Я не считаю цветовые ассоциации (синий — успокаивает, красный — яркий) убедительной концепцией, поскольку восприятие цвета зависит от культуры. Для создания определенного настроения важнее то, как именно вы смешиваете цвета.

Здесь мы используем цветовой RGB-круг с 12 сегментами (которые представляют собой первичные, вторичные и третичные цвета). Я для простоты разбила его на секции — но в приложениях для дизайна обычно для выбора цвета будет предложен цветовой круг с мягким переходом цветов. У Adobe также есть отличный инструмент, который поможет с подбором цвета.

Однотонные цвета

Однотонные цвета
Однотонные цвета

Выбрав один цвет, двигайтесь к центру круга — и вы получите красивый оттенок. Такое цветовое сочетание выглядит аккуратно и продуманно.

Близкие по оттенку цвета

Близкие по оттенку цвета
Близкие по оттенку цвета

В этом случае выбираются цвета, находящиеся рядом, — двигаться можно в любом направлении цветового круга. Хороший результат дают цвета, расположенные в пределах 90°. Такой подход добавляет динамики и позволяет сохранить изящество.

Дополняющие цвета

Дополняющие цвета
Дополняющие цвета

Если нужно что-то очень яркое — это ваш вариант. Сначала выберите основной цвет, а затем добавьте дополняющий — с противоположной стороны круга. Добавляя однотонные цвета, можно получить красивое сочетание.

Раздельные дополняющие цвета

Раздельные дополняющие цвета
Раздельные дополняющие цвета

К дополняющим цветам можно добавить близкий по оттенку цвет — тогда палитра будет еще ярче: такой подход называется «раздельные дополняющие цвета».

Эти три подхода помогут сформировать палитру, но есть и другие методы — например, триадный и тетрадный, — на которых тоже можно поучиться выбирать цвета.

Поиграйте с оттенком и другими параметрами

Подобрав нужные цвета, необязательно ограничивать себя только ими — это основа, на базе которой могут быть различные вариации. При этом важно понимать, какие есть способы изменения цветов, — пробуйте, но не переусердствуйте.

Оттенок, осветление, затенение и тон
Оттенок, осветление, затенение и тон

Оттенок (hue) → чистый цвет, без модификаций. Обходя цветовой круг по внешней части, мы меняем именно оттенок.

Осветление (tint) → оттенок с добавлением белого.

Затенение (shade) → оттенок с добавлением черного.

Тон (tone) → оттенок с добавлением серого.

Вариации цветов

Чтобы добавить разнообразия в палитру, следует использовать вариации цветов. Для этого можно изменять оттенок вручную или применить специальный инструмент — например, цветовые палитры «материального дизайна».

https://material.io/design/color/the-color-system
https://material.io/design/color/the-color-system

​Примечание. Буква «P» в круге показывает, как будет читаться цвет текста на таком фоне. Белая «P» означает, что на таком фоне читается белый текст, черная — что читаться будет черный текст.

Укажите свое HEX-значение, и инструмент предложит вам различные вариации — можете использовать столько цветов, сколько нужно. У меня обычно три-пять вариаций цвета, но и девять — это вполне нормально. Выбранные цвета необязательно должны находиться рядом — подбирайте подходящий к дизайну контраст и отбрасывайте лишнее. Если вас интересует численное обозначение на рисунке — об этом в следующем разделе, посвященном именованию.

Вариации цветов
Вариации цветов

Как правильно называть цвета

Именование цветов

Итак, цвета и их вариации подобраны — теперь нужно задокументировать их в таблице стилей и (или) дизайн-системе.

Не давайте названия по самим цветам (например, красный и синий) — используйте что-то общее, поскольку со временем цвета могут меняться.

Именование цветов
Именование цветов

Неважно, какие названия вы используете, — главное, чтобы они были информативными и последовательными, например, фон, оттенки серого и т. д. можно назвать нейтральными цветами. Затем могут идти первичный и вторичный цвет (причем у меня вторичный цвет обычно используется для выделения).

Именование вариаций

Называть вариации следует по первичному или вторичному цвету — добавляя номера, которые помогут их различать. В палитре «материального дизайна» они формируются с шагом 100 — мне такой поход тоже нравится. Но с таким же успехом можно использовать и шаг 10.

Именование вариаций
Именование вариаций

Для основного цвета я люблю использовать значение 500, на базе которого при необходимости создаю другие вариации.

Однако не используйте шаг в единицу (1, 2, 3, 4 и т. д.): если позже понадобится добавить промежуточные цвета, это будет неудобно. Оставьте себе некоторое пространство для маневра.

Цветовая таблица стилей с системными цветами
Цветовая таблица стилей с системными цветами

​ Не забывайте о системных цветах для ошибок, предупреждений, информационных сообщений и успешно выполненных операций — обычно это красный, оранжевый, синий и зеленый. При необходимости эти цвета можно подкорректировать, чтобы они соответствовали бренду, — но цвет ошибки всегда должен быть красным, ВСЕГДА!

Читаемый цвет переднего плана

Следует также добавить и цвет переднего плана (on-color) — то есть, цвет, используемый поверх другого цвета (например, для типографики и значков). Продуманное использование этого цвета дает два основных преимущества:

Добавление цвета переднего плана
Добавление цвета переднего плана
  1. Вы сможете получать напоминание о проверке на читаемость по цветовому контрасту — с помощью плагина в ПО для дизайна либо онлайн-инструмента для проверки контраста (например, вот этого), что очень удобно. Вы, кстати, обязаны обеспечить доступность страницы согласно нормам WCAG.

Проверка доступности цветового контраста
Проверка доступности цветового контраста

2. Допустим, вторичный цвет у вас — темно-серый, и вы не определили цвет переднего плана. Часто этот цвет будет использоваться как переменная и применяться в том числе в качестве цвета текста — и если вы измените его, скажем, на ярко-синий, придется менять весь текст в файле дизайна — возможно, даже код. Так что сделайте так, чтобы всё было просто, понятно и на своих местах.

Попробуйте сами в Фигме!

Здесь можно скачать бесплатную цветовую таблицу стилей для Фигмы — поиграйте с цветами.

Цветовая таблица стилей для Фигмы
Цветовая таблица стилей для Фигмы

Правило распределения цветов «60-30-10»

Распределение цветов
Распределение цветов

Кроме того, какие цвета используются, важно также то, как они используются, и здесь очень пригодится правило «60-30-10»: основной цвет используется в 60% случаев, первичный — в 30%, вторичный — в 10%, для выделения (например, для призывов к действию и кнопок).

Основной цвет — 60%, первичный — 30% и 10% — вторичный цвет для призывов к действию.

Понятно, что эти проценты берутся скорее «по ощущениям» и не являются результатом точных измерений. Под цветом здесь можно понимать как один цвет, так и цвет со всеми его вариациями. Это правило можно подстроить под себя — но призывы к действию должны выделяться и быть единообразными.

Правило «60-30-10»
Правило «60-30-10»

Как видите, в результате получается вполне хорошая цветовая основа и уделяется должное внимание призывам к действию.

Спасибо!

Если статья понравилась, заходите на moonlearning.io — я там преподаю UX/UI-дизайн в видеоформате.


О переводчике

Перевод статьи выполнен в Alconost.

Alconost занимается локализацией игр, приложений и сайтов на 70 языков. Переводчики-носители языка, лингвистическое тестирование, облачная платформа с API, непрерывная локализация, менеджеры проектов 24/7, любые форматы строковых ресурсов.

Мы также делаем рекламные и обучающие видеоролики — для сайтов, продающие, имиджевые, рекламные, обучающие, тизеры, эксплейнеры, трейлеры для Google Play и App Store.

ссылка на оригинал статьи https://habr.com/ru/company/alconost/blog/565438/

Windows must die

image

Об авторе: Joel — ИТ-журналист с 19-летним стажем и создатель Deep Space Nine Upscale Project (DS9UP).

Согласно заявлениям Microsoft, Windows 11 сделает существенный шаг назад по сравнению с Windows 10. В частности, Windows 11 Home теперь будет требовать как доступ к Интернету, так и учетную запись Microsoft для настройки ПК.

Скажу как рецензент — это требование смехотворно. Я регулярно строю и уничтожаю тестовые прототипы. Я не хочу создавать одноразовые аккаунты и подключаться к интернету каждую итерацию. Если я пытаюсь протестировать поведение двух конкретных версий Windows, система, которая заставляет меня обновляться до последней версии в качестве условия установки, буквально мешает мне выполнять свою работу.

Однако я признаю, что у меня узкий нишевый случай. Хотя я нахожу эти ограничения раздражающими, они не являются проблемой для обычного пользователя ПК. И это не основная причина, по которой я не собираюсь пользоваться учетной записью Microsoft, чтобы использовать свой компьютер.

Компьютер — это не Интернет

Я готов смириться с тем фактом, что это может быть артефактом того времени, в котором я вырос. Для меня мой компьютер и «Интернет» — две совершенно разные вещи. Я подключаюсь к интернету, чтобы загружать файлы, читать новости и смотреть контент, но это лишь часть того, чем я занимаюсь на своем ПК. Использование онлайн-учетной записи для входа на мой ПК стирает разницу. Я признаю, что это мои личные проблемы, но для меня эта разница важна. На самом деле это очень важно. Я не хочу, чтобы моя локальная учетная запись Windows была синонимом онлайн-входа.

Но это не единственная моя причина.

Другая причина, по которой я не буду использовать онлайн-аккаунт, заключается в том, что Microsoft не перестанет заставлять меня использовать его.

Я не обвиняю Microsoft в том, что она шпионила за пользователями или злоупотребляла своими возможностями сбора данных. Хотя изначально в Windows 10 были некоторые проблемы с телеметрией, компания решила их в более поздних обновлениях. Не было никаких скандалов с конфиденциальностью или безопасностью, вызванных использованием учетной записи Microsoft вместо локальной учетной записи. Насколько мне известно, использование учетной записи Microsoft вместо локальной учетной записи не подвергает риску вашу личную конфиденциальность или безопасность.

Моя проблема с учетными записями Microsoft и нелокальными учетными записями заключается в следующем: с момента появления Windows 10 Microsoft использовала все известные дарк паттерны, известные человечеству. Они пускали пыль в глаза и мешали создать локальную учетную записи, используя непонятные формулировки. Они запускали установщики, которые скрывали возможность создания локальной учетной записи, если вы не были в автономном режиме при запуске установки. Они внедряли инструменты «Get Windows 10», которые были настолько агрессивными, что действовали больше как вредоносное ПО, чем продукт, созданный компанией из списка Fortune 500.

image

image

image

image

image

Зачем пользователям Windows 7 эти забавы? Клиенты Windows 8 тоже не остались без внимания.

Меня не запугать, чтобы принять онлайн-учетную запись в качестве локального входа, потому что Microsoft сочла удобным газлайтить своих пользователей и принуждать к соглашению. Если бы Microsoft предложила эту опцию в качестве фичи в Windows 10 и оставила меня в покое, я мог бы в конце концов переключиться. Но этого не произошло. Для Microsoft важно, чтобы вы использовали онлайн-аккаунт.

Вот почему я никогда им не воспользуюсь.

Я не знаю, почему Microsoft хочет, чтобы все использовали онлайн-вход. Я не знаю, почему Microsoft считала, что имеет право относиться к своим клиентам так, как она поступила с операцией Get Windows 10 или ее шестилетней борьбой за то, чтобы подтолкнуть всех к использованию онлайн-аккаунтов. Но я не буду играть в эту игру. Я не буду идти на компромисс и соглашаться: «Ну, вы действительно можете создать локальную учетную запись после того, как войдете в систему в первый раз и переключитесь на нее». Значения по умолчанию имеют огромную силу, и Microsoft это знает.

Возможность создать логин Windows через онлайн-аккаунт — это здорово. Это требование является недопустимым вторжением в то, что должно быть персональным компьютером пользователя. Мне не интересно становиться соучастникм Greater Microsoft Data Conglomerate в любом качестве, помимо уровня, в котором я уже вынужден участвовать, и я не буду поощрять своим согласием то, что я считаю нарушением личных границ (borderline abusive behavior). Компании, которые намерены обращаться с вашими данными этично, не гонятся за ними, разбрызгивая слюни, как пёс, бегущий за машиной.

По словам Сатья Наделлы, «Windows всегда олицетворяла суверенитет для создателей и сервис для потребителей». Может, когда-то так и было. Сегодня «сервис для потребителей», по-видимому, означает «сервис для потребителей, если они делают одобренный Microsoft выбор, используют услуги и продукты Microsoft, не хотят откладывать обновления и не возражают, чтобы их компьютеры перезагружались из-за них без предупреждения."

Не такого сервиса я ожидал, когда стал изучать MS-DOS 3.1 около тридцати лет назад. Не такой сервис я желал, когда купил свой первый компьютер с Windows 98 SE. Не такой сервис я хочу сегодня и не соглашаюсь с тем что происходит.

ссылка на оригинал статьи https://habr.com/ru/company/macloud/blog/565134/