├── .doc └── spring-webflux.png ├── .gitignore ├── 01-external-services ├── external-services-instructions.md └── external-services.jar ├── 02-webflux-playground ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── vinsguru │ │ │ └── playground │ │ │ ├── WebFluxPlaygroundApplication.java │ │ │ ├── sec01 │ │ │ ├── Product.java │ │ │ ├── ReactiveWebController.java │ │ │ └── TraditionalWebController.java │ │ │ ├── sec02 │ │ │ ├── dto │ │ │ │ └── OrderDetails.java │ │ │ ├── entity │ │ │ │ ├── Customer.java │ │ │ │ ├── CustomerOrder.java │ │ │ │ └── Product.java │ │ │ └── repository │ │ │ │ ├── CustomerOrderRepository.java │ │ │ │ ├── CustomerRepository.java │ │ │ │ └── ProductRepository.java │ │ │ ├── sec03 │ │ │ ├── controller │ │ │ │ └── CustomerController.java │ │ │ ├── dto │ │ │ │ └── CustomerDto.java │ │ │ ├── entity │ │ │ │ └── Customer.java │ │ │ ├── mapper │ │ │ │ └── EntityDtoMapper.java │ │ │ ├── repository │ │ │ │ └── CustomerRepository.java │ │ │ └── service │ │ │ │ └── CustomerService.java │ │ │ ├── sec04 │ │ │ ├── advice │ │ │ │ └── ApplicationExceptionHandler.java │ │ │ ├── controller │ │ │ │ └── CustomerController.java │ │ │ ├── dto │ │ │ │ └── CustomerDto.java │ │ │ ├── entity │ │ │ │ └── Customer.java │ │ │ ├── exceptions │ │ │ │ ├── ApplicationExceptions.java │ │ │ │ ├── CustomerNotFoundException.java │ │ │ │ └── InvalidInputException.java │ │ │ ├── mapper │ │ │ │ └── EntityDtoMapper.java │ │ │ ├── repository │ │ │ │ └── CustomerRepository.java │ │ │ ├── service │ │ │ │ └── CustomerService.java │ │ │ └── validator │ │ │ │ └── RequestValidator.java │ │ │ ├── sec05 │ │ │ ├── advice │ │ │ │ └── ApplicationExceptionHandler.java │ │ │ ├── controller │ │ │ │ └── CustomerController.java │ │ │ ├── dto │ │ │ │ └── CustomerDto.java │ │ │ ├── entity │ │ │ │ └── Customer.java │ │ │ ├── exceptions │ │ │ │ ├── ApplicationExceptions.java │ │ │ │ ├── CustomerNotFoundException.java │ │ │ │ └── InvalidInputException.java │ │ │ ├── filter │ │ │ │ ├── AuthenticationWebFilter.java │ │ │ │ ├── AuthorizationWebFilter.java │ │ │ │ ├── Category.java │ │ │ │ └── FilterErrorHandler.java │ │ │ ├── mapper │ │ │ │ └── EntityDtoMapper.java │ │ │ ├── repository │ │ │ │ └── CustomerRepository.java │ │ │ ├── service │ │ │ │ └── CustomerService.java │ │ │ └── validator │ │ │ │ └── RequestValidator.java │ │ │ ├── sec06 │ │ │ ├── assignment │ │ │ │ └── CalculatorAssignment.java │ │ │ ├── config │ │ │ │ ├── ApplicationExceptionHandler.java │ │ │ │ ├── CustomerRequestHandler.java │ │ │ │ └── RouterConfiguration.java │ │ │ ├── dto │ │ │ │ └── CustomerDto.java │ │ │ ├── entity │ │ │ │ └── Customer.java │ │ │ ├── exceptions │ │ │ │ ├── ApplicationExceptions.java │ │ │ │ ├── CustomerNotFoundException.java │ │ │ │ └── InvalidInputException.java │ │ │ ├── mapper │ │ │ │ └── EntityDtoMapper.java │ │ │ ├── repository │ │ │ │ └── CustomerRepository.java │ │ │ ├── service │ │ │ │ └── CustomerService.java │ │ │ └── validator │ │ │ │ └── RequestValidator.java │ │ │ ├── sec07 │ │ │ └── note.txt │ │ │ ├── sec08 │ │ │ ├── controller │ │ │ │ └── ProductController.java │ │ │ ├── dto │ │ │ │ ├── ProductDto.java │ │ │ │ └── UploadResponse.java │ │ │ ├── entity │ │ │ │ └── Product.java │ │ │ ├── mapper │ │ │ │ └── EntityDtoMapper.java │ │ │ ├── repository │ │ │ │ └── ProductRepository.java │ │ │ └── service │ │ │ │ └── ProductService.java │ │ │ └── sec09 │ │ │ ├── config │ │ │ └── ApplicationConfig.java │ │ │ ├── controller │ │ │ └── ProductController.java │ │ │ ├── dto │ │ │ ├── ProductDto.java │ │ │ └── UploadResponse.java │ │ │ ├── entity │ │ │ └── Product.java │ │ │ ├── mapper │ │ │ └── EntityDtoMapper.java │ │ │ ├── repository │ │ │ └── ProductRepository.java │ │ │ └── service │ │ │ ├── DataSetupService.java │ │ │ └── ProductService.java │ └── resources │ │ ├── application.properties │ │ ├── logback.xml │ │ ├── sql │ │ └── data.sql │ │ └── static │ │ └── index.html │ └── test │ └── java │ └── com │ └── vinsguru │ └── playground │ └── tests │ ├── sec02 │ ├── AbstractTest.java │ ├── Lec01CustomerRepositoryTest.java │ ├── Lec02ProductRepositoryTest.java │ ├── Lec03CustomerOrderRepositoryTest.java │ └── Lec04DatabaseClientTest.java │ ├── sec03 │ └── CustomerServiceTest.java │ ├── sec04 │ └── CustomerServiceTest.java │ ├── sec05 │ └── CustomerServiceTest.java │ ├── sec06 │ ├── CalculatorTest.java │ └── CustomerServiceTest.java │ ├── sec07 │ ├── AbstractWebClient.java │ ├── Lec01MonoTest.java │ ├── Lec02FluxTest.java │ ├── Lec03PostTest.java │ ├── Lec04HeaderTest.java │ ├── Lec05ErrorResponseTest.java │ ├── Lec06QueryParamsTest.java │ ├── Lec07BasicAuthTest.java │ ├── Lec08BearerAuthTest.java │ ├── Lec09ExchangeFilterTest.java │ └── dto │ │ ├── CalculatorResponse.java │ │ └── Product.java │ ├── sec08 │ ├── FileWriter.java │ ├── ProductClient.java │ └── ProductsUploadDownloadTest.java │ ├── sec09 │ └── ServerSentEventsTest.java │ └── sec10 │ ├── AbstractWebClient.java │ ├── Lec01HttpConnectionPoolingTest.java │ ├── Lec02Http2Test.java │ └── dto │ └── Product.java ├── 03-final-project └── trade-platofrm │ ├── aggregator-service │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── vinsguru │ │ │ │ └── aggregator │ │ │ │ ├── AggregatorServiceApplication.java │ │ │ │ ├── advice │ │ │ │ └── ApplicationExceptionHandler.java │ │ │ │ ├── client │ │ │ │ ├── CustomerServiceClient.java │ │ │ │ └── StockServiceClient.java │ │ │ │ ├── config │ │ │ │ └── ServiceClientsConfig.java │ │ │ │ ├── controller │ │ │ │ ├── CustomerPortfolioController.java │ │ │ │ └── StockPriceStreamController.java │ │ │ │ ├── domain │ │ │ │ ├── Ticker.java │ │ │ │ └── TradeAction.java │ │ │ │ ├── dto │ │ │ │ ├── CustomerInformation.java │ │ │ │ ├── Holding.java │ │ │ │ ├── PriceUpdate.java │ │ │ │ ├── StockPriceResponse.java │ │ │ │ ├── StockTradeRequest.java │ │ │ │ ├── StockTradeResponse.java │ │ │ │ └── TradeRequest.java │ │ │ │ ├── exceptions │ │ │ │ ├── ApplicationExceptions.java │ │ │ │ ├── CustomerNotFoundException.java │ │ │ │ └── InvalidTradeRequestException.java │ │ │ │ ├── service │ │ │ │ └── CustomerPortfolioService.java │ │ │ │ └── validator │ │ │ │ └── RequestValidator.java │ │ └── resources │ │ │ ├── application.properties │ │ │ ├── logback.xml │ │ │ └── static │ │ │ └── index.html │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── vinsguru │ │ │ └── aggregator │ │ │ └── tests │ │ │ ├── AbstractIntegrationTest.java │ │ │ ├── CustomerInformationTest.java │ │ │ ├── CustomerTradeTest.java │ │ │ └── StockPriceStreamTest.java │ │ └── resources │ │ ├── customer-service │ │ ├── customer-information-200.json │ │ ├── customer-information-404.json │ │ ├── customer-trade-200.json │ │ └── customer-trade-400.json │ │ └── stock-service │ │ ├── stock-price-200.json │ │ └── stock-price-stream-200.jsonl │ └── customer-service │ ├── .gitignore │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── vinsguru │ │ │ └── customerportfolio │ │ │ ├── CustomerServiceApplication.java │ │ │ ├── advice │ │ │ └── ApplicationExceptionHandler.java │ │ │ ├── controller │ │ │ └── CustomerController.java │ │ │ ├── domain │ │ │ ├── Ticker.java │ │ │ └── TradeAction.java │ │ │ ├── dto │ │ │ ├── CustomerInformation.java │ │ │ ├── Holding.java │ │ │ ├── StockTradeRequest.java │ │ │ └── StockTradeResponse.java │ │ │ ├── entity │ │ │ ├── Customer.java │ │ │ └── PortfolioItem.java │ │ │ ├── exceptions │ │ │ ├── ApplicationExceptions.java │ │ │ ├── CustomerNotFoundException.java │ │ │ ├── InsufficientBalanceException.java │ │ │ └── InsufficientSharesException.java │ │ │ ├── mapper │ │ │ └── EntityDtoMapper.java │ │ │ ├── repository │ │ │ ├── CustomerRepository.java │ │ │ └── PortfolioItemRepository.java │ │ │ └── service │ │ │ ├── CustomerService.java │ │ │ └── TradeService.java │ └── resources │ │ ├── application.properties │ │ ├── logback.xml │ │ └── sql │ │ └── data.sql │ └── test │ └── java │ └── com │ └── vinsguru │ └── customerportfolio │ └── tests │ └── CustomerServiceApplicationTests.java ├── 04-r2dbc-vs-jdbc └── reactive-vs-traditional-postgres │ ├── .gitignore │ ├── Makefile │ ├── customer.sql │ ├── docker-compose.yaml │ ├── pom.xml │ ├── reactive │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── vinsguru │ │ │ ├── ReactiveApp.java │ │ │ ├── entity │ │ │ └── Customer.java │ │ │ ├── repository │ │ │ └── CustomerRepository.java │ │ │ └── runner │ │ │ ├── EfficiencyTestRunner.java │ │ │ └── ThroughputTestRunner.java │ │ └── resources │ │ └── application.properties │ └── traditional │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── com │ │ └── vinsguru │ │ ├── TraditionalApp.java │ │ ├── entity │ │ └── Customer.java │ │ ├── repository │ │ └── CustomerRepository.java │ │ └── runner │ │ ├── EfficiencyTestRunner.java │ │ └── ThroughputTestRunner.java │ └── resources │ └── application.properties ├── 05-additional-resources └── 01-r2dbc.md ├── 06-old-projects ├── order-service │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── vinsguru │ │ │ │ └── orderservice │ │ │ │ ├── OrderServiceApplication.java │ │ │ │ ├── client │ │ │ │ ├── ProductClient.java │ │ │ │ └── UserClient.java │ │ │ │ ├── controller │ │ │ │ └── PurchaseOrderController.java │ │ │ │ ├── dto │ │ │ │ ├── OrderStatus.java │ │ │ │ ├── ProductDto.java │ │ │ │ ├── PurchaseOrderRequestDto.java │ │ │ │ ├── PurchaseOrderResponseDto.java │ │ │ │ ├── RequestContext.java │ │ │ │ ├── TransactionRequestDto.java │ │ │ │ ├── TransactionResponseDto.java │ │ │ │ ├── TransactionStatus.java │ │ │ │ └── UserDto.java │ │ │ │ ├── entity │ │ │ │ └── PurchaseOrder.java │ │ │ │ ├── repository │ │ │ │ └── PurchaseOrderRepository.java │ │ │ │ ├── service │ │ │ │ ├── OrderFulfillmentService.java │ │ │ │ └── OrderQueryService.java │ │ │ │ └── util │ │ │ │ └── EntityDtoUtil.java │ │ └── resources │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── com │ │ └── vinsguru │ │ └── orderservice │ │ └── OrderServiceApplicationTests.java ├── product-service │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── vinsguru │ │ │ │ └── productservice │ │ │ │ ├── ProductServiceApplication.java │ │ │ │ ├── config │ │ │ │ └── SinkConfig.java │ │ │ │ ├── controller │ │ │ │ ├── ProductController.java │ │ │ │ └── ProductStreamController.java │ │ │ │ ├── dto │ │ │ │ └── ProductDto.java │ │ │ │ ├── entity │ │ │ │ └── Product.java │ │ │ │ ├── repository │ │ │ │ └── ProductRepository.java │ │ │ │ ├── service │ │ │ │ ├── DataSetupService.java │ │ │ │ └── ProductService.java │ │ │ │ └── util │ │ │ │ └── EntityDtoUtil.java │ │ └── resources │ │ │ ├── application.properties │ │ │ └── static │ │ │ └── index.html │ │ └── test │ │ └── java │ │ └── com │ │ └── vinsguru │ │ └── productservice │ │ └── ProductServiceApplicationTests.java ├── user-service │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── vinsguru │ │ │ │ └── userservice │ │ │ │ ├── UserServiceApplication.java │ │ │ │ ├── controller │ │ │ │ ├── UserController.java │ │ │ │ └── UserTransactionController.java │ │ │ │ ├── dto │ │ │ │ ├── TransactionRequestDto.java │ │ │ │ ├── TransactionResponseDto.java │ │ │ │ ├── TransactionStatus.java │ │ │ │ └── UserDto.java │ │ │ │ ├── entity │ │ │ │ ├── User.java │ │ │ │ └── UserTransaction.java │ │ │ │ ├── repository │ │ │ │ ├── UserRepository.java │ │ │ │ └── UserTransactionRepository.java │ │ │ │ ├── service │ │ │ │ ├── DataSetupService.java │ │ │ │ ├── TransactionService.java │ │ │ │ └── UserService.java │ │ │ │ └── util │ │ │ │ └── EntityDtoUtil.java │ │ └── resources │ │ │ ├── application.properties │ │ │ └── h2 │ │ │ └── init.sql │ │ └── test │ │ └── java │ │ └── com │ │ └── vinsguru │ │ └── userservice │ │ └── UserServiceApplicationTests.java └── webflux-demo │ ├── .gitignore │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── vinsguru │ │ │ └── webfluxdemo │ │ │ ├── WebfluxDemoApplication.java │ │ │ ├── config │ │ │ ├── CalculatorHandler.java │ │ │ ├── CalculatorRouterConfig.java │ │ │ ├── RequestHandler.java │ │ │ └── RouterConfig.java │ │ │ ├── controller │ │ │ ├── MathController.java │ │ │ ├── ParamsController.java │ │ │ ├── ReactiveMathController.java │ │ │ └── ReactiveMathValidationController.java │ │ │ ├── dto │ │ │ ├── InputFailedValidationResponse.java │ │ │ ├── MultiplyRequestDto.java │ │ │ └── Response.java │ │ │ ├── exception │ │ │ └── InputValidationException.java │ │ │ ├── exceptionhandler │ │ │ └── InputValidationHandler.java │ │ │ └── service │ │ │ ├── MathService.java │ │ │ ├── ReactiveMathService.java │ │ │ └── SleepUtil.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── vinsguru │ └── webfluxdemo │ ├── config │ └── WebClientConfig.java │ ├── webclient │ ├── BaseTest.java │ ├── Lec01GetSingleResponseTest.java │ ├── Lec02GetMultiResponseTest.java │ ├── Lec03PostRequestTest.java │ ├── Lec04HeadersTest.java │ ├── Lec05BadRequestTest.java │ ├── Lec06ExchangeTest.java │ ├── Lec07QueryParamsTest.java │ ├── Lec08AttributesTest.java │ └── Lec09AssignmentTest.java │ └── webtestclient │ ├── Lec01SimpleWebTestClientTest.java │ ├── Lec02ControllerGetTest.java │ ├── Lec03ControllerPostTest.java │ ├── Lec04ErrorHandlingTest.java │ └── Lec05RouterFunctionTest.java └── README.md /.doc/spring-webflux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinsguru/spring-webflux-course/94cb3ab0e1e82ae8eb3f751d4a9d6074bc4047cf/.doc/spring-webflux.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Java template 2 | # Compiled class file 3 | **/*.class 4 | 5 | # Log file 6 | **/*.log 7 | 8 | # BlueJ files 9 | **/*.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | **/.mtj.tmp/ 13 | 14 | # Package Files # 15 | **/*.jar 16 | !01-external-services/external-services.jar 17 | **/*.war 18 | **/*.nar 19 | **/*.ear 20 | **/*.zip 21 | **/*.tar.gz 22 | **/*.rar 23 | 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | **/hs_err_pid* 26 | 27 | ### Maven template 28 | **/target/ 29 | **/pom.xml.tag 30 | **/pom.xml.releaseBackup 31 | **/pom.xml.versionsBackup 32 | **/pom.xml.next 33 | **/release.properties 34 | **/dependency-reduced-pom.xml 35 | **/buildNumber.properties 36 | **/.mvn/timing.properties 37 | **/.mvn/wrapper/maven-wrapper.jar 38 | 39 | **/*.iml 40 | 41 | **/.idea/ 42 | **/.DS_STORE 43 | 44 | **/HELP.md 45 | **/.mvn/ 46 | **/mvnw 47 | **/mvnw.cmd 48 | **/node_modules/ -------------------------------------------------------------------------------- /01-external-services/external-services-instructions.md: -------------------------------------------------------------------------------- 1 | # External Services 2 | 3 | We will be using below jar file to demonstrate other service dependencies in our Microservices architecture. 4 | 5 | Please download [this jar](https://github.com/vinsguru/spring-webflux-course/raw/master/01-external-services/external-services.jar) 6 | 7 | ## How To Run 8 | 9 | ```bash 10 | java -jar external-services.jar 11 | ``` 12 | - It uses port `7070` by default. 13 | 14 | ## To change the port 15 | 16 | ```bash 17 | java -jar external-services.jar --server.port=8080 18 | ``` 19 | -------------------------------------------------------------------------------- /01-external-services/external-services.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinsguru/spring-webflux-course/94cb3ab0e1e82ae8eb3f751d4a9d6074bc4047cf/01-external-services/external-services.jar -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/WebFluxPlaygroundApplication.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; 6 | 7 | @SpringBootApplication(scanBasePackages = "com.vinsguru.playground.${sec}") 8 | @EnableR2dbcRepositories(basePackages = "com.vinsguru.playground.${sec}") 9 | public class WebFluxPlaygroundApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(WebFluxPlaygroundApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec01/Product.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec01; 2 | 3 | public record Product(Integer id, 4 | String description, 5 | Integer price) { 6 | } 7 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec01/ReactiveWebController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec01; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import org.springframework.web.reactive.function.client.WebClient; 10 | import reactor.core.publisher.Flux; 11 | 12 | @RestController 13 | @RequestMapping("reactive") 14 | public class ReactiveWebController { 15 | 16 | private static final Logger log = LoggerFactory.getLogger(ReactiveWebController.class); 17 | private final WebClient webClient = WebClient.builder() 18 | .baseUrl("http://localhost:7070") 19 | .build(); 20 | 21 | @GetMapping("products") 22 | public Flux getProducts() { 23 | return this.webClient.get() 24 | .uri("/demo01/products") 25 | .retrieve() 26 | .bodyToFlux(Product.class) 27 | .doOnNext(p -> log.info("received: {}", p)); 28 | } 29 | 30 | @GetMapping(value = "products/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 31 | public Flux getProductsStream() { 32 | return this.webClient.get() 33 | .uri("/demo01/products") 34 | .retrieve() 35 | .bodyToFlux(Product.class) 36 | .doOnNext(p -> log.info("received: {}", p)); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec02/dto/OrderDetails.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec02.dto; 2 | 3 | import java.time.Instant; 4 | import java.util.UUID; 5 | 6 | public record OrderDetails(UUID orderId, 7 | String customerName, 8 | String productName, 9 | Integer amount, 10 | Instant orderDate) { 11 | } 12 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec02/entity/Customer.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec02.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.relational.core.mapping.Column; 5 | import org.springframework.data.relational.core.mapping.Table; 6 | 7 | /* 8 | We do not have @Entity in R2DBC. 9 | @Table / @Column are not really required here. but adding it here for your reference...! 10 | */ 11 | @Table("customer") 12 | public class Customer { 13 | 14 | @Id 15 | private Integer id; 16 | 17 | @Column("name") 18 | private String name; 19 | private String email; 20 | 21 | public Integer getId() { 22 | return id; 23 | } 24 | 25 | public void setId(Integer id) { 26 | this.id = id; 27 | } 28 | 29 | public String getName() { 30 | return name; 31 | } 32 | 33 | public void setName(String name) { 34 | this.name = name; 35 | } 36 | 37 | public String getEmail() { 38 | return email; 39 | } 40 | 41 | public void setEmail(String email) { 42 | this.email = email; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "Customer{" + 48 | "id=" + id + 49 | ", name='" + name + '\'' + 50 | ", email='" + email + '\'' + 51 | '}'; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec02/entity/CustomerOrder.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec02.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | import java.time.Instant; 6 | import java.util.UUID; 7 | 8 | public class CustomerOrder { 9 | 10 | @Id 11 | private UUID orderId; 12 | private Integer customerId; 13 | private Integer productId; 14 | private Integer amount; 15 | private Instant orderDate; 16 | 17 | public UUID getOrderId() { 18 | return orderId; 19 | } 20 | 21 | public void setOrderId(UUID orderId) { 22 | this.orderId = orderId; 23 | } 24 | 25 | public Integer getCustomerId() { 26 | return customerId; 27 | } 28 | 29 | public void setCustomerId(Integer customerId) { 30 | this.customerId = customerId; 31 | } 32 | 33 | public Integer getProductId() { 34 | return productId; 35 | } 36 | 37 | public void setProductId(Integer productId) { 38 | this.productId = productId; 39 | } 40 | 41 | public Integer getAmount() { 42 | return amount; 43 | } 44 | 45 | public void setAmount(Integer amount) { 46 | this.amount = amount; 47 | } 48 | 49 | public Instant getOrderDate() { 50 | return orderDate; 51 | } 52 | 53 | public void setOrderDate(Instant orderDate) { 54 | this.orderDate = orderDate; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec02/entity/Product.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec02.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class Product { 6 | 7 | @Id 8 | private Integer id; 9 | private String description; 10 | private Integer price; 11 | 12 | public Integer getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Integer id) { 17 | this.id = id; 18 | } 19 | 20 | public String getDescription() { 21 | return description; 22 | } 23 | 24 | public void setDescription(String description) { 25 | this.description = description; 26 | } 27 | 28 | public Integer getPrice() { 29 | return price; 30 | } 31 | 32 | public void setPrice(Integer price) { 33 | this.price = price; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "Product{" + 39 | "id=" + id + 40 | ", description='" + description + '\'' + 41 | ", price=" + price + 42 | '}'; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec02/repository/CustomerOrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec02.repository; 2 | 3 | import com.vinsguru.playground.sec02.dto.OrderDetails; 4 | import com.vinsguru.playground.sec02.entity.CustomerOrder; 5 | import com.vinsguru.playground.sec02.entity.Product; 6 | import org.springframework.data.r2dbc.repository.Query; 7 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 8 | import org.springframework.stereotype.Repository; 9 | import reactor.core.publisher.Flux; 10 | 11 | import java.util.UUID; 12 | 13 | @Repository 14 | public interface CustomerOrderRepository extends ReactiveCrudRepository { 15 | 16 | @Query(""" 17 | SELECT 18 | p.* 19 | FROM 20 | customer c 21 | INNER JOIN customer_order co ON c.id = co.customer_id 22 | INNER JOIN product p ON co.product_id = p.id 23 | WHERE 24 | c.name = :name 25 | """) 26 | Flux getProductsOrderedByCustomer(String name); 27 | 28 | @Query(""" 29 | SELECT 30 | co.order_id, 31 | c.name AS customer_name, 32 | p.description AS product_name, 33 | co.amount, 34 | co.order_date 35 | FROM 36 | customer c 37 | INNER JOIN customer_order co ON c.id = co.customer_id 38 | INNER JOIN product p ON co.product_id = p.id 39 | WHERE 40 | p.description = :description 41 | ORDER BY co.amount DESC 42 | """) 43 | Flux getOrderDetailsByProduct(String description); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec02/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec02.repository; 2 | 3 | import com.vinsguru.playground.sec02.entity.Customer; 4 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | import reactor.core.publisher.Flux; 7 | 8 | @Repository 9 | public interface CustomerRepository extends ReactiveCrudRepository { 10 | 11 | Flux findByName(String name); 12 | 13 | Flux findByEmailEndingWith(String email); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec02/repository/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec02.repository; 2 | 3 | import com.vinsguru.playground.sec02.entity.Product; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 6 | import org.springframework.stereotype.Repository; 7 | import reactor.core.publisher.Flux; 8 | 9 | @Repository 10 | public interface ProductRepository extends ReactiveCrudRepository { 11 | 12 | Flux findByPriceBetween(int from, int to); 13 | 14 | Flux findBy(Pageable pageable); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec03/dto/CustomerDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec03.dto; 2 | 3 | public record CustomerDto(Integer id, 4 | String name, 5 | String email) { 6 | } 7 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec03/entity/Customer.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec03.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class Customer { 6 | 7 | @Id 8 | private Integer id; 9 | private String name; 10 | private String email; 11 | 12 | public Integer getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Integer id) { 17 | this.id = id; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public String getEmail() { 29 | return email; 30 | } 31 | 32 | public void setEmail(String email) { 33 | this.email = email; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "Customer{" + 39 | "id=" + id + 40 | ", name='" + name + '\'' + 41 | ", email='" + email + '\'' + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec03/mapper/EntityDtoMapper.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec03.mapper; 2 | 3 | import com.vinsguru.playground.sec03.dto.CustomerDto; 4 | import com.vinsguru.playground.sec03.entity.Customer; 5 | 6 | public class EntityDtoMapper { 7 | 8 | public static Customer toEntity(CustomerDto dto){ 9 | var customer = new Customer(); 10 | customer.setName(dto.name()); 11 | customer.setEmail(dto.email()); 12 | customer.setId(dto.id()); 13 | return customer; 14 | } 15 | 16 | public static CustomerDto toDto(Customer customer){ 17 | return new CustomerDto( 18 | customer.getId(), 19 | customer.getName(), 20 | customer.getEmail() 21 | ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec03/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec03.repository; 2 | 3 | import com.vinsguru.playground.sec03.entity.Customer; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.r2dbc.repository.Modifying; 6 | import org.springframework.data.r2dbc.repository.Query; 7 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 8 | import org.springframework.stereotype.Repository; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | @Repository 13 | public interface CustomerRepository extends ReactiveCrudRepository { 14 | 15 | @Modifying // for demo 16 | @Query("delete from customer where id=:id") 17 | Mono deleteCustomerById(Integer id); 18 | 19 | Flux findBy(Pageable pageable); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec04/advice/ApplicationExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec04.advice; 2 | 3 | import com.vinsguru.playground.sec04.exceptions.CustomerNotFoundException; 4 | import com.vinsguru.playground.sec04.exceptions.InvalidInputException; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ProblemDetail; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | 10 | import java.net.URI; 11 | 12 | @ControllerAdvice 13 | public class ApplicationExceptionHandler { 14 | 15 | @ExceptionHandler(CustomerNotFoundException.class) 16 | public ProblemDetail handleException(CustomerNotFoundException ex){ 17 | var problem = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage()); 18 | problem.setType(URI.create("http://example.com/problems/customer-not-found")); 19 | problem.setTitle("Customer Not Found"); 20 | return problem; 21 | } 22 | 23 | @ExceptionHandler(InvalidInputException.class) 24 | public ProblemDetail handleException(InvalidInputException ex){ 25 | var problem = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ex.getMessage()); 26 | problem.setType(URI.create("http://example.com/problems/invalid-input")); 27 | problem.setTitle("Invalid Input"); 28 | return problem; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec04/dto/CustomerDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec04.dto; 2 | 3 | public record CustomerDto(Integer id, 4 | String name, 5 | String email) { 6 | } 7 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec04/entity/Customer.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec04.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class Customer { 6 | 7 | @Id 8 | private Integer id; 9 | private String name; 10 | private String email; 11 | 12 | public Integer getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Integer id) { 17 | this.id = id; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public String getEmail() { 29 | return email; 30 | } 31 | 32 | public void setEmail(String email) { 33 | this.email = email; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "Customer{" + 39 | "id=" + id + 40 | ", name='" + name + '\'' + 41 | ", email='" + email + '\'' + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec04/exceptions/ApplicationExceptions.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec04.exceptions; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public class ApplicationExceptions { 6 | 7 | public static Mono customerNotFound(Integer id){ 8 | return Mono.error(new CustomerNotFoundException(id)); 9 | } 10 | 11 | public static Mono missingName(){ 12 | return Mono.error(new InvalidInputException("Name is required")); 13 | } 14 | 15 | public static Mono missingValidEmail(){ 16 | return Mono.error(new InvalidInputException("Valid email is required")); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec04/exceptions/CustomerNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec04.exceptions; 2 | 3 | public class CustomerNotFoundException extends RuntimeException { 4 | 5 | private static final String MESSAGE = "Customer [id=%d] is not found"; 6 | 7 | public CustomerNotFoundException(Integer id) { 8 | super(MESSAGE.formatted(id)); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec04/exceptions/InvalidInputException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec04.exceptions; 2 | 3 | public class InvalidInputException extends RuntimeException { 4 | 5 | public InvalidInputException(String message) { 6 | super(message); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec04/mapper/EntityDtoMapper.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec04.mapper; 2 | 3 | import com.vinsguru.playground.sec04.dto.CustomerDto; 4 | import com.vinsguru.playground.sec04.entity.Customer; 5 | 6 | public class EntityDtoMapper { 7 | 8 | public static Customer toEntity(CustomerDto dto){ 9 | var customer = new Customer(); 10 | customer.setName(dto.name()); 11 | customer.setEmail(dto.email()); 12 | customer.setId(dto.id()); 13 | return customer; 14 | } 15 | 16 | public static CustomerDto toDto(Customer customer){ 17 | return new CustomerDto( 18 | customer.getId(), 19 | customer.getName(), 20 | customer.getEmail() 21 | ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec04/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec04.repository; 2 | 3 | import com.vinsguru.playground.sec04.entity.Customer; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.r2dbc.repository.Modifying; 6 | import org.springframework.data.r2dbc.repository.Query; 7 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 8 | import org.springframework.stereotype.Repository; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | @Repository 13 | public interface CustomerRepository extends ReactiveCrudRepository { 14 | 15 | @Modifying // for demo 16 | @Query("delete from customer where id=:id") 17 | Mono deleteCustomerById(Integer id); 18 | 19 | Flux findBy(Pageable pageable); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec04/validator/RequestValidator.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec04.validator; 2 | 3 | import com.vinsguru.playground.sec04.dto.CustomerDto; 4 | import com.vinsguru.playground.sec04.exceptions.ApplicationExceptions; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.util.Objects; 8 | import java.util.function.Predicate; 9 | import java.util.function.UnaryOperator; 10 | 11 | public class RequestValidator { 12 | 13 | public static UnaryOperator> validate() { 14 | return mono -> mono.filter(hasName()) 15 | .switchIfEmpty(ApplicationExceptions.missingName()) 16 | .filter(hasValidEmail()) 17 | .switchIfEmpty(ApplicationExceptions.missingValidEmail()); 18 | } 19 | 20 | private static Predicate hasName() { 21 | return dto -> Objects.nonNull(dto.name()); 22 | } 23 | 24 | private static Predicate hasValidEmail() { 25 | return dto -> Objects.nonNull(dto.email()) && dto.email().contains("@"); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/advice/ApplicationExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.advice; 2 | 3 | import com.vinsguru.playground.sec05.exceptions.CustomerNotFoundException; 4 | import com.vinsguru.playground.sec05.exceptions.InvalidInputException; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ProblemDetail; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | 10 | import java.net.URI; 11 | 12 | @ControllerAdvice 13 | public class ApplicationExceptionHandler { 14 | 15 | @ExceptionHandler(CustomerNotFoundException.class) 16 | public ProblemDetail handleException(CustomerNotFoundException ex){ 17 | var problem = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage()); 18 | problem.setType(URI.create("http://example.com/problems/customer-not-found")); 19 | problem.setTitle("Customer Not Found"); 20 | return problem; 21 | } 22 | 23 | @ExceptionHandler(InvalidInputException.class) 24 | public ProblemDetail handleException(InvalidInputException ex){ 25 | var problem = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, ex.getMessage()); 26 | problem.setType(URI.create("http://example.com/problems/invalid-input")); 27 | problem.setTitle("Invalid Input"); 28 | return problem; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/dto/CustomerDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.dto; 2 | 3 | public record CustomerDto(Integer id, 4 | String name, 5 | String email) { 6 | } 7 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/entity/Customer.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class Customer { 6 | 7 | @Id 8 | private Integer id; 9 | private String name; 10 | private String email; 11 | 12 | public Integer getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Integer id) { 17 | this.id = id; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public String getEmail() { 29 | return email; 30 | } 31 | 32 | public void setEmail(String email) { 33 | this.email = email; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "Customer{" + 39 | "id=" + id + 40 | ", name='" + name + '\'' + 41 | ", email='" + email + '\'' + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/exceptions/ApplicationExceptions.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.exceptions; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public class ApplicationExceptions { 6 | 7 | public static Mono customerNotFound(Integer id){ 8 | return Mono.error(new CustomerNotFoundException(id)); 9 | } 10 | 11 | public static Mono missingName(){ 12 | return Mono.error(new InvalidInputException("Name is required")); 13 | } 14 | 15 | public static Mono missingValidEmail(){ 16 | return Mono.error(new InvalidInputException("Valid email is required")); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/exceptions/CustomerNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.exceptions; 2 | 3 | public class CustomerNotFoundException extends RuntimeException { 4 | 5 | private static final String MESSAGE = "Customer [id=%d] is not found"; 6 | 7 | public CustomerNotFoundException(Integer id) { 8 | super(MESSAGE.formatted(id)); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/exceptions/InvalidInputException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.exceptions; 2 | 3 | public class InvalidInputException extends RuntimeException { 4 | 5 | public InvalidInputException(String message) { 6 | super(message); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/filter/AuthenticationWebFilter.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.filter; 2 | 3 | import org.springframework.core.annotation.Order; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.web.server.ServerWebExchange; 7 | import org.springframework.web.server.WebFilter; 8 | import org.springframework.web.server.WebFilterChain; 9 | import reactor.core.publisher.Mono; 10 | 11 | import java.util.Map; 12 | import java.util.Objects; 13 | 14 | @Order(1) 15 | @Service 16 | public class AuthenticationWebFilter implements WebFilter { 17 | 18 | private static final Map TOKEN_CATEGORY_MAP = Map.of( 19 | "secret123", Category.STANDARD, 20 | "secret456", Category.PRIME 21 | ); 22 | 23 | @Override 24 | public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { 25 | var token = exchange.getRequest().getHeaders().getFirst("auth-token"); 26 | if(Objects.nonNull(token) && TOKEN_CATEGORY_MAP.containsKey(token)){ 27 | exchange.getAttributes().put("category", TOKEN_CATEGORY_MAP.get(token)); 28 | return chain.filter(exchange); 29 | } 30 | return Mono.fromRunnable(() -> exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/filter/AuthorizationWebFilter.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.filter; 2 | 3 | import org.springframework.core.annotation.Order; 4 | import org.springframework.http.HttpMethod; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.server.ServerWebExchange; 8 | import org.springframework.web.server.WebFilter; 9 | import org.springframework.web.server.WebFilterChain; 10 | import reactor.core.publisher.Mono; 11 | 12 | @Order(2) 13 | @Service 14 | public class AuthorizationWebFilter implements WebFilter { 15 | 16 | @Override 17 | public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { 18 | var category = exchange.getAttributeOrDefault("category", Category.STANDARD); 19 | return switch (category){ 20 | case STANDARD -> standard(exchange, chain); 21 | case PRIME -> prime(exchange, chain); 22 | }; 23 | } 24 | 25 | private Mono prime(ServerWebExchange exchange, WebFilterChain chain) { 26 | return chain.filter(exchange); 27 | } 28 | 29 | private Mono standard(ServerWebExchange exchange, WebFilterChain chain) { 30 | var isGet = HttpMethod.GET.equals(exchange.getRequest().getMethod()); 31 | if(isGet){ 32 | return chain.filter(exchange); 33 | } 34 | return Mono.fromRunnable(() -> exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN)); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/filter/Category.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.filter; 2 | 3 | public enum Category { 4 | 5 | STANDARD, 6 | PRIME; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/mapper/EntityDtoMapper.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.mapper; 2 | 3 | import com.vinsguru.playground.sec05.dto.CustomerDto; 4 | import com.vinsguru.playground.sec05.entity.Customer; 5 | 6 | public class EntityDtoMapper { 7 | 8 | public static Customer toEntity(CustomerDto dto){ 9 | var customer = new Customer(); 10 | customer.setName(dto.name()); 11 | customer.setEmail(dto.email()); 12 | customer.setId(dto.id()); 13 | return customer; 14 | } 15 | 16 | public static CustomerDto toDto(Customer customer){ 17 | return new CustomerDto( 18 | customer.getId(), 19 | customer.getName(), 20 | customer.getEmail() 21 | ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.repository; 2 | 3 | import com.vinsguru.playground.sec05.entity.Customer; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.r2dbc.repository.Modifying; 6 | import org.springframework.data.r2dbc.repository.Query; 7 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 8 | import org.springframework.stereotype.Repository; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | @Repository 13 | public interface CustomerRepository extends ReactiveCrudRepository { 14 | 15 | @Modifying // for demo 16 | @Query("delete from customer where id=:id") 17 | Mono deleteCustomerById(Integer id); 18 | 19 | Flux findBy(Pageable pageable); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec05/validator/RequestValidator.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec05.validator; 2 | 3 | import com.vinsguru.playground.sec05.dto.CustomerDto; 4 | import com.vinsguru.playground.sec05.exceptions.ApplicationExceptions; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.util.Objects; 8 | import java.util.function.Predicate; 9 | import java.util.function.UnaryOperator; 10 | 11 | public class RequestValidator { 12 | 13 | public static UnaryOperator> validate() { 14 | return mono -> mono.filter(hasName()) 15 | .switchIfEmpty(ApplicationExceptions.missingName()) 16 | .filter(hasValidEmail()) 17 | .switchIfEmpty(ApplicationExceptions.missingValidEmail()); 18 | } 19 | 20 | private static Predicate hasName() { 21 | return dto -> Objects.nonNull(dto.name()); 22 | } 23 | 24 | private static Predicate hasValidEmail() { 25 | return dto -> Objects.nonNull(dto.email()) && dto.email().contains("@"); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec06/config/ApplicationExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec06.config; 2 | 3 | import com.vinsguru.playground.sec06.exceptions.CustomerNotFoundException; 4 | import com.vinsguru.playground.sec06.exceptions.InvalidInputException; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ProblemDetail; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.reactive.function.server.ServerRequest; 9 | import org.springframework.web.reactive.function.server.ServerResponse; 10 | import reactor.core.publisher.Mono; 11 | 12 | import java.net.URI; 13 | import java.util.function.Consumer; 14 | 15 | @Service 16 | public class ApplicationExceptionHandler { 17 | 18 | public Mono handleException(CustomerNotFoundException ex, ServerRequest request){ 19 | return handleException(HttpStatus.NOT_FOUND, ex, request, problem -> { 20 | problem.setType(URI.create("http://example.com/problems/customer-not-found")); 21 | problem.setTitle("Customer Not Found"); 22 | }); 23 | } 24 | 25 | public Mono handleException(InvalidInputException ex, ServerRequest request){ 26 | return handleException(HttpStatus.BAD_REQUEST, ex, request, problem -> { 27 | problem.setType(URI.create("http://example.com/problems/invalid-input")); 28 | problem.setTitle("Invalid Input"); 29 | }); 30 | } 31 | 32 | private Mono handleException(HttpStatus status, Exception ex, ServerRequest request, Consumer consumer){ 33 | var problem = ProblemDetail.forStatusAndDetail(status, ex.getMessage()); 34 | problem.setInstance(URI.create(request.path())); 35 | consumer.accept(problem); 36 | return ServerResponse.status(status).bodyValue(problem); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec06/config/RouterConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec06.config; 2 | 3 | import com.vinsguru.playground.sec06.exceptions.CustomerNotFoundException; 4 | import com.vinsguru.playground.sec06.exceptions.InvalidInputException; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.web.reactive.function.server.RouterFunction; 9 | import org.springframework.web.reactive.function.server.RouterFunctions; 10 | import org.springframework.web.reactive.function.server.ServerResponse; 11 | 12 | @Configuration 13 | public class RouterConfiguration { 14 | 15 | @Autowired 16 | private CustomerRequestHandler customerRequestHandler; 17 | 18 | @Autowired 19 | private ApplicationExceptionHandler exceptionHandler; 20 | 21 | @Bean 22 | public RouterFunction customerRoutes() { 23 | return RouterFunctions.route() 24 | .GET("/customers", this.customerRequestHandler::allCustomers) 25 | .GET("/customers/paginated", this.customerRequestHandler::paginatedCustomers) 26 | .GET("/customers/{id}", this.customerRequestHandler::getCustomer) 27 | .POST("/customers", this.customerRequestHandler::saveCustomer) 28 | .PUT("/customers/{id}", this.customerRequestHandler::updateCustomer) 29 | .DELETE("/customers/{id}", this.customerRequestHandler::deleteCustomer) 30 | .onError(CustomerNotFoundException.class, this.exceptionHandler::handleException) 31 | .onError(InvalidInputException.class, this.exceptionHandler::handleException) 32 | .build(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec06/dto/CustomerDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec06.dto; 2 | 3 | public record CustomerDto(Integer id, 4 | String name, 5 | String email) { 6 | } 7 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec06/entity/Customer.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec06.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class Customer { 6 | 7 | @Id 8 | private Integer id; 9 | private String name; 10 | private String email; 11 | 12 | public Integer getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Integer id) { 17 | this.id = id; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public String getEmail() { 29 | return email; 30 | } 31 | 32 | public void setEmail(String email) { 33 | this.email = email; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "Customer{" + 39 | "id=" + id + 40 | ", name='" + name + '\'' + 41 | ", email='" + email + '\'' + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec06/exceptions/ApplicationExceptions.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec06.exceptions; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public class ApplicationExceptions { 6 | 7 | public static Mono customerNotFound(Integer id){ 8 | return Mono.error(new CustomerNotFoundException(id)); 9 | } 10 | 11 | public static Mono missingName(){ 12 | return Mono.error(new InvalidInputException("Name is required")); 13 | } 14 | 15 | public static Mono missingValidEmail(){ 16 | return Mono.error(new InvalidInputException("Valid email is required")); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec06/exceptions/CustomerNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec06.exceptions; 2 | 3 | public class CustomerNotFoundException extends RuntimeException { 4 | 5 | private static final String MESSAGE = "Customer [id=%d] is not found"; 6 | 7 | public CustomerNotFoundException(Integer id) { 8 | super(MESSAGE.formatted(id)); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec06/exceptions/InvalidInputException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec06.exceptions; 2 | 3 | public class InvalidInputException extends RuntimeException { 4 | 5 | public InvalidInputException(String message) { 6 | super(message); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec06/mapper/EntityDtoMapper.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec06.mapper; 2 | 3 | import com.vinsguru.playground.sec06.dto.CustomerDto; 4 | import com.vinsguru.playground.sec06.entity.Customer; 5 | 6 | public class EntityDtoMapper { 7 | 8 | public static Customer toEntity(CustomerDto dto){ 9 | var customer = new Customer(); 10 | customer.setName(dto.name()); 11 | customer.setEmail(dto.email()); 12 | customer.setId(dto.id()); 13 | return customer; 14 | } 15 | 16 | public static CustomerDto toDto(Customer customer){ 17 | return new CustomerDto( 18 | customer.getId(), 19 | customer.getName(), 20 | customer.getEmail() 21 | ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec06/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec06.repository; 2 | 3 | import com.vinsguru.playground.sec06.entity.Customer; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.r2dbc.repository.Modifying; 6 | import org.springframework.data.r2dbc.repository.Query; 7 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 8 | import org.springframework.stereotype.Repository; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | @Repository 13 | public interface CustomerRepository extends ReactiveCrudRepository { 14 | 15 | @Modifying // for demo 16 | @Query("delete from customer where id=:id") 17 | Mono deleteCustomerById(Integer id); 18 | 19 | Flux findBy(Pageable pageable); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec06/validator/RequestValidator.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec06.validator; 2 | 3 | import com.vinsguru.playground.sec06.dto.CustomerDto; 4 | import com.vinsguru.playground.sec06.exceptions.ApplicationExceptions; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.util.Objects; 8 | import java.util.function.Predicate; 9 | import java.util.function.UnaryOperator; 10 | 11 | public class RequestValidator { 12 | 13 | public static UnaryOperator> validate() { 14 | return mono -> mono.filter(hasName()) 15 | .switchIfEmpty(ApplicationExceptions.missingName()) 16 | .filter(hasValidEmail()) 17 | .switchIfEmpty(ApplicationExceptions.missingValidEmail()); 18 | } 19 | 20 | private static Predicate hasName() { 21 | return dto -> Objects.nonNull(dto.name()); 22 | } 23 | 24 | private static Predicate hasValidEmail() { 25 | return dto -> Objects.nonNull(dto.email()) && dto.email().contains("@"); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec07/note.txt: -------------------------------------------------------------------------------- 1 | Please refer to src/test/java => com.vinsguru.playground.tests.sec07 -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec08/controller/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec08.controller; 2 | 3 | import com.vinsguru.playground.sec08.dto.ProductDto; 4 | import com.vinsguru.playground.sec08.dto.UploadResponse; 5 | import com.vinsguru.playground.sec08.service.ProductService; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.web.bind.annotation.*; 11 | import reactor.core.publisher.Flux; 12 | import reactor.core.publisher.Mono; 13 | 14 | import java.util.UUID; 15 | 16 | @RestController 17 | @RequestMapping("products") 18 | public class ProductController { 19 | 20 | private static final Logger log = LoggerFactory.getLogger(ProductController.class); 21 | 22 | @Autowired 23 | private ProductService service; 24 | 25 | @PostMapping(value = "upload", consumes = MediaType.APPLICATION_NDJSON_VALUE) 26 | public Mono uploadProducts(@RequestBody Flux flux) { 27 | log.info("invoked"); 28 | return this.service.saveProducts(flux) 29 | .then(this.service.getProductsCount()) 30 | .map(count -> new UploadResponse(UUID.randomUUID(), count)); 31 | } 32 | 33 | @GetMapping(value = "download", produces = MediaType.APPLICATION_NDJSON_VALUE) 34 | public Flux downloadProducts(){ 35 | return this.service.allProducts(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec08/dto/ProductDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec08.dto; 2 | 3 | public record ProductDto(Integer id, 4 | String description, 5 | Integer price) { 6 | } 7 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec08/dto/UploadResponse.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec08.dto; 2 | 3 | import java.util.UUID; 4 | 5 | public record UploadResponse(UUID confirmationId, 6 | Long productsCount) { 7 | } 8 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec08/entity/Product.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec08.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class Product { 6 | 7 | @Id 8 | private Integer id; 9 | private String description; 10 | private Integer price; 11 | 12 | public Integer getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Integer id) { 17 | this.id = id; 18 | } 19 | 20 | public String getDescription() { 21 | return description; 22 | } 23 | 24 | public void setDescription(String description) { 25 | this.description = description; 26 | } 27 | 28 | public Integer getPrice() { 29 | return price; 30 | } 31 | 32 | public void setPrice(Integer price) { 33 | this.price = price; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "Product{" + 39 | "id=" + id + 40 | ", description='" + description + '\'' + 41 | ", price=" + price + 42 | '}'; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec08/mapper/EntityDtoMapper.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec08.mapper; 2 | 3 | import com.vinsguru.playground.sec08.dto.ProductDto; 4 | import com.vinsguru.playground.sec08.entity.Product; 5 | 6 | public class EntityDtoMapper { 7 | 8 | public static Product toEntity(ProductDto dto){ 9 | var product = new Product(); 10 | product.setId(dto.id()); 11 | product.setDescription(dto.description()); 12 | product.setPrice(dto.price()); 13 | return product; 14 | } 15 | 16 | public static ProductDto toDto(Product product){ 17 | return new ProductDto( 18 | product.getId(), 19 | product.getDescription(), 20 | product.getPrice() 21 | ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec08/repository/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec08.repository; 2 | 3 | import com.vinsguru.playground.sec08.entity.Product; 4 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface ProductRepository extends ReactiveCrudRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec08/service/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec08.service; 2 | 3 | import com.vinsguru.playground.sec08.dto.ProductDto; 4 | import com.vinsguru.playground.sec08.mapper.EntityDtoMapper; 5 | import com.vinsguru.playground.sec08.repository.ProductRepository; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | 11 | @Service 12 | public class ProductService { 13 | 14 | @Autowired 15 | private ProductRepository repository; 16 | 17 | public Flux saveProducts(Flux flux) { 18 | return flux.map(EntityDtoMapper::toEntity) 19 | .as(this.repository::saveAll) 20 | .map(EntityDtoMapper::toDto); 21 | } 22 | 23 | public Mono getProductsCount() { 24 | return this.repository.count(); 25 | } 26 | 27 | public Flux allProducts() { 28 | return this.repository.findAll() 29 | .map(EntityDtoMapper::toDto); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec09/config/ApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec09.config; 2 | 3 | import com.vinsguru.playground.sec09.dto.ProductDto; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import reactor.core.publisher.Sinks; 7 | 8 | @Configuration 9 | public class ApplicationConfig { 10 | 11 | @Bean 12 | public Sinks.Many sink(){ 13 | return Sinks.many().replay().limit(1); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec09/controller/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec09.controller; 2 | 3 | import com.vinsguru.playground.sec09.dto.ProductDto; 4 | import com.vinsguru.playground.sec09.service.ProductService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.web.bind.annotation.*; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | 11 | @RestController 12 | @RequestMapping("products") 13 | public class ProductController { 14 | 15 | @Autowired 16 | private ProductService service; 17 | 18 | @PostMapping 19 | public Mono saveProduct(@RequestBody Mono mono) { 20 | return this.service.saveProduct(mono); 21 | } 22 | 23 | @GetMapping(value = "/stream/{maxPrice}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 24 | public Flux productStream(@PathVariable Integer maxPrice) { 25 | return this.service.productStream() 26 | .filter(dto -> dto.price() <= maxPrice); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec09/dto/ProductDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec09.dto; 2 | 3 | public record ProductDto(Integer id, 4 | String description, 5 | Integer price) { 6 | } 7 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec09/dto/UploadResponse.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec09.dto; 2 | 3 | import java.util.UUID; 4 | 5 | public record UploadResponse(UUID confirmationId, 6 | Long productsCount) { 7 | } 8 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec09/entity/Product.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec09.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class Product { 6 | 7 | @Id 8 | private Integer id; 9 | private String description; 10 | private Integer price; 11 | 12 | public Integer getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Integer id) { 17 | this.id = id; 18 | } 19 | 20 | public String getDescription() { 21 | return description; 22 | } 23 | 24 | public void setDescription(String description) { 25 | this.description = description; 26 | } 27 | 28 | public Integer getPrice() { 29 | return price; 30 | } 31 | 32 | public void setPrice(Integer price) { 33 | this.price = price; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "Product{" + 39 | "id=" + id + 40 | ", description='" + description + '\'' + 41 | ", price=" + price + 42 | '}'; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec09/mapper/EntityDtoMapper.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec09.mapper; 2 | 3 | import com.vinsguru.playground.sec09.dto.ProductDto; 4 | import com.vinsguru.playground.sec09.entity.Product; 5 | 6 | public class EntityDtoMapper { 7 | 8 | public static Product toEntity(ProductDto dto){ 9 | var product = new Product(); 10 | product.setId(dto.id()); 11 | product.setDescription(dto.description()); 12 | product.setPrice(dto.price()); 13 | return product; 14 | } 15 | 16 | public static ProductDto toDto(Product product){ 17 | return new ProductDto( 18 | product.getId(), 19 | product.getDescription(), 20 | product.getPrice() 21 | ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec09/repository/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec09.repository; 2 | 3 | import com.vinsguru.playground.sec09.entity.Product; 4 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface ProductRepository extends ReactiveCrudRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec09/service/DataSetupService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec09.service; 2 | 3 | import com.vinsguru.playground.sec09.dto.ProductDto; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.stereotype.Service; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | import java.time.Duration; 11 | import java.util.concurrent.ThreadLocalRandom; 12 | 13 | @Service 14 | public class DataSetupService implements CommandLineRunner { 15 | 16 | @Autowired 17 | private ProductService productService; 18 | 19 | @Override 20 | public void run(String... args) throws Exception { 21 | Flux.range(1, 1000) 22 | .delayElements(Duration.ofSeconds(1)) 23 | .map(i -> new ProductDto(null, "product-" + i, ThreadLocalRandom.current().nextInt(1, 100))) 24 | .flatMap(dto -> this.productService.saveProduct(Mono.just(dto))) 25 | .subscribe(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/java/com/vinsguru/playground/sec09/service/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.sec09.service; 2 | 3 | import com.vinsguru.playground.sec09.dto.ProductDto; 4 | import com.vinsguru.playground.sec09.mapper.EntityDtoMapper; 5 | import com.vinsguru.playground.sec09.repository.ProductRepository; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | import reactor.core.publisher.Sinks; 11 | 12 | @Service 13 | public class ProductService { 14 | 15 | @Autowired 16 | private ProductRepository repository; 17 | 18 | @Autowired 19 | private Sinks.Many sink; 20 | 21 | public Mono saveProduct(Mono mono) { 22 | return mono.map(EntityDtoMapper::toEntity) 23 | .flatMap(this.repository::save) 24 | .map(EntityDtoMapper::toDto) 25 | .doOnNext(this.sink::tryEmitNext); 26 | } 27 | 28 | public Flux productStream(){ 29 | return this.sink.asFlux(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /02-webflux-playground/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=webflux-playground 2 | 3 | sec=sec09 4 | 5 | spring.sql.init.data-locations=classpath:sql/data.sql -------------------------------------------------------------------------------- /02-webflux-playground/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} %-5level [%15.15t] %cyan(%-30.30logger{30}) : %m%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec02/AbstractTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec02; 2 | 3 | import org.springframework.boot.test.context.SpringBootTest; 4 | 5 | @SpringBootTest(properties = { 6 | "sec=sec02" 7 | }) 8 | public abstract class AbstractTest { 9 | } 10 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec02/Lec02ProductRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec02; 2 | 3 | import com.vinsguru.playground.sec02.repository.ProductRepository; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.domain.PageRequest; 10 | import org.springframework.data.domain.Sort; 11 | import reactor.test.StepVerifier; 12 | 13 | public class Lec02ProductRepositoryTest extends AbstractTest { 14 | 15 | private static final Logger log = LoggerFactory.getLogger(Lec02ProductRepositoryTest.class); 16 | 17 | @Autowired 18 | private ProductRepository repository; 19 | 20 | @Test 21 | public void findByPriceRange() { 22 | this.repository.findByPriceBetween(750, 1000) 23 | .doOnNext(p -> log.info("{}", p)) 24 | .as(StepVerifier::create) 25 | .expectNextCount(3) 26 | .expectComplete() 27 | .verify(); 28 | } 29 | 30 | @Test 31 | public void pageable() { 32 | this.repository.findBy(PageRequest.of(0, 3).withSort(Sort.by("price").ascending())) 33 | .doOnNext(p -> log.info("{}", p)) 34 | .as(StepVerifier::create) 35 | .assertNext(p -> Assertions.assertEquals(200, p.getPrice())) 36 | .assertNext(p -> Assertions.assertEquals(250, p.getPrice())) 37 | .assertNext(p -> Assertions.assertEquals(300, p.getPrice())) 38 | .expectComplete() 39 | .verify(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec02/Lec03CustomerOrderRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec02; 2 | 3 | import com.vinsguru.playground.sec02.repository.CustomerOrderRepository; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import reactor.test.StepVerifier; 10 | 11 | public class Lec03CustomerOrderRepositoryTest extends AbstractTest { 12 | 13 | private static final Logger log = LoggerFactory.getLogger(Lec03CustomerOrderRepositoryTest.class); 14 | 15 | @Autowired 16 | private CustomerOrderRepository repository; 17 | 18 | @Test 19 | public void productsOrderedByCustomer() { 20 | this.repository.getProductsOrderedByCustomer("mike") 21 | .doOnNext(p -> log.info("{}", p)) 22 | .as(StepVerifier::create) 23 | .expectNextCount(2) 24 | .expectComplete() 25 | .verify(); 26 | } 27 | 28 | @Test 29 | public void orderDetailsByProduct() { 30 | this.repository.getOrderDetailsByProduct("iphone 20") 31 | .doOnNext(dto -> log.info("{}", dto)) 32 | .as(StepVerifier::create) 33 | .assertNext(dto -> Assertions.assertEquals(975, dto.amount())) 34 | .assertNext(dto -> Assertions.assertEquals(950, dto.amount())) 35 | .expectComplete() 36 | .verify(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec07/AbstractWebClient.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec07; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | 7 | import java.util.function.Consumer; 8 | 9 | abstract class AbstractWebClient { 10 | 11 | private static final Logger log = LoggerFactory.getLogger(AbstractWebClient.class); 12 | 13 | protected Consumer print(){ 14 | return item -> log.info("received: {}", item); 15 | } 16 | 17 | protected WebClient createWebClient() { 18 | return createWebClient(b -> {}); 19 | } 20 | 21 | protected WebClient createWebClient(Consumer consumer) { 22 | var builder = WebClient.builder() 23 | .baseUrl("http://localhost:7070/demo02"); 24 | consumer.accept(builder); 25 | return builder.build(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec07/Lec01MonoTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec07; 2 | 3 | import com.vinsguru.playground.tests.sec07.dto.Product; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | 7 | import java.time.Duration; 8 | 9 | public class Lec01MonoTest extends AbstractWebClient { 10 | 11 | private final WebClient client = createWebClient(); 12 | 13 | @Test 14 | public void simpleGet() throws InterruptedException { 15 | this.client.get() 16 | .uri("/lec01/product/1") 17 | .retrieve() 18 | .bodyToMono(Product.class) 19 | .doOnNext(print()) 20 | .subscribe(); 21 | 22 | Thread.sleep(Duration.ofSeconds(2)); 23 | } 24 | 25 | @Test 26 | public void concurrentRequests() throws InterruptedException { 27 | for (int i = 1; i <= 100; i++) { 28 | this.client.get() 29 | .uri("/lec01/product/{id}", i) 30 | .retrieve() 31 | .bodyToMono(Product.class) 32 | .doOnNext(print()) 33 | .subscribe(); 34 | } 35 | 36 | Thread.sleep(Duration.ofSeconds(2)); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec07/Lec02FluxTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec07; 2 | 3 | import com.vinsguru.playground.tests.sec07.dto.Product; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import reactor.test.StepVerifier; 7 | 8 | import java.time.Duration; 9 | 10 | public class Lec02FluxTest extends AbstractWebClient { 11 | 12 | private final WebClient client = createWebClient(); 13 | 14 | @Test 15 | public void streamingResponse() { 16 | this.client.get() 17 | .uri("/lec02/product/stream") 18 | .retrieve() 19 | .bodyToFlux(Product.class) 20 | .take(Duration.ofSeconds(3)) 21 | .doOnNext(print()) 22 | .then() 23 | .as(StepVerifier::create) 24 | .expectComplete() 25 | .verify(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec07/Lec03PostTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec07; 2 | 3 | import com.vinsguru.playground.tests.sec07.dto.Product; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import reactor.core.publisher.Mono; 7 | import reactor.test.StepVerifier; 8 | 9 | import java.time.Duration; 10 | 11 | public class Lec03PostTest extends AbstractWebClient { 12 | 13 | private final WebClient client = createWebClient(); 14 | 15 | @Test 16 | public void postBodyValue() { 17 | var product = new Product(null, "iphone", 1000); 18 | this.client.post() 19 | .uri("/lec03/product") 20 | .bodyValue(product) 21 | .retrieve() 22 | .bodyToMono(Product.class) 23 | .doOnNext(print()) 24 | .then() 25 | .as(StepVerifier::create) 26 | .expectComplete() 27 | .verify(); 28 | } 29 | 30 | @Test 31 | public void postBody() { 32 | var mono = Mono.fromSupplier(() -> new Product(null, "iphone", 1000)) 33 | .delayElement(Duration.ofSeconds(1)); 34 | this.client.post() 35 | .uri("/lec03/product") 36 | .body(mono, Product.class) 37 | .retrieve() 38 | .bodyToMono(Product.class) 39 | .doOnNext(print()) 40 | .then() 41 | .as(StepVerifier::create) 42 | .expectComplete() 43 | .verify(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec07/Lec06QueryParamsTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec07; 2 | 3 | import com.vinsguru.playground.tests.sec07.dto.CalculatorResponse; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import reactor.test.StepVerifier; 7 | 8 | import java.util.Map; 9 | 10 | public class Lec06QueryParamsTest extends AbstractWebClient { 11 | 12 | private final WebClient client = createWebClient(); 13 | 14 | @Test 15 | public void uriBuilderVariables() { 16 | var path = "/lec06/calculator"; 17 | var query = "first={first}&second={second}&operation={operation}"; 18 | this.client.get() 19 | .uri(builder -> builder.path(path).query(query).build(10, 20, "+")) 20 | .retrieve() 21 | .bodyToMono(CalculatorResponse.class) 22 | .doOnNext(print()) 23 | .then() 24 | .as(StepVerifier::create) 25 | .expectComplete() 26 | .verify(); 27 | } 28 | 29 | @Test 30 | public void uriBuilderMap() { 31 | var path = "/lec06/calculator"; 32 | var query = "first={first}&second={second}&operation={operation}"; 33 | var map = Map.of( 34 | "first", 10, 35 | "second", 20, 36 | "operation", "*" 37 | ); 38 | this.client.get() 39 | .uri(builder -> builder.path(path).query(query).build(map)) 40 | .retrieve() 41 | .bodyToMono(CalculatorResponse.class) 42 | .doOnNext(print()) 43 | .then() 44 | .as(StepVerifier::create) 45 | .expectComplete() 46 | .verify(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec07/Lec07BasicAuthTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec07; 2 | 3 | import com.vinsguru.playground.tests.sec07.dto.Product; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import reactor.test.StepVerifier; 7 | 8 | public class Lec07BasicAuthTest extends AbstractWebClient { 9 | 10 | private final WebClient client = createWebClient(b -> b.defaultHeaders(h -> h.setBasicAuth("java", "secret"))); 11 | 12 | @Test 13 | public void basicAuth() { 14 | this.client.get() 15 | .uri("/lec07/product/{id}", 1) 16 | .retrieve() 17 | .bodyToMono(Product.class) 18 | .doOnNext(print()) 19 | .then() 20 | .as(StepVerifier::create) 21 | .expectComplete() 22 | .verify(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec07/Lec08BearerAuthTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec07; 2 | 3 | import com.vinsguru.playground.tests.sec07.dto.Product; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import reactor.test.StepVerifier; 7 | 8 | public class Lec08BearerAuthTest extends AbstractWebClient { 9 | 10 | private final WebClient client = createWebClient(b -> b.defaultHeaders(h -> h.setBearerAuth("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"))); 11 | 12 | @Test 13 | public void bearerAuth() { 14 | this.client.get() 15 | .uri("/lec08/product/{id}", 1) 16 | .retrieve() 17 | .bodyToMono(Product.class) 18 | .doOnNext(print()) 19 | .then() 20 | .as(StepVerifier::create) 21 | .expectComplete() 22 | .verify(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec07/dto/CalculatorResponse.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec07.dto; 2 | 3 | public record CalculatorResponse(Integer first, 4 | Integer second, 5 | String operation, 6 | Double result) { 7 | } 8 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec07/dto/Product.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec07.dto; 2 | 3 | public record Product(Integer id, 4 | String description, 5 | Integer price) { 6 | } 7 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec08/FileWriter.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec08; 2 | 3 | import reactor.core.publisher.Flux; 4 | import reactor.core.publisher.Mono; 5 | 6 | import java.io.BufferedWriter; 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | 11 | // just for demo 12 | public class FileWriter { 13 | 14 | private final Path path; 15 | private BufferedWriter writer; 16 | 17 | private FileWriter(Path path) { 18 | this.path = path; 19 | } 20 | 21 | private void createFile() { 22 | try { 23 | this.writer = Files.newBufferedWriter(path); 24 | } catch (IOException e) { 25 | throw new RuntimeException(e); 26 | } 27 | } 28 | 29 | private void closeFile() { 30 | try { 31 | this.writer.close(); 32 | } catch (IOException e) { 33 | throw new RuntimeException(e); 34 | } 35 | } 36 | 37 | // just for demo 38 | private void write(String content) { 39 | try { 40 | this.writer.write(content); 41 | this.writer.newLine(); 42 | this.writer.flush(); 43 | } catch (IOException e) { 44 | throw new RuntimeException(e); 45 | } 46 | } 47 | 48 | public static Mono create(Flux flux, Path path) { 49 | var writer = new FileWriter(path); 50 | return flux.doOnNext(writer::write) 51 | .doFirst(writer::createFile) 52 | .doFinally(s -> writer.closeFile()) 53 | .then(); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec08/ProductClient.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec08; 2 | 3 | import com.vinsguru.playground.sec08.dto.ProductDto; 4 | import com.vinsguru.playground.sec08.dto.UploadResponse; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.web.reactive.function.client.WebClient; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | public class ProductClient { 11 | 12 | private final WebClient client = WebClient.builder() 13 | .baseUrl("http://localhost:8080") 14 | .build(); 15 | 16 | public Mono uploadProducts(Flux flux) { 17 | return this.client.post() 18 | .uri("/products/upload") 19 | .contentType(MediaType.APPLICATION_NDJSON) 20 | .body(flux, ProductDto.class) 21 | .retrieve() 22 | .bodyToMono(UploadResponse.class); 23 | } 24 | 25 | public Flux downloadProducts() { 26 | return this.client.get() 27 | .uri("/products/download") 28 | .accept(MediaType.APPLICATION_NDJSON) 29 | .retrieve() 30 | .bodyToFlux(ProductDto.class); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec08/ProductsUploadDownloadTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec08; 2 | 3 | import com.vinsguru.playground.sec08.dto.ProductDto; 4 | import org.junit.jupiter.api.Test; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import reactor.core.publisher.Flux; 8 | import reactor.test.StepVerifier; 9 | 10 | import java.nio.file.Path; 11 | 12 | /* 13 | Just for demo 14 | */ 15 | public class ProductsUploadDownloadTest { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(ProductsUploadDownloadTest.class); 18 | private final ProductClient productClient = new ProductClient(); 19 | 20 | @Test 21 | public void upload() { 22 | var flux = Flux.range(1, 1_000_000) 23 | .map(i -> new ProductDto(null, "product-" + i, i)); 24 | 25 | this.productClient.uploadProducts(flux) 26 | .doOnNext(r -> log.info("received: {}", r)) 27 | .then() 28 | .as(StepVerifier::create) 29 | .expectComplete() 30 | .verify(); 31 | } 32 | 33 | @Test 34 | public void download() { 35 | this.productClient.downloadProducts() 36 | .map(ProductDto::toString) 37 | .as(flux -> FileWriter.create(flux, Path.of("products.txt"))) 38 | .as(StepVerifier::create) 39 | .expectComplete() 40 | .verify(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec09/ServerSentEventsTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec09; 2 | 3 | import com.vinsguru.playground.sec09.dto.ProductDto; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.test.web.reactive.server.WebTestClient; 13 | import reactor.test.StepVerifier; 14 | 15 | @AutoConfigureWebTestClient 16 | @SpringBootTest(properties = "sec=sec09") 17 | public class ServerSentEventsTest { 18 | 19 | private static final Logger log = LoggerFactory.getLogger(ServerSentEventsTest.class); 20 | 21 | @Autowired 22 | private WebTestClient client; 23 | 24 | @Test 25 | public void serverSentEvents() { 26 | this.client.get() 27 | .uri("/products/stream/80") 28 | .accept(MediaType.TEXT_EVENT_STREAM) 29 | .exchange() 30 | .expectStatus().is2xxSuccessful() 31 | .returnResult(ProductDto.class) 32 | .getResponseBody() 33 | .take(3) 34 | .doOnNext(dto -> log.info("received: {}", dto)) 35 | .collectList() 36 | .as(StepVerifier::create) 37 | .assertNext(list -> { 38 | Assertions.assertEquals(3, list.size()); 39 | Assertions.assertTrue(list.stream().allMatch(p -> p.price() <= 80)); 40 | }) 41 | .expectComplete() 42 | .verify(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec10/AbstractWebClient.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec10; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | 7 | import java.util.function.Consumer; 8 | 9 | abstract class AbstractWebClient { 10 | 11 | private static final Logger log = LoggerFactory.getLogger(AbstractWebClient.class); 12 | 13 | protected Consumer print(){ 14 | return item -> log.info("received: {}", item); 15 | } 16 | 17 | protected WebClient createWebClient() { 18 | return createWebClient(b -> {}); 19 | } 20 | 21 | protected WebClient createWebClient(Consumer consumer) { 22 | var builder = WebClient.builder() 23 | .baseUrl("http://localhost:7070/demo03"); 24 | consumer.accept(builder); 25 | return builder.build(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /02-webflux-playground/src/test/java/com/vinsguru/playground/tests/sec10/dto/Product.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.playground.tests.sec10.dto; 2 | 3 | public record Product(Integer id, 4 | String description, 5 | Integer price) { 6 | } 7 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.5.0 9 | 10 | 11 | com.vinsguru 12 | aggregator-service 13 | 0.0.1-SNAPSHOT 14 | aggregator-service 15 | Demo project for Spring Boot 16 | 17 | 21 18 | 5.15.0 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-webflux 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | test 29 | 30 | 31 | io.projectreactor 32 | reactor-test 33 | test 34 | 35 | 36 | org.mock-server 37 | mockserver-spring-test-listener-no-dependencies 38 | ${mock-server.version} 39 | test 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-maven-plugin 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/AggregatorServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class AggregatorServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(AggregatorServiceApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/advice/ApplicationExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.advice; 2 | 3 | import com.vinsguru.aggregator.exceptions.CustomerNotFoundException; 4 | import com.vinsguru.aggregator.exceptions.InvalidTradeRequestException; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ProblemDetail; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | 10 | import java.net.URI; 11 | import java.util.function.Consumer; 12 | 13 | @ControllerAdvice 14 | public class ApplicationExceptionHandler { 15 | 16 | @ExceptionHandler(CustomerNotFoundException.class) 17 | public ProblemDetail handleException(CustomerNotFoundException ex) { 18 | return build(HttpStatus.NOT_FOUND, ex, problem -> { 19 | problem.setType(URI.create("http://example.com/problems/customer-not-found")); 20 | problem.setTitle("Customer Not Found"); 21 | }); 22 | } 23 | 24 | @ExceptionHandler(InvalidTradeRequestException.class) 25 | public ProblemDetail handleException(InvalidTradeRequestException ex) { 26 | return build(HttpStatus.BAD_REQUEST, ex, problem -> { 27 | problem.setType(URI.create("http://example.com/problems/invalid-trade-request")); 28 | problem.setTitle("Invalid Trade Request"); 29 | }); 30 | } 31 | 32 | private ProblemDetail build(HttpStatus status, Exception ex, Consumer consumer) { 33 | var problem = ProblemDetail.forStatusAndDetail(status, ex.getMessage()); 34 | consumer.accept(problem); 35 | return problem; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/config/ServiceClientsConfig.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.config; 2 | 3 | import com.vinsguru.aggregator.client.CustomerServiceClient; 4 | import com.vinsguru.aggregator.client.StockServiceClient; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.web.reactive.function.client.WebClient; 11 | 12 | @Configuration 13 | public class ServiceClientsConfig { 14 | 15 | private static final Logger log = LoggerFactory.getLogger(ServiceClientsConfig.class); 16 | 17 | @Bean 18 | public CustomerServiceClient customerServiceClient(@Value("${customer.service.url}") String baseUrl) { 19 | return new CustomerServiceClient(createWebClient(baseUrl)); 20 | } 21 | 22 | @Bean 23 | public StockServiceClient stockServiceClient(@Value("${stock.service.url}") String baseUrl) { 24 | return new StockServiceClient(createWebClient(baseUrl)); 25 | } 26 | 27 | private WebClient createWebClient(String baseUrl) { 28 | log.info("base url: {}", baseUrl); 29 | return WebClient.builder() 30 | .baseUrl(baseUrl) 31 | .build(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/controller/CustomerPortfolioController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.controller; 2 | 3 | import com.vinsguru.aggregator.dto.CustomerInformation; 4 | import com.vinsguru.aggregator.dto.StockTradeResponse; 5 | import com.vinsguru.aggregator.dto.TradeRequest; 6 | import com.vinsguru.aggregator.service.CustomerPortfolioService; 7 | import com.vinsguru.aggregator.validator.RequestValidator; 8 | import org.springframework.web.bind.annotation.*; 9 | import reactor.core.publisher.Mono; 10 | 11 | @RestController 12 | @RequestMapping("customers") 13 | public class CustomerPortfolioController { 14 | 15 | private final CustomerPortfolioService customerPortfolioService; 16 | 17 | public CustomerPortfolioController(CustomerPortfolioService customerPortfolioService) { 18 | this.customerPortfolioService = customerPortfolioService; 19 | } 20 | 21 | @GetMapping("/{customerId}") 22 | public Mono getCustomerInformation(@PathVariable Integer customerId) { 23 | return this.customerPortfolioService.getCustomerInformation(customerId); 24 | } 25 | 26 | @PostMapping("/{customerId}/trade") 27 | public Mono trade(@PathVariable Integer customerId, @RequestBody Mono mono) { 28 | return mono.transform(RequestValidator.validate()) 29 | .flatMap(req -> this.customerPortfolioService.trade(customerId, req)); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/controller/StockPriceStreamController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.controller; 2 | 3 | import com.vinsguru.aggregator.client.StockServiceClient; 4 | import com.vinsguru.aggregator.dto.PriceUpdate; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import reactor.core.publisher.Flux; 10 | 11 | @RestController 12 | @RequestMapping("stock") 13 | public class StockPriceStreamController { 14 | 15 | private final StockServiceClient stockServiceClient; 16 | 17 | public StockPriceStreamController(StockServiceClient stockServiceClient) { 18 | this.stockServiceClient = stockServiceClient; 19 | } 20 | 21 | @GetMapping(value = "/price-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 22 | public Flux priceUpdatesStream(){ 23 | return this.stockServiceClient.priceUpdatesStream(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/domain/Ticker.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.domain; 2 | 3 | public enum Ticker { 4 | 5 | AMAZON, 6 | APPLE, 7 | GOOGLE, 8 | MICROSOFT; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/domain/TradeAction.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.domain; 2 | 3 | public enum TradeAction { 4 | 5 | BUY, 6 | SELL; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/dto/CustomerInformation.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.dto; 2 | 3 | import java.util.List; 4 | 5 | public record CustomerInformation(Integer id, 6 | String name, 7 | Integer balance, 8 | List holdings) { 9 | } 10 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/dto/Holding.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.dto; 2 | 3 | 4 | import com.vinsguru.aggregator.domain.Ticker; 5 | 6 | public record Holding(Ticker ticker, 7 | Integer quantity) { 8 | } 9 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/dto/PriceUpdate.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.dto; 2 | 3 | import com.vinsguru.aggregator.domain.Ticker; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | public record PriceUpdate(Ticker ticker, 8 | Integer price, 9 | LocalDateTime time) { 10 | } 11 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/dto/StockPriceResponse.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.dto; 2 | 3 | import com.vinsguru.aggregator.domain.Ticker; 4 | 5 | public record StockPriceResponse(Ticker ticker, 6 | Integer price) { 7 | } 8 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/dto/StockTradeRequest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.dto; 2 | 3 | 4 | import com.vinsguru.aggregator.domain.Ticker; 5 | import com.vinsguru.aggregator.domain.TradeAction; 6 | 7 | public record StockTradeRequest(Ticker ticker, 8 | Integer price, 9 | Integer quantity, 10 | TradeAction action) { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/dto/StockTradeResponse.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.dto; 2 | 3 | import com.vinsguru.aggregator.domain.Ticker; 4 | import com.vinsguru.aggregator.domain.TradeAction; 5 | 6 | public record StockTradeResponse(Integer customerId, 7 | Ticker ticker, 8 | Integer price, 9 | Integer quantity, 10 | TradeAction action, 11 | Integer totalPrice, 12 | Integer balance) { 13 | } 14 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/dto/TradeRequest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.dto; 2 | 3 | import com.vinsguru.aggregator.domain.Ticker; 4 | import com.vinsguru.aggregator.domain.TradeAction; 5 | 6 | public record TradeRequest(Ticker ticker, 7 | TradeAction action, 8 | Integer quantity) { 9 | } 10 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/exceptions/ApplicationExceptions.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.exceptions; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public class ApplicationExceptions { 6 | 7 | public static Mono customerNotFound(Integer customerId){ 8 | return Mono.error(new CustomerNotFoundException(customerId)); 9 | } 10 | 11 | public static Mono invalidTradeRequest(String message){ 12 | return Mono.error(new InvalidTradeRequestException(message)); 13 | } 14 | 15 | public static Mono missingTicker(){ 16 | return Mono.error(new InvalidTradeRequestException("Ticker is required")); 17 | } 18 | 19 | public static Mono missingTradeAction(){ 20 | return Mono.error(new InvalidTradeRequestException("Trade action is required")); 21 | } 22 | 23 | public static Mono invalidQuantity(){ 24 | return Mono.error(new InvalidTradeRequestException("Quantity should be > 0")); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/exceptions/CustomerNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.exceptions; 2 | 3 | public class CustomerNotFoundException extends RuntimeException { 4 | 5 | private static final String MESSAGE = "Customer [id=%d] is not found"; 6 | 7 | public CustomerNotFoundException(Integer id) { 8 | super(MESSAGE.formatted(id)); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/exceptions/InvalidTradeRequestException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.exceptions; 2 | 3 | public class InvalidTradeRequestException extends RuntimeException { 4 | 5 | public InvalidTradeRequestException(String message){ 6 | super(message); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/service/CustomerPortfolioService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.service; 2 | 3 | import com.vinsguru.aggregator.client.CustomerServiceClient; 4 | import com.vinsguru.aggregator.client.StockServiceClient; 5 | import com.vinsguru.aggregator.dto.*; 6 | import org.springframework.stereotype.Service; 7 | import reactor.core.publisher.Mono; 8 | 9 | @Service 10 | public class CustomerPortfolioService { 11 | 12 | private final StockServiceClient stockServiceClient; 13 | private final CustomerServiceClient customerServiceClient; 14 | 15 | public CustomerPortfolioService(StockServiceClient stockServiceClient, CustomerServiceClient customerServiceClient) { 16 | this.stockServiceClient = stockServiceClient; 17 | this.customerServiceClient = customerServiceClient; 18 | } 19 | 20 | public Mono getCustomerInformation(Integer customerId) { 21 | return this.customerServiceClient.getCustomerInformation(customerId); 22 | } 23 | 24 | public Mono trade(Integer customerId, TradeRequest request) { 25 | return this.stockServiceClient.getStockPrice(request.ticker()) 26 | .map(StockPriceResponse::price) 27 | .map(price -> this.toStockTradeRequest(request, price)) 28 | .flatMap(req -> this.customerServiceClient.trade(customerId, req)); 29 | } 30 | 31 | private StockTradeRequest toStockTradeRequest(TradeRequest request, Integer price) { 32 | return new StockTradeRequest( 33 | request.ticker(), 34 | price, 35 | request.quantity(), 36 | request.action() 37 | ); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/java/com/vinsguru/aggregator/validator/RequestValidator.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.validator; 2 | 3 | import com.vinsguru.aggregator.dto.TradeRequest; 4 | import com.vinsguru.aggregator.exceptions.ApplicationExceptions; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.util.Objects; 8 | import java.util.function.Predicate; 9 | import java.util.function.UnaryOperator; 10 | 11 | public class RequestValidator { 12 | 13 | public static UnaryOperator> validate() { 14 | return mono -> mono.filter(hasTicker()) 15 | .switchIfEmpty(ApplicationExceptions.missingTicker()) 16 | .filter(hasTradeAction()) 17 | .switchIfEmpty(ApplicationExceptions.missingTradeAction()) 18 | .filter(isValidQuantity()) 19 | .switchIfEmpty(ApplicationExceptions.invalidQuantity()); 20 | } 21 | 22 | private static Predicate hasTicker() { 23 | return dto -> Objects.nonNull(dto.ticker()); 24 | } 25 | 26 | private static Predicate hasTradeAction() { 27 | return dto -> Objects.nonNull(dto.action()); 28 | } 29 | 30 | private static Predicate isValidQuantity() { 31 | return dto -> Objects.nonNull(dto.quantity()) && dto.quantity() > 0; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | customer.service.url=http://localhost:6060 3 | stock.service.url=http://localhost:7070 -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} %-5level [%15.15t] %cyan(%-30.30logger{30}) : %m%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/test/java/com/vinsguru/aggregator/tests/AbstractIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.aggregator.tests; 2 | 3 | 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.mockserver.client.MockServerClient; 6 | import org.mockserver.configuration.ConfigurationProperties; 7 | import org.mockserver.springtest.MockServerTest; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.web.reactive.server.WebTestClient; 12 | 13 | import java.io.IOException; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | 17 | @MockServerTest 18 | @AutoConfigureWebTestClient 19 | @SpringBootTest(properties = { 20 | "customer.service.url=http://localhost:${mockServerPort}", 21 | "stock.service.url=http://localhost:${mockServerPort}" 22 | }) 23 | abstract class AbstractIntegrationTest { 24 | 25 | private static final Path TEST_RESOURCES_PATH = Path.of("src/test/resources"); 26 | 27 | // it is set by @MockServerTest 28 | protected MockServerClient mockServerClient; 29 | 30 | @Autowired 31 | protected WebTestClient client; 32 | 33 | @BeforeAll 34 | public static void setup(){ 35 | ConfigurationProperties.disableLogging(true); 36 | } 37 | 38 | protected String resourceToString(String relativePath){ 39 | try { 40 | return Files.readString(TEST_RESOURCES_PATH.resolve(relativePath)); 41 | } catch (IOException e) { 42 | throw new RuntimeException(e); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/test/resources/customer-service/customer-information-200.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "Sam", 4 | "balance": 10000, 5 | "holdings": [ 6 | { 7 | "ticker": "GOOGLE", 8 | "quantity": 2 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/test/resources/customer-service/customer-information-404.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "Customer [id=1] is not found" 3 | } -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/test/resources/customer-service/customer-trade-200.json: -------------------------------------------------------------------------------- 1 | { 2 | "customerId": 1, 3 | "ticker": "GOOGLE", 4 | "price": 110, 5 | "quantity": 2, 6 | "action": "BUY", 7 | "totalPrice": 220, 8 | "balance": 9780 9 | } -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/test/resources/customer-service/customer-trade-400.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "Customer [id=1] does not have enough funds to complete the transaction" 3 | } -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/test/resources/stock-service/stock-price-200.json: -------------------------------------------------------------------------------- 1 | { 2 | "ticker": "GOOGLE", 3 | "price": 110 4 | } -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/aggregator-service/src/test/resources/stock-service/stock-price-stream-200.jsonl: -------------------------------------------------------------------------------- 1 | {"ticker": "GOOGLE", "price": 53, "time": "2026-01-01T12:00:01"} 2 | {"ticker": "GOOGLE", "price": 54, "time": "2026-01-01T12:00:02"} 3 | {"ticker": "GOOGLE", "price": 55, "time": "2026-01-01T12:00:03"} -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/CustomerServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class CustomerServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(CustomerServiceApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/controller/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.controller; 2 | 3 | import com.vinsguru.customerportfolio.dto.CustomerInformation; 4 | import com.vinsguru.customerportfolio.dto.StockTradeRequest; 5 | import com.vinsguru.customerportfolio.dto.StockTradeResponse; 6 | import com.vinsguru.customerportfolio.service.CustomerService; 7 | import com.vinsguru.customerportfolio.service.TradeService; 8 | import org.springframework.web.bind.annotation.*; 9 | import reactor.core.publisher.Mono; 10 | 11 | @RestController 12 | @RequestMapping("customers") 13 | public class CustomerController { 14 | 15 | private final CustomerService customerService; 16 | private final TradeService tradeService; 17 | 18 | public CustomerController(CustomerService customerService, TradeService tradeService) { 19 | this.customerService = customerService; 20 | this.tradeService = tradeService; 21 | } 22 | 23 | @GetMapping("/{customerId}") 24 | public Mono getCustomerInformation(@PathVariable Integer customerId){ 25 | return this.customerService.getCustomerInformation(customerId); 26 | } 27 | 28 | @PostMapping("/{customerId}/trade") 29 | public Mono trade(@PathVariable Integer customerId, @RequestBody Mono mono){ 30 | return mono.flatMap(req -> this.tradeService.trade(customerId, req)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/domain/Ticker.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.domain; 2 | 3 | public enum Ticker { 4 | 5 | AMAZON, 6 | APPLE, 7 | GOOGLE, 8 | MICROSOFT; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/domain/TradeAction.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.domain; 2 | 3 | public enum TradeAction { 4 | 5 | BUY, 6 | SELL; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/dto/CustomerInformation.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.dto; 2 | 3 | import java.util.List; 4 | 5 | public record CustomerInformation(Integer id, 6 | String name, 7 | Integer balance, 8 | List holdings) { 9 | } 10 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/dto/Holding.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.dto; 2 | 3 | import com.vinsguru.customerportfolio.domain.Ticker; 4 | 5 | public record Holding(Ticker ticker, 6 | Integer quantity) { 7 | } 8 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/dto/StockTradeRequest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.dto; 2 | 3 | import com.vinsguru.customerportfolio.domain.Ticker; 4 | import com.vinsguru.customerportfolio.domain.TradeAction; 5 | 6 | public record StockTradeRequest(Ticker ticker, 7 | Integer price, 8 | Integer quantity, 9 | TradeAction action) { 10 | 11 | public Integer totalPrice(){ 12 | return price * quantity; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/dto/StockTradeResponse.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.dto; 2 | 3 | import com.vinsguru.customerportfolio.domain.Ticker; 4 | import com.vinsguru.customerportfolio.domain.TradeAction; 5 | 6 | public record StockTradeResponse(Integer customerId, 7 | Ticker ticker, 8 | Integer price, 9 | Integer quantity, 10 | TradeAction action, 11 | Integer totalPrice, 12 | Integer balance) { 13 | } 14 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/entity/Customer.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class Customer { 6 | 7 | @Id 8 | private Integer id; 9 | private String name; 10 | private Integer balance; 11 | 12 | public Integer getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Integer id) { 17 | this.id = id; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public Integer getBalance() { 29 | return balance; 30 | } 31 | 32 | public void setBalance(Integer balance) { 33 | this.balance = balance; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/entity/PortfolioItem.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.entity; 2 | 3 | import com.vinsguru.customerportfolio.domain.Ticker; 4 | import org.springframework.data.annotation.Id; 5 | 6 | public class PortfolioItem { 7 | 8 | @Id 9 | private Integer id; 10 | private Integer customerId; 11 | private Ticker ticker; 12 | private Integer quantity; 13 | 14 | public Integer getId() { 15 | return id; 16 | } 17 | 18 | public void setId(Integer id) { 19 | this.id = id; 20 | } 21 | 22 | public Integer getCustomerId() { 23 | return customerId; 24 | } 25 | 26 | public void setCustomerId(Integer customerId) { 27 | this.customerId = customerId; 28 | } 29 | 30 | public Ticker getTicker() { 31 | return ticker; 32 | } 33 | 34 | public void setTicker(Ticker ticker) { 35 | this.ticker = ticker; 36 | } 37 | 38 | public Integer getQuantity() { 39 | return quantity; 40 | } 41 | 42 | public void setQuantity(Integer quantity) { 43 | this.quantity = quantity; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/exceptions/ApplicationExceptions.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.exceptions; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public class ApplicationExceptions { 6 | 7 | public static Mono customerNotFound(Integer customerId){ 8 | return Mono.error(new CustomerNotFoundException(customerId)); 9 | } 10 | 11 | public static Mono insufficientBalance(Integer customerId){ 12 | return Mono.error(new InsufficientBalanceException(customerId)); 13 | } 14 | 15 | public static Mono insufficientShares(Integer customerId){ 16 | return Mono.error(new InsufficientSharesException(customerId)); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/exceptions/CustomerNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.exceptions; 2 | 3 | public class CustomerNotFoundException extends RuntimeException { 4 | 5 | private static final String MESSAGE = "Customer [id=%d] is not found"; 6 | 7 | public CustomerNotFoundException(Integer id) { 8 | super(MESSAGE.formatted(id)); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/exceptions/InsufficientBalanceException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.exceptions; 2 | 3 | public class InsufficientBalanceException extends RuntimeException { 4 | 5 | private static final String MESSAGE = "Customer [id=%d] does not have enough funds to complete the transaction"; 6 | 7 | public InsufficientBalanceException(Integer customerId){ 8 | super(MESSAGE.formatted(customerId)); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/exceptions/InsufficientSharesException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.exceptions; 2 | 3 | public class InsufficientSharesException extends RuntimeException { 4 | 5 | private static final String MESSAGE = "Customer [id=%d] does not have enough shares to complete the transaction"; 6 | 7 | public InsufficientSharesException(Integer customerId){ 8 | super(MESSAGE.formatted(customerId)); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.repository; 2 | 3 | import com.vinsguru.customerportfolio.entity.Customer; 4 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface CustomerRepository extends ReactiveCrudRepository { 9 | } 10 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/repository/PortfolioItemRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.repository; 2 | 3 | import com.vinsguru.customerportfolio.domain.Ticker; 4 | import com.vinsguru.customerportfolio.entity.PortfolioItem; 5 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 6 | import org.springframework.stereotype.Repository; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | @Repository 11 | public interface PortfolioItemRepository extends ReactiveCrudRepository { 12 | 13 | Flux findAllByCustomerId(Integer customerId); 14 | 15 | Mono findByCustomerIdAndTicker(Integer customerId, Ticker ticker); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/java/com/vinsguru/customerportfolio/service/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.customerportfolio.service; 2 | 3 | import com.vinsguru.customerportfolio.dto.CustomerInformation; 4 | import com.vinsguru.customerportfolio.entity.Customer; 5 | import com.vinsguru.customerportfolio.exceptions.ApplicationExceptions; 6 | import com.vinsguru.customerportfolio.mapper.EntityDtoMapper; 7 | import com.vinsguru.customerportfolio.repository.CustomerRepository; 8 | import com.vinsguru.customerportfolio.repository.PortfolioItemRepository; 9 | import org.springframework.stereotype.Service; 10 | import reactor.core.publisher.Mono; 11 | 12 | @Service 13 | public class CustomerService { 14 | 15 | private final CustomerRepository customerRepository; 16 | private final PortfolioItemRepository portfolioItemRepository; 17 | 18 | public CustomerService(CustomerRepository customerRepository, PortfolioItemRepository portfolioItemRepository) { 19 | this.customerRepository = customerRepository; 20 | this.portfolioItemRepository = portfolioItemRepository; 21 | } 22 | 23 | public Mono getCustomerInformation(Integer customerId) { 24 | return this.customerRepository.findById(customerId) 25 | .switchIfEmpty(ApplicationExceptions.customerNotFound(customerId)) 26 | .flatMap(this::buildCustomerInformation); 27 | } 28 | 29 | private Mono buildCustomerInformation(Customer customer) { 30 | return this.portfolioItemRepository.findAllByCustomerId(customer.getId()) 31 | .collectList() 32 | .map(list -> EntityDtoMapper.toCustomerInformation(customer, list)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=6060 2 | 3 | spring.sql.init.data-locations=classpath:sql/data.sql -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} %-5level [%15.15t] %cyan(%-30.30logger{30}) : %m%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /03-final-project/trade-platofrm/customer-service/src/main/resources/sql/data.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS customer; 2 | DROP TABLE IF EXISTS portfolio_item; 3 | 4 | CREATE TABLE customer ( 5 | id int AUTO_INCREMENT primary key, 6 | name VARCHAR(50), 7 | balance int 8 | ); 9 | 10 | CREATE TABLE portfolio_item ( 11 | id int AUTO_INCREMENT primary key, 12 | customer_id int, 13 | ticker VARCHAR(10), 14 | quantity int, 15 | foreign key (customer_id) references customer(id) 16 | ); 17 | 18 | insert into customer(name, balance) 19 | values 20 | ('Sam', 10000), 21 | ('Mike', 10000), 22 | ('John', 10000); -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/Makefile: -------------------------------------------------------------------------------- 1 | package: 2 | mvn clean package 3 | 4 | traditional-efficiency-test: 5 | java -Xmx4000m -jar ./traditional/target/traditional-0.0.1-SNAPSHOT.jar --efficiency.test=true 6 | 7 | reactive-efficiency-test: 8 | java -Xmx200m -jar ./reactive/target/reactive-0.0.1-SNAPSHOT.jar --efficiency.test=true 9 | 10 | traditional-throughput-test: 11 | java -Xmx1000m -jar ./traditional/target/traditional-0.0.1-SNAPSHOT.jar --throughput.test=true --useVirtualThreadExecutor=false 12 | 13 | reactive-throughput-test: 14 | java -Xmx1000m -jar ./reactive/target/reactive-0.0.1-SNAPSHOT.jar --throughput.test=true -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/customer.sql: -------------------------------------------------------------------------------- 1 | -- it can be part of the docker-compose init sql. 2 | 3 | CREATE TABLE customer ( 4 | id SERIAL PRIMARY KEY, 5 | name VARCHAR(255), 6 | email VARCHAR(255) 7 | ); 8 | 9 | INSERT INTO customer (name, email) 10 | SELECT 'customer' || num, 'customer' || num || '@gmail.com' 11 | FROM generate_series(1, 10000000) AS num; -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | postgres: 4 | image: postgres 5 | container_name: postgres 6 | environment: 7 | - POSTGRES_USER=admin 8 | - POSTGRES_PASSWORD=admin 9 | - POSTGRES_DB=customer 10 | volumes: 11 | - ./db:/var/lib/postgresql/data 12 | - ./customer.sql:/docker-entrypoint-initdb.d/customer.sql 13 | ports: 14 | - 5432:5432 -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.5.0 9 | 10 | 11 | com.vinsguru 12 | reactive-vs-traditional-postgres 13 | 0.0.1-SNAPSHOT 14 | pom 15 | reactive-vs-traditional-postgres 16 | Demo project for Spring Boot 17 | 18 | traditional 19 | reactive 20 | 21 | 22 | 21 23 | 24 | 25 | 26 | org.postgresql 27 | postgresql 28 | runtime 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-test 33 | test 34 | 35 | 36 | io.projectreactor 37 | reactor-test 38 | test 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-maven-plugin 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/reactive/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.vinsguru 8 | reactive-vs-traditional-postgres 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | reactive 13 | 14 | 15 | 21 16 | 21 17 | UTF-8 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-data-r2dbc 24 | 25 | 26 | org.postgresql 27 | r2dbc-postgresql 28 | runtime 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/reactive/src/main/java/com/vinsguru/ReactiveApp.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ReactiveApp { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ReactiveApp.class, args); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/reactive/src/main/java/com/vinsguru/entity/Customer.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class Customer { 6 | 7 | @Id 8 | private Integer id; 9 | private String name; 10 | private String email; 11 | 12 | public Integer getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Integer id) { 17 | this.id = id; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public String getEmail() { 29 | return email; 30 | } 31 | 32 | public void setEmail(String email) { 33 | this.email = email; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "Customer{" + 39 | "id=" + id + 40 | ", name='" + name + '\'' + 41 | ", email='" + email + '\'' + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/reactive/src/main/java/com/vinsguru/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.repository; 2 | 3 | import com.vinsguru.entity.Customer; 4 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface CustomerRepository extends ReactiveCrudRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/reactive/src/main/java/com/vinsguru/runner/EfficiencyTestRunner.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.runner; 2 | 3 | import com.vinsguru.repository.CustomerRepository; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.CommandLineRunner; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | 13 | @Service 14 | @ConditionalOnProperty(value = "efficiency.test", havingValue = "true") 15 | public class EfficiencyTestRunner implements CommandLineRunner { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(EfficiencyTestRunner.class); 18 | 19 | @Autowired 20 | private CustomerRepository repository; 21 | 22 | @Override 23 | public void run(String... args) { 24 | var atomicInteger = new AtomicInteger(0); 25 | log.info("starting"); 26 | this.repository.findAll() 27 | .doOnNext(c -> { 28 | // for every customer, increment 29 | // print for every million 30 | var count = atomicInteger.incrementAndGet(); 31 | if (count % 1_000_000 == 0) { 32 | log.info("{}", count); 33 | } 34 | }) 35 | .then() 36 | .block(); // for demo 37 | log.info("done"); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/reactive/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.r2dbc.url=r2dbc:postgresql://localhost:5432/customer 2 | spring.r2dbc.username=admin 3 | spring.r2dbc.password=admin -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/traditional/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.vinsguru 8 | reactive-vs-traditional-postgres 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | traditional 13 | 14 | 15 | 21 16 | 21 17 | 6.1.2.Final 18 | UTF-8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-jpa 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/traditional/src/main/java/com/vinsguru/TraditionalApp.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class TraditionalApp { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(TraditionalApp.class, args); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/traditional/src/main/java/com/vinsguru/entity/Customer.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.entity; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.Id; 5 | 6 | @Entity 7 | public class Customer { 8 | 9 | @Id 10 | private Integer id; 11 | private String name; 12 | private String email; 13 | 14 | public Integer getId() { 15 | return id; 16 | } 17 | 18 | public void setId(Integer id) { 19 | this.id = id; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | 30 | public String getEmail() { 31 | return email; 32 | } 33 | 34 | public void setEmail(String email) { 35 | this.email = email; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return "Customer{" + 41 | "id=" + id + 42 | ", name='" + name + '\'' + 43 | ", email='" + email + '\'' + 44 | '}'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/traditional/src/main/java/com/vinsguru/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.repository; 2 | 3 | import com.vinsguru.entity.Customer; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface CustomerRepository extends JpaRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/traditional/src/main/java/com/vinsguru/runner/EfficiencyTestRunner.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.runner; 2 | 3 | import com.vinsguru.repository.CustomerRepository; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.CommandLineRunner; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | @ConditionalOnProperty(value = "efficiency.test", havingValue = "true") 13 | public class EfficiencyTestRunner implements CommandLineRunner { 14 | 15 | private static final Logger log = LoggerFactory.getLogger(EfficiencyTestRunner.class); 16 | 17 | @Autowired 18 | private CustomerRepository repository; 19 | 20 | @Override 21 | public void run(String... args) { 22 | log.info("starting"); 23 | var list = this.repository.findAll(); 24 | log.info("list size: {}", list.size()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /04-r2dbc-vs-jdbc/reactive-vs-traditional-postgres/traditional/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://localhost:5432/customer 2 | spring.datasource.username=admin 3 | spring.datasource.password=admin -------------------------------------------------------------------------------- /05-additional-resources/01-r2dbc.md: -------------------------------------------------------------------------------- 1 | ## Resources for R2DBC 2 | 3 | ### Database Initialization Scripts 4 | 5 | ```properties 6 | # For db initialization scripts 7 | spring.sql.init.data-locations=classpath:sql/data.sql 8 | 9 | # To show SQL 10 | logging.level.org.springframework.r2dbc=TRACE 11 | ``` 12 | 13 | ### Connection String 14 | 15 | ```properties 16 | # H2 17 | r2dbc:h2:mem:///userdb 18 | 19 | # Postgres 20 | r2dbc:postgresql://localhost:5432/userdb 21 | 22 | # Mysql 23 | r2dbc:mysql://localhost:3306/userdb 24 | ``` 25 | 26 | ### Additional Resources 27 | 28 | - [R2DBC Documentation](https://r2dbc.io/spec/1.0.0.RELEASE/spec/html/) 29 | - [R2DBC Drivers](https://r2dbc.io/drivers/) 30 | - [R2DBC Data Types](https://r2dbc.io/spec/1.0.0.RELEASE/spec/html/#datatypes) 31 | - [Spring Data R2DBC](https://docs.spring.io/spring-data/relational/reference/r2dbc.html) 32 | - [Spring Query Methods](https://docs.spring.io/spring-data/relational/reference/r2dbc/query-methods.html) -------------------------------------------------------------------------------- /06-old-projects/order-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/OrderServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class OrderServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(OrderServiceApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/client/ProductClient.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.client; 2 | 3 | import com.vinsguru.orderservice.dto.ProductDto; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.web.reactive.function.client.WebClient; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | @Service 11 | public class ProductClient { 12 | 13 | private final WebClient webClient; 14 | 15 | public ProductClient(@Value("${product.service.url}") String url){ 16 | this.webClient = WebClient.builder() 17 | .baseUrl(url) 18 | .build(); 19 | } 20 | 21 | public Mono getProductById(final String productId){ 22 | return this.webClient 23 | .get() 24 | .uri("{id}", productId) 25 | .retrieve() 26 | .bodyToMono(ProductDto.class); 27 | } 28 | 29 | public Flux getAllProducts(){ 30 | return this.webClient 31 | .get() 32 | .uri("all") 33 | .retrieve() 34 | .bodyToFlux(ProductDto.class); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/client/UserClient.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.client; 2 | 3 | import com.vinsguru.orderservice.dto.ProductDto; 4 | import com.vinsguru.orderservice.dto.TransactionRequestDto; 5 | import com.vinsguru.orderservice.dto.TransactionResponseDto; 6 | import com.vinsguru.orderservice.dto.UserDto; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.web.reactive.function.client.WebClient; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | 13 | @Service 14 | public class UserClient { 15 | 16 | private final WebClient webClient; 17 | 18 | public UserClient(@Value("${user.service.url}") String url){ 19 | this.webClient = WebClient.builder() 20 | .baseUrl(url) 21 | .build(); 22 | } 23 | 24 | public Mono authorizeTransaction(TransactionRequestDto requestDto){ 25 | return this.webClient 26 | .post() 27 | .uri("transaction") 28 | .bodyValue(requestDto) 29 | .retrieve() 30 | .bodyToMono(TransactionResponseDto.class); 31 | } 32 | 33 | public Flux getAllUsers(){ 34 | return this.webClient 35 | .get() 36 | .uri("all") 37 | .retrieve() 38 | .bodyToFlux(UserDto.class); 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/controller/PurchaseOrderController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.controller; 2 | 3 | import com.vinsguru.orderservice.dto.PurchaseOrderRequestDto; 4 | import com.vinsguru.orderservice.dto.PurchaseOrderResponseDto; 5 | import com.vinsguru.orderservice.service.OrderFulfillmentService; 6 | import com.vinsguru.orderservice.service.OrderQueryService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | import org.springframework.web.reactive.function.client.WebClientRequestException; 12 | import org.springframework.web.reactive.function.client.WebClientResponseException; 13 | import reactor.core.publisher.Flux; 14 | import reactor.core.publisher.Mono; 15 | 16 | @RestController 17 | @RequestMapping("order") 18 | public class PurchaseOrderController { 19 | 20 | @Autowired 21 | private OrderFulfillmentService orderFulfillmentService; 22 | 23 | @Autowired 24 | private OrderQueryService queryService; 25 | 26 | @PostMapping 27 | public Mono> order(@RequestBody Mono requestDtoMono){ 28 | return this.orderFulfillmentService.processOrder(requestDtoMono) 29 | .map(ResponseEntity::ok) 30 | .onErrorReturn(WebClientResponseException.class, ResponseEntity.badRequest().build()) 31 | .onErrorReturn(WebClientRequestException.class, ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build()); 32 | } 33 | 34 | @GetMapping("user/{userId}") 35 | public Flux getOrdersByUserId(@PathVariable int userId){ 36 | return this.queryService.getProductsByUserId(userId); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/dto/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.dto; 2 | 3 | public enum OrderStatus { 4 | 5 | COMPLETED, 6 | FAILED; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/dto/ProductDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.dto; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import lombok.ToString; 6 | 7 | @Data 8 | @ToString 9 | @NoArgsConstructor 10 | public class ProductDto { 11 | 12 | private String id; 13 | private String description; 14 | private Integer price; 15 | 16 | public ProductDto(String description, Integer price) { 17 | this.description = description; 18 | this.price = price; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/dto/PurchaseOrderRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.dto; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class PurchaseOrderRequestDto { 9 | 10 | private Integer userId; 11 | private String productId; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/dto/PurchaseOrderResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.dto; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class PurchaseOrderResponseDto { 9 | 10 | private Integer orderId; 11 | private Integer userId; 12 | private String productId; 13 | private Integer amount; 14 | private OrderStatus status; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/dto/RequestContext.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.dto; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class RequestContext { 9 | 10 | private PurchaseOrderRequestDto purchaseOrderRequestDto; 11 | private ProductDto productDto; 12 | private TransactionRequestDto transactionRequestDto; 13 | private TransactionResponseDto transactionResponseDto; 14 | 15 | public RequestContext(PurchaseOrderRequestDto purchaseOrderRequestDto) { 16 | this.purchaseOrderRequestDto = purchaseOrderRequestDto; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/dto/TransactionRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.dto; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class TransactionRequestDto { 9 | 10 | private Integer userId; 11 | private Integer amount; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/dto/TransactionResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.dto; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class TransactionResponseDto { 9 | 10 | private Integer userId; 11 | private Integer amount; 12 | private TransactionStatus status; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/dto/TransactionStatus.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.dto; 2 | 3 | public enum TransactionStatus { 4 | APPROVED, 5 | DECLINED; 6 | } 7 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/dto/UserDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.dto; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class UserDto { 9 | 10 | private Integer id; 11 | private String name; 12 | private Integer balance; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/entity/PurchaseOrder.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.entity; 2 | 3 | import com.vinsguru.orderservice.dto.OrderStatus; 4 | import lombok.Data; 5 | import lombok.ToString; 6 | 7 | import jakarta.persistence.Entity; 8 | import jakarta.persistence.GeneratedValue; 9 | import jakarta.persistence.Id; 10 | 11 | @Data 12 | @Entity 13 | @ToString 14 | public class PurchaseOrder { 15 | 16 | @Id 17 | @GeneratedValue 18 | private Integer id; 19 | private String productId; 20 | private Integer userId; 21 | private Integer amount; 22 | private OrderStatus status; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/repository/PurchaseOrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.repository; 2 | 3 | import com.vinsguru.orderservice.entity.PurchaseOrder; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface PurchaseOrderRepository extends JpaRepository { 11 | 12 | List findByUserId(int userId); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/service/OrderQueryService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.service; 2 | 3 | import com.vinsguru.orderservice.dto.PurchaseOrderResponseDto; 4 | import com.vinsguru.orderservice.repository.PurchaseOrderRepository; 5 | import com.vinsguru.orderservice.util.EntityDtoUtil; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.scheduler.Schedulers; 10 | 11 | @Service 12 | public class OrderQueryService { 13 | 14 | @Autowired 15 | private PurchaseOrderRepository orderRepository; 16 | 17 | public Flux getProductsByUserId(int userId){ 18 | return Flux.fromStream(() -> this.orderRepository.findByUserId(userId).stream()) // blocking 19 | .map(EntityDtoUtil::getPurchaseOrderResponseDto) 20 | .subscribeOn(Schedulers.boundedElastic()); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/java/com/vinsguru/orderservice/util/EntityDtoUtil.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice.util; 2 | 3 | import com.vinsguru.orderservice.dto.*; 4 | import com.vinsguru.orderservice.entity.PurchaseOrder; 5 | import org.springframework.beans.BeanUtils; 6 | 7 | public class EntityDtoUtil { 8 | 9 | public static PurchaseOrderResponseDto getPurchaseOrderResponseDto(PurchaseOrder purchaseOrder){ 10 | PurchaseOrderResponseDto dto = new PurchaseOrderResponseDto(); 11 | BeanUtils.copyProperties(purchaseOrder, dto); 12 | dto.setOrderId(purchaseOrder.getId()); 13 | return dto; 14 | } 15 | 16 | public static void setTransactionRequestDto(RequestContext requestContext){ 17 | TransactionRequestDto dto = new TransactionRequestDto(); 18 | dto.setUserId(requestContext.getPurchaseOrderRequestDto().getUserId()); 19 | dto.setAmount(requestContext.getProductDto().getPrice()); 20 | requestContext.setTransactionRequestDto(dto); 21 | } 22 | 23 | public static PurchaseOrder getPurchaseOrder(RequestContext requestContext){ 24 | PurchaseOrder purchaseOrder = new PurchaseOrder(); 25 | purchaseOrder.setUserId(requestContext.getPurchaseOrderRequestDto().getUserId()); 26 | purchaseOrder.setProductId(requestContext.getPurchaseOrderRequestDto().getProductId()); 27 | purchaseOrder.setAmount(requestContext.getProductDto().getPrice()); 28 | TransactionStatus status = requestContext.getTransactionResponseDto().getStatus(); 29 | OrderStatus orderStatus = TransactionStatus.APPROVED.equals(status) ? OrderStatus.COMPLETED : OrderStatus.FAILED; 30 | purchaseOrder.setStatus(orderStatus); 31 | return purchaseOrder; 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | product.service.url=http://localhost:8091/product/ 2 | user.service.url=http://localhost:8092/user/ 3 | -------------------------------------------------------------------------------- /06-old-projects/order-service/src/test/java/com/vinsguru/orderservice/OrderServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.orderservice; 2 | 3 | import com.vinsguru.orderservice.client.ProductClient; 4 | import com.vinsguru.orderservice.client.UserClient; 5 | import com.vinsguru.orderservice.dto.ProductDto; 6 | import com.vinsguru.orderservice.dto.PurchaseOrderRequestDto; 7 | import com.vinsguru.orderservice.dto.PurchaseOrderResponseDto; 8 | import com.vinsguru.orderservice.dto.UserDto; 9 | import com.vinsguru.orderservice.service.OrderFulfillmentService; 10 | import org.junit.jupiter.api.Test; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import reactor.core.publisher.Flux; 14 | import reactor.core.publisher.Mono; 15 | import reactor.test.StepVerifier; 16 | 17 | @SpringBootTest 18 | class OrderServiceApplicationTests { 19 | 20 | @Autowired 21 | private UserClient userClient; 22 | 23 | @Autowired 24 | private ProductClient productClient; 25 | 26 | @Autowired 27 | private OrderFulfillmentService fulfillmentService; 28 | 29 | @Test 30 | void contextLoads() { 31 | 32 | Flux dtoFlux = Flux.zip(userClient.getAllUsers(), productClient.getAllProducts()) 33 | .map(t -> buildDto(t.getT1(), t.getT2())) 34 | .flatMap(dto -> this.fulfillmentService.processOrder(Mono.just(dto))) 35 | .doOnNext(System.out::println); 36 | 37 | StepVerifier.create(dtoFlux) 38 | .expectNextCount(4) 39 | .verifyComplete(); 40 | 41 | 42 | } 43 | 44 | private PurchaseOrderRequestDto buildDto(UserDto userDto, ProductDto productDto){ 45 | PurchaseOrderRequestDto dto = new PurchaseOrderRequestDto(); 46 | dto.setUserId(userDto.getId()); 47 | dto.setProductId(productDto.getId()); 48 | return dto; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /06-old-projects/product-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /06-old-projects/product-service/src/main/java/com/vinsguru/productservice/ProductServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.productservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ProductServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ProductServiceApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /06-old-projects/product-service/src/main/java/com/vinsguru/productservice/config/SinkConfig.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.productservice.config; 2 | 3 | import com.vinsguru.productservice.dto.ProductDto; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Sinks; 8 | 9 | @Configuration 10 | public class SinkConfig { 11 | 12 | @Bean 13 | public Sinks.Many sink(){ 14 | return Sinks.many().replay().limit(1); 15 | } 16 | 17 | @Bean 18 | public Flux productBroadcast(Sinks.Many sink){ 19 | return sink.asFlux(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /06-old-projects/product-service/src/main/java/com/vinsguru/productservice/controller/ProductStreamController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.productservice.controller; 2 | 3 | import com.vinsguru.productservice.dto.ProductDto; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import reactor.core.publisher.Flux; 11 | 12 | @RestController 13 | @RequestMapping("product") 14 | public class ProductStreamController { 15 | 16 | @Autowired 17 | private Flux flux; 18 | 19 | @GetMapping(value = "stream/{maxPrice}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 20 | public Flux getProductUpdates(@PathVariable int maxPrice){ 21 | return this.flux 22 | .filter(dto -> dto.getPrice() <= maxPrice); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /06-old-projects/product-service/src/main/java/com/vinsguru/productservice/dto/ProductDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.productservice.dto; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import lombok.ToString; 6 | 7 | @Data 8 | @ToString 9 | @NoArgsConstructor 10 | public class ProductDto { 11 | 12 | private String id; 13 | private String description; 14 | private Integer price; 15 | 16 | public ProductDto(String description, Integer price) { 17 | this.description = description; 18 | this.price = price; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /06-old-projects/product-service/src/main/java/com/vinsguru/productservice/entity/Product.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.productservice.entity; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import org.springframework.data.annotation.Id; 6 | 7 | @Data 8 | @ToString 9 | public class Product { 10 | 11 | @Id 12 | private String id; 13 | private String description; 14 | private Integer price; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /06-old-projects/product-service/src/main/java/com/vinsguru/productservice/repository/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.productservice.repository; 2 | 3 | import com.vinsguru.productservice.entity.Product; 4 | import org.springframework.data.domain.Range; 5 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 6 | import org.springframework.stereotype.Repository; 7 | import reactor.core.publisher.Flux; 8 | 9 | @Repository 10 | public interface ProductRepository extends ReactiveMongoRepository { 11 | 12 | // > min & < max 13 | // Flux findByPriceBetween(int min, int max); 14 | Flux findByPriceBetween(Range range); 15 | } 16 | -------------------------------------------------------------------------------- /06-old-projects/product-service/src/main/java/com/vinsguru/productservice/service/DataSetupService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.productservice.service; 2 | 3 | import com.vinsguru.productservice.dto.ProductDto; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.stereotype.Service; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | import java.time.Duration; 11 | import java.util.concurrent.ThreadLocalRandom; 12 | 13 | @Service 14 | public class DataSetupService implements CommandLineRunner { 15 | 16 | @Autowired 17 | private ProductService service; 18 | 19 | @Override 20 | public void run(String... args) throws Exception { 21 | ProductDto p1 = new ProductDto("4k-tv", 1000); 22 | ProductDto p2 = new ProductDto("slr-camera", 750); 23 | ProductDto p3 = new ProductDto("iphone", 800); 24 | ProductDto p4 = new ProductDto("headphone", 100); 25 | 26 | Flux.just(p1, p2, p3, p4) 27 | .concatWith(newProducts()) 28 | .flatMap(p -> this.service.insertProduct(Mono.just(p))) 29 | .subscribe(System.out::println); 30 | 31 | } 32 | 33 | private Flux newProducts(){ 34 | return Flux.range(1, 1000) 35 | .delayElements(Duration.ofSeconds(2)) 36 | .map(i -> new ProductDto("product-" + i, ThreadLocalRandom.current().nextInt(10, 100))); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /06-old-projects/product-service/src/main/java/com/vinsguru/productservice/util/EntityDtoUtil.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.productservice.util; 2 | 3 | import com.vinsguru.productservice.dto.ProductDto; 4 | import com.vinsguru.productservice.entity.Product; 5 | import org.springframework.beans.BeanUtils; 6 | 7 | public class EntityDtoUtil { 8 | 9 | public static ProductDto toDto(Product product){ 10 | ProductDto dto = new ProductDto(); 11 | BeanUtils.copyProperties(product, dto); 12 | return dto; 13 | } 14 | 15 | public static Product toEntity(ProductDto productDto){ 16 | Product product = new Product(); 17 | BeanUtils.copyProperties(productDto, product); 18 | return product; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /06-old-projects/product-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8091 2 | spring.data.mongodb.database=productdb 3 | #spring.data.mongodb.host=10.11.12.13 4 | #spring.data.mongodb.port=27017 5 | #spring.data.mongodb.username= 6 | #spring.data.mongodb.password= 7 | de.flapdoodle.mongodb.embedded.version=4.4.1 -------------------------------------------------------------------------------- /06-old-projects/product-service/src/test/java/com/vinsguru/productservice/ProductServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.productservice; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ProductServiceApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /06-old-projects/user-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/UserServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class UserServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(UserServiceApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.controller; 2 | 3 | import com.vinsguru.userservice.dto.UserDto; 4 | import com.vinsguru.userservice.service.UserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.*; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | 11 | @RestController 12 | @RequestMapping("user") 13 | public class UserController { 14 | 15 | @Autowired 16 | private UserService service; 17 | 18 | @GetMapping("all") 19 | public Flux all(){ 20 | return this.service.all(); 21 | } 22 | 23 | @GetMapping("{id}") 24 | public Mono> getUserById(@PathVariable int id){ 25 | return this.service.getUserById(id) 26 | .map(ResponseEntity::ok) 27 | .defaultIfEmpty(ResponseEntity.notFound().build()); 28 | } 29 | 30 | @PostMapping 31 | public Mono createUser(@RequestBody Mono userDtoMono){ 32 | return this.service.createUser(userDtoMono); 33 | } 34 | 35 | @PutMapping("{id}") 36 | public Mono> updateUser(@PathVariable int id, @RequestBody Mono userDtoMono){ 37 | return this.service.updateUser(id, userDtoMono) 38 | .map(ResponseEntity::ok) 39 | .defaultIfEmpty(ResponseEntity.notFound().build()); 40 | } 41 | 42 | @DeleteMapping("{id}") 43 | public Mono deleteUser(@PathVariable int id){ 44 | return this.service.deleteUser(id); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/controller/UserTransactionController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.controller; 2 | 3 | import com.vinsguru.userservice.dto.TransactionRequestDto; 4 | import com.vinsguru.userservice.dto.TransactionResponseDto; 5 | import com.vinsguru.userservice.entity.UserTransaction; 6 | import com.vinsguru.userservice.service.TransactionService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.*; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | @RestController 13 | @RequestMapping("user/transaction") 14 | public class UserTransactionController { 15 | 16 | @Autowired 17 | private TransactionService transactionService; 18 | 19 | @PostMapping 20 | public Mono createTransaction(@RequestBody Mono requestDtoMono){ 21 | return requestDtoMono.flatMap(this.transactionService::createTransaction); 22 | } 23 | 24 | @GetMapping 25 | public Flux getByUserId(@RequestParam("userId") int userId){ 26 | return this.transactionService.getByUserId(userId); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/dto/TransactionRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.dto; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class TransactionRequestDto { 9 | 10 | private Integer userId; 11 | private Integer amount; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/dto/TransactionResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.dto; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class TransactionResponseDto { 9 | 10 | private Integer userId; 11 | private Integer amount; 12 | private TransactionStatus status; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/dto/TransactionStatus.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.dto; 2 | 3 | public enum TransactionStatus { 4 | APPROVED, 5 | DECLINED; 6 | } 7 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/dto/UserDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.dto; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class UserDto { 9 | 10 | private Integer id; 11 | private String name; 12 | private Integer balance; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.entity; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import org.springframework.data.annotation.Id; 6 | import org.springframework.data.relational.core.mapping.Table; 7 | 8 | @Data 9 | @ToString 10 | @Table("users") 11 | public class User { 12 | 13 | @Id 14 | private Integer id; 15 | private String name; 16 | private Integer balance; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/entity/UserTransaction.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.entity; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | import org.springframework.data.annotation.Id; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Data 10 | @ToString 11 | public class UserTransaction { 12 | 13 | @Id 14 | private Integer id; 15 | private Integer userId; 16 | private Integer amount; 17 | private LocalDateTime transactionDate; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.repository; 2 | 3 | import com.vinsguru.userservice.entity.User; 4 | import org.springframework.data.r2dbc.repository.Modifying; 5 | import org.springframework.data.r2dbc.repository.Query; 6 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 7 | import org.springframework.stereotype.Repository; 8 | import reactor.core.publisher.Mono; 9 | 10 | @Repository 11 | public interface UserRepository extends ReactiveCrudRepository { 12 | 13 | @Modifying 14 | @Query( 15 | "update users " + 16 | "set balance = balance - :amount " + 17 | "where id = :userId " + 18 | "and balance >= :amount" 19 | ) 20 | Mono updateUserBalance(int userId, int amount); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/repository/UserTransactionRepository.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.repository; 2 | 3 | import com.vinsguru.userservice.entity.UserTransaction; 4 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | import reactor.core.publisher.Flux; 7 | 8 | @Repository 9 | public interface UserTransactionRepository extends ReactiveCrudRepository { 10 | 11 | Flux findByUserId(int userId); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/service/DataSetupService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.service; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.core.io.Resource; 7 | import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.util.StreamUtils; 10 | 11 | import java.nio.charset.StandardCharsets; 12 | 13 | @Service 14 | public class DataSetupService implements CommandLineRunner { 15 | 16 | @Value("classpath:h2/init.sql") 17 | private Resource initSql; 18 | 19 | @Autowired 20 | private R2dbcEntityTemplate entityTemplate; 21 | 22 | @Override 23 | public void run(String... args) throws Exception { 24 | String query = StreamUtils.copyToString(initSql.getInputStream(), StandardCharsets.UTF_8); 25 | System.out.println(query); 26 | this.entityTemplate 27 | .getDatabaseClient() 28 | .sql(query) 29 | .then() 30 | .subscribe(); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/service/TransactionService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.service; 2 | 3 | import com.vinsguru.userservice.dto.TransactionRequestDto; 4 | import com.vinsguru.userservice.dto.TransactionResponseDto; 5 | import com.vinsguru.userservice.dto.TransactionStatus; 6 | import com.vinsguru.userservice.entity.UserTransaction; 7 | import com.vinsguru.userservice.repository.UserRepository; 8 | import com.vinsguru.userservice.repository.UserTransactionRepository; 9 | import com.vinsguru.userservice.util.EntityDtoUtil; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | import reactor.core.publisher.Flux; 13 | import reactor.core.publisher.Mono; 14 | 15 | @Service 16 | public class TransactionService { 17 | 18 | @Autowired 19 | private UserRepository userRepository; 20 | 21 | @Autowired 22 | private UserTransactionRepository transactionRepository; 23 | 24 | public Mono createTransaction(final TransactionRequestDto requestDto){ 25 | return this.userRepository.updateUserBalance(requestDto.getUserId(), requestDto.getAmount()) 26 | .filter(Boolean::booleanValue) 27 | .map(b -> EntityDtoUtil.toEntity(requestDto)) 28 | .flatMap(this.transactionRepository::save) 29 | .map(ut -> EntityDtoUtil.toDto(requestDto, TransactionStatus.APPROVED)) 30 | .defaultIfEmpty(EntityDtoUtil.toDto(requestDto, TransactionStatus.DECLINED)); 31 | } 32 | 33 | public Flux getByUserId(int userId){ 34 | return this.transactionRepository.findByUserId(userId); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.service; 2 | 3 | import com.vinsguru.userservice.dto.UserDto; 4 | import com.vinsguru.userservice.repository.UserRepository; 5 | import com.vinsguru.userservice.util.EntityDtoUtil; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | 11 | @Service 12 | public class UserService { 13 | 14 | @Autowired 15 | private UserRepository userRepository; 16 | 17 | public Flux all(){ 18 | return this.userRepository.findAll() 19 | .map(EntityDtoUtil::toDto); 20 | } 21 | 22 | public Mono getUserById(final int userId){ 23 | return this.userRepository.findById(userId) 24 | .map(EntityDtoUtil::toDto); 25 | } 26 | 27 | public Mono createUser(Mono userDtoMono){ 28 | return userDtoMono 29 | .map(EntityDtoUtil::toEntity) 30 | .flatMap(this.userRepository::save) 31 | .map(EntityDtoUtil::toDto); 32 | } 33 | 34 | public Mono updateUser(int id, Mono userDtoMono){ 35 | return this.userRepository.findById(id) 36 | .flatMap(u -> userDtoMono 37 | .map(EntityDtoUtil::toEntity) 38 | .doOnNext(e -> e.setId(id))) 39 | .flatMap(this.userRepository::save) 40 | .map(EntityDtoUtil::toDto); 41 | } 42 | 43 | public Mono deleteUser(int id){ 44 | return this.userRepository.deleteById(id); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/java/com/vinsguru/userservice/util/EntityDtoUtil.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice.util; 2 | 3 | import com.vinsguru.userservice.dto.TransactionRequestDto; 4 | import com.vinsguru.userservice.dto.TransactionResponseDto; 5 | import com.vinsguru.userservice.dto.TransactionStatus; 6 | import com.vinsguru.userservice.dto.UserDto; 7 | import com.vinsguru.userservice.entity.User; 8 | import com.vinsguru.userservice.entity.UserTransaction; 9 | import org.springframework.beans.BeanUtils; 10 | 11 | import java.time.LocalDateTime; 12 | 13 | public class EntityDtoUtil { 14 | 15 | public static UserDto toDto(User user){ 16 | UserDto dto = new UserDto(); 17 | BeanUtils.copyProperties(user, dto); 18 | return dto; 19 | } 20 | 21 | public static User toEntity(UserDto dto){ 22 | User user = new User(); 23 | BeanUtils.copyProperties(dto, user); 24 | return user; 25 | } 26 | 27 | public static UserTransaction toEntity(TransactionRequestDto requestDto){ 28 | UserTransaction ut = new UserTransaction(); 29 | ut.setUserId(requestDto.getUserId()); 30 | ut.setAmount(requestDto.getAmount()); 31 | ut.setTransactionDate(LocalDateTime.now()); 32 | return ut; 33 | } 34 | 35 | public static TransactionResponseDto toDto(TransactionRequestDto requestDto, TransactionStatus status){ 36 | TransactionResponseDto responseDto = new TransactionResponseDto(); 37 | responseDto.setAmount(requestDto.getAmount()); 38 | responseDto.setUserId(requestDto.getUserId()); 39 | responseDto.setStatus(status); 40 | return responseDto; 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8092 2 | spring.r2dbc.url=r2dbc:h2:mem:///userdb 3 | #spring.r2dbc.url=r2dbc:postgresql://localhost:5432/userdb 4 | spring.r2dbc.username= 5 | spring.r2dbc.password= 6 | -------------------------------------------------------------------------------- /06-old-projects/user-service/src/main/resources/h2/init.sql: -------------------------------------------------------------------------------- 1 | create table users ( 2 | id bigint auto_increment, 3 | name varchar(50), 4 | balance int, 5 | primary key (id) 6 | ); 7 | 8 | create table user_transaction( 9 | id bigint auto_increment, 10 | user_id bigint, 11 | amount int, 12 | transaction_date timestamp, 13 | foreign key (user_id) references users (id) on delete cascade 14 | ); 15 | 16 | insert into users 17 | (name, balance) 18 | values 19 | ('sam', 1000), 20 | ('mike', 1200), 21 | ('jake', 800), 22 | ('marshal', 2000); -------------------------------------------------------------------------------- /06-old-projects/user-service/src/test/java/com/vinsguru/userservice/UserServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.userservice; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class UserServiceApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.0 9 | 10 | 11 | com.vinsguru 12 | webflux-demo 13 | 0.0.1-SNAPSHOT 14 | webflux-demo 15 | Demo project for Spring Boot 16 | 17 | 21 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-webflux 23 | 24 | 25 | org.projectlombok 26 | lombok 27 | true 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-test 32 | test 33 | 34 | 35 | io.projectreactor 36 | reactor-test 37 | test 38 | 39 | 40 | 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-maven-plugin 46 | 47 | 48 | 49 | org.projectlombok 50 | lombok 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/WebfluxDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class WebfluxDemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(WebfluxDemoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/config/CalculatorHandler.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.config; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.web.reactive.function.server.ServerRequest; 5 | import org.springframework.web.reactive.function.server.ServerResponse; 6 | import reactor.core.publisher.Mono; 7 | 8 | import java.util.function.BiFunction; 9 | 10 | @Service 11 | public class CalculatorHandler { 12 | 13 | //creating multiple handlers intentionally 14 | // calculator/{a}/{b} 15 | public Mono additionHandler(ServerRequest request){ 16 | return process(request, (a, b) -> ServerResponse.ok().bodyValue(a + b)); 17 | } 18 | 19 | public Mono subtractionHandler(ServerRequest request){ 20 | return process(request, (a, b) -> ServerResponse.ok().bodyValue(a - b)); 21 | } 22 | 23 | public Mono multiplicationHandler(ServerRequest request){ 24 | return process(request, (a, b) -> ServerResponse.ok().bodyValue(a * b)); 25 | } 26 | 27 | public Mono divisionHandler(ServerRequest request){ 28 | return process(request, (a, b) -> 29 | b != 0 ? ServerResponse.ok().bodyValue(a / b) : 30 | ServerResponse.badRequest().bodyValue("b can not be 0") 31 | ); 32 | } 33 | 34 | private Mono process(ServerRequest request, 35 | BiFunction> opLogic){ 36 | int a = getValue(request, "a"); 37 | int b = getValue(request, "b"); 38 | return opLogic.apply(a, b); 39 | } 40 | 41 | private int getValue(ServerRequest request, String key){ 42 | return Integer.parseInt(request.pathVariable(key)); 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/config/CalculatorRouterConfig.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.config; 2 | 3 | import com.vinsguru.webfluxdemo.exception.InputValidationException; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.reactive.function.server.*; 8 | 9 | @Configuration 10 | public class CalculatorRouterConfig { 11 | 12 | @Autowired 13 | private CalculatorHandler handler; 14 | 15 | @Bean 16 | public RouterFunction highLevelCalculatorRouter(){ 17 | return RouterFunctions.route() 18 | .path("calculator", this::serverResponseRouterFunction) 19 | .build(); 20 | } 21 | 22 | private RouterFunction serverResponseRouterFunction(){ 23 | return RouterFunctions.route() 24 | .GET("{a}/{b}", isOperation("+") , handler::additionHandler) 25 | .GET("{a}/{b}", isOperation("-"), handler::subtractionHandler) 26 | .GET("{a}/{b}", isOperation("*"), handler::multiplicationHandler) 27 | .GET("{a}/{b}", isOperation("/"), handler::divisionHandler) 28 | .GET("{a}/{b}", req -> ServerResponse.badRequest().bodyValue("OP should be + - * /")) 29 | .build(); 30 | } 31 | 32 | private RequestPredicate isOperation(String operation){ 33 | return RequestPredicates.headers(headers -> operation.equals(headers.asHttpHeaders() 34 | .toSingleValueMap() 35 | .get("OP"))); 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/controller/MathController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.controller; 2 | 3 | import com.vinsguru.webfluxdemo.dto.Response; 4 | import com.vinsguru.webfluxdemo.service.MathService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.List; 12 | 13 | @RestController 14 | @RequestMapping("math") 15 | public class MathController { 16 | 17 | @Autowired 18 | private MathService mathService; 19 | 20 | @GetMapping("square/{input}") 21 | public Response findSquare(@PathVariable int input){ 22 | if(input < 10) 23 | throw new IllegalArgumentException(); 24 | return this.mathService.findSquare(input); 25 | } 26 | 27 | @GetMapping("table/{input}") 28 | public List multiplicationTable(@PathVariable int input){ 29 | return this.mathService.multiplicationTable(input); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/controller/ParamsController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.controller; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestParam; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import reactor.core.publisher.Flux; 8 | 9 | @RestController 10 | @RequestMapping("jobs") 11 | public class ParamsController { 12 | 13 | @GetMapping("search") 14 | public Flux searchJobs(@RequestParam("count") int count, 15 | @RequestParam("page") int page){ 16 | return Flux.just(count, page); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/controller/ReactiveMathController.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.controller; 2 | 3 | import com.vinsguru.webfluxdemo.dto.MultiplyRequestDto; 4 | import com.vinsguru.webfluxdemo.dto.Response; 5 | import com.vinsguru.webfluxdemo.service.ReactiveMathService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.web.bind.annotation.*; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | import java.util.Map; 13 | 14 | @RestController 15 | @RequestMapping("reactive-math") 16 | public class ReactiveMathController { 17 | 18 | @Autowired 19 | private ReactiveMathService mathService; 20 | 21 | @GetMapping("square/{input}") 22 | public Mono findSquare(@PathVariable int input){ 23 | return this.mathService.findSquare(input) 24 | .defaultIfEmpty(new Response(-1)); 25 | } 26 | 27 | @GetMapping("table/{input}") 28 | public Flux multiplicationTable(@PathVariable int input){ 29 | return this.mathService.multiplicationTable(input); 30 | } 31 | 32 | @GetMapping(value = "table/{input}/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 33 | public Flux multiplicationTableStream(@PathVariable int input) { 34 | return this.mathService.multiplicationTable(input); 35 | } 36 | 37 | @PostMapping("multiply") 38 | public Mono multiply(@RequestBody Mono requestDtoMono, 39 | @RequestHeader Map headers){ 40 | System.out.println(headers); 41 | return this.mathService.multiply(requestDtoMono); 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/dto/InputFailedValidationResponse.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.dto; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class InputFailedValidationResponse { 9 | 10 | private int errorCode; 11 | private int input; 12 | private String message; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/dto/MultiplyRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.dto; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class MultiplyRequestDto { 9 | 10 | private int first; 11 | private int second; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/dto/Response.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.dto; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import lombok.ToString; 6 | 7 | import java.util.Date; 8 | 9 | @Data 10 | @ToString 11 | @NoArgsConstructor 12 | public class Response { 13 | 14 | private Date date = new Date(); 15 | private int output; 16 | 17 | public Response(int output) { 18 | this.output = output; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/exception/InputValidationException.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.exception; 2 | 3 | public class InputValidationException extends RuntimeException{ 4 | 5 | private static final String MSG = "allowed range is 10 - 20"; 6 | private static final int errorCode = 100; 7 | private final int input; 8 | 9 | public InputValidationException(int input) { 10 | super(MSG); 11 | this.input = input; 12 | } 13 | 14 | public int getErrorCode() { 15 | return errorCode; 16 | } 17 | 18 | public int getInput() { 19 | return input; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/exceptionhandler/InputValidationHandler.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.exceptionhandler; 2 | 3 | import com.vinsguru.webfluxdemo.dto.InputFailedValidationResponse; 4 | import com.vinsguru.webfluxdemo.exception.InputValidationException; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | 9 | @ControllerAdvice 10 | public class InputValidationHandler { 11 | 12 | @ExceptionHandler(InputValidationException.class) 13 | public ResponseEntity handleException(InputValidationException ex){ 14 | InputFailedValidationResponse response = new InputFailedValidationResponse(); 15 | response.setErrorCode(ex.getErrorCode()); 16 | response.setInput(ex.getInput()); 17 | response.setMessage(ex.getMessage()); 18 | return ResponseEntity.badRequest().body(response); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/service/MathService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.service; 2 | 3 | import com.vinsguru.webfluxdemo.dto.Response; 4 | import org.springframework.stereotype.Service; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.IntStream; 9 | 10 | @Service 11 | public class MathService { 12 | 13 | public Response findSquare(int input){ 14 | return new Response(input * input); 15 | } 16 | 17 | public List multiplicationTable(int input){ 18 | return IntStream.rangeClosed(1, 10) 19 | .peek(i -> SleepUtil.sleepSeconds(1)) 20 | .peek(i -> System.out.println("math-service processing : " + i)) 21 | .mapToObj(i -> new Response(i * input)) 22 | .collect(Collectors.toList()); 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/service/ReactiveMathService.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.service; 2 | 3 | import com.vinsguru.webfluxdemo.dto.MultiplyRequestDto; 4 | import com.vinsguru.webfluxdemo.dto.Response; 5 | import org.springframework.stereotype.Service; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | 9 | import java.time.Duration; 10 | 11 | @Service 12 | public class ReactiveMathService { 13 | 14 | public Mono findSquare(int input){ 15 | return Mono.fromSupplier(() -> input * input) 16 | .map(Response::new); 17 | } 18 | 19 | public Flux multiplicationTable(int input){ 20 | return Flux.range(1, 10) 21 | .delayElements(Duration.ofSeconds(1)) 22 | //.doOnNext(i -> SleepUtil.sleepSeconds(1)) 23 | .doOnNext(i -> System.out.println("reactive-math-service processing : " + i)) 24 | .map(i -> new Response(i * input)); 25 | } 26 | 27 | public Mono multiply(Mono dtoMono){ 28 | return dtoMono 29 | .map(dto -> dto.getFirst() * dto.getSecond()) 30 | .map(Response::new); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/java/com/vinsguru/webfluxdemo/service/SleepUtil.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.service; 2 | 3 | public class SleepUtil { 4 | 5 | public static void sleepSeconds(int seconds){ 6 | try { 7 | Thread.sleep(seconds * 1000); 8 | } catch (InterruptedException e) { 9 | e.printStackTrace(); 10 | } 11 | } 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/config/WebClientConfig.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.reactive.function.client.*; 6 | import reactor.core.publisher.Mono; 7 | 8 | @Configuration 9 | public class WebClientConfig { 10 | 11 | @Bean 12 | public WebClient webClient(){ 13 | return WebClient.builder() 14 | .baseUrl("http://localhost:8080") 15 | .filter(this::sessionToken) 16 | .build(); 17 | } 18 | 19 | /* private Mono sessionToken(ClientRequest request, ExchangeFunction ex){ 20 | System.out.println("generating session token"); 21 | ClientRequest clientRequest = ClientRequest.from(request).headers(h -> h.setBearerAuth("some-lengthy-jwt")).build(); 22 | return ex.exchange(clientRequest); 23 | }*/ 24 | 25 | private Mono sessionToken(ClientRequest request, ExchangeFunction ex){ 26 | //auth --> basic or oauth 27 | ClientRequest clientRequest = request.attribute("auth") 28 | .map(v -> v.equals("basic") ? withBasicAuth(request) : withOAuth(request)) 29 | .orElse(request); 30 | return ex.exchange(clientRequest); 31 | } 32 | 33 | private ClientRequest withBasicAuth(ClientRequest request){ 34 | return ClientRequest.from(request) 35 | .headers(h -> h.setBasicAuth("username", "password")) 36 | .build(); 37 | } 38 | 39 | private ClientRequest withOAuth(ClientRequest request){ 40 | return ClientRequest.from(request) 41 | .headers(h -> h.setBearerAuth("some-token")) 42 | .build(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webclient/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webclient; 2 | 3 | import org.springframework.boot.test.context.SpringBootTest; 4 | 5 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) 6 | class BaseTest { 7 | 8 | 9 | 10 | } 11 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webclient/Lec01GetSingleResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webclient; 2 | 3 | import com.vinsguru.webfluxdemo.dto.Response; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.reactive.function.client.WebClient; 7 | import reactor.core.publisher.Mono; 8 | import reactor.test.StepVerifier; 9 | 10 | public class Lec01GetSingleResponseTest extends BaseTest { 11 | 12 | @Autowired 13 | private WebClient webClient; 14 | 15 | @Test 16 | public void blockTest(){ 17 | 18 | Response response = this.webClient 19 | .get() 20 | .uri("reactive-math/square/{number}", 5) 21 | .retrieve() 22 | .bodyToMono(Response.class) // Mono 23 | .block(); 24 | 25 | System.out.println( 26 | response 27 | ); 28 | 29 | } 30 | 31 | @Test 32 | public void stepVerifierTest(){ 33 | 34 | Mono responseMono = this.webClient 35 | .get() 36 | .uri("reactive-math/square/{number}", 5) 37 | .retrieve() 38 | .bodyToMono(Response.class); // Mono 39 | 40 | 41 | StepVerifier.create(responseMono) 42 | .expectNextMatches(r -> r.getOutput() == 25) 43 | .verifyComplete(); 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webclient/Lec02GetMultiResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webclient; 2 | 3 | import com.vinsguru.webfluxdemo.dto.Response; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.reactive.function.client.WebClient; 7 | import reactor.core.publisher.Flux; 8 | import reactor.test.StepVerifier; 9 | 10 | public class Lec02GetMultiResponseTest extends BaseTest { 11 | 12 | @Autowired 13 | private WebClient webClient; 14 | 15 | @Test 16 | public void fluxTest(){ 17 | 18 | Flux responseFlux = this.webClient 19 | .get() 20 | .uri("reactive-math/table/{number}", 5) 21 | .retrieve() 22 | .bodyToFlux(Response.class) 23 | .doOnNext(System.out::println); 24 | 25 | StepVerifier.create(responseFlux) 26 | .expectNextCount(10) 27 | .verifyComplete(); 28 | 29 | } 30 | 31 | @Test 32 | public void fluxStreamTest(){ 33 | Flux responseFlux = this.webClient 34 | .get() 35 | .uri("reactive-math/table/{number}/stream", 5) 36 | .retrieve() 37 | .bodyToFlux(Response.class) 38 | .doOnNext(System.out::println); 39 | 40 | StepVerifier.create(responseFlux) 41 | .expectNextCount(100) 42 | .verifyComplete(); 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webclient/Lec03PostRequestTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webclient; 2 | 3 | import com.vinsguru.webfluxdemo.dto.MultiplyRequestDto; 4 | import com.vinsguru.webfluxdemo.dto.Response; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.reactive.function.client.WebClient; 8 | import reactor.core.publisher.Mono; 9 | import reactor.test.StepVerifier; 10 | 11 | public class Lec03PostRequestTest extends BaseTest { 12 | 13 | @Autowired 14 | private WebClient webClient; 15 | 16 | @Test 17 | public void postTest(){ 18 | Mono responseMono = this.webClient 19 | .post() 20 | .uri("reactive-math/multiply") 21 | .bodyValue(buildRequestDto(5, 2)) 22 | .retrieve() 23 | .bodyToMono(Response.class) 24 | .doOnNext(System.out::println); 25 | 26 | StepVerifier.create(responseMono) 27 | .expectNextCount(1) 28 | .verifyComplete(); 29 | 30 | } 31 | 32 | private MultiplyRequestDto buildRequestDto(int a, int b){ 33 | MultiplyRequestDto dto = new MultiplyRequestDto(); 34 | dto.setFirst(a); 35 | dto.setSecond(b); 36 | return dto; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webclient/Lec04HeadersTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webclient; 2 | 3 | import com.vinsguru.webfluxdemo.dto.MultiplyRequestDto; 4 | import com.vinsguru.webfluxdemo.dto.Response; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.reactive.function.client.WebClient; 8 | import reactor.core.publisher.Mono; 9 | import reactor.test.StepVerifier; 10 | 11 | public class Lec04HeadersTest extends BaseTest { 12 | 13 | @Autowired 14 | private WebClient webClient; 15 | 16 | @Test 17 | public void headersTest(){ 18 | Mono responseMono = this.webClient 19 | .post() 20 | .uri("reactive-math/multiply") 21 | .bodyValue(buildRequestDto(5, 2)) 22 | .headers(h -> h.set("someKey", "someVal")) 23 | .retrieve() 24 | .bodyToMono(Response.class) 25 | .doOnNext(System.out::println); 26 | 27 | StepVerifier.create(responseMono) 28 | .expectNextCount(1) 29 | .verifyComplete(); 30 | 31 | StepVerifier.create(responseMono) 32 | .expectNextCount(1) 33 | .verifyComplete(); 34 | 35 | } 36 | 37 | private MultiplyRequestDto buildRequestDto(int a, int b){ 38 | MultiplyRequestDto dto = new MultiplyRequestDto(); 39 | dto.setFirst(a); 40 | dto.setSecond(b); 41 | return dto; 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webclient/Lec05BadRequestTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webclient; 2 | 3 | import com.vinsguru.webfluxdemo.dto.Response; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.reactive.function.client.WebClient; 7 | import org.springframework.web.reactive.function.client.WebClientResponseException; 8 | import reactor.core.publisher.Mono; 9 | import reactor.test.StepVerifier; 10 | 11 | public class Lec05BadRequestTest extends BaseTest { 12 | 13 | @Autowired 14 | private WebClient webClient; 15 | 16 | @Test 17 | public void badRequestTest(){ 18 | 19 | Mono responseMono = this.webClient 20 | .get() 21 | .uri("reactive-math/square/{number}/throw", 5) 22 | .retrieve() 23 | .bodyToMono(Response.class) 24 | .doOnNext(System.out::println) 25 | .doOnError(err -> System.out.println(err.getMessage())); 26 | 27 | StepVerifier.create(responseMono) 28 | .verifyError(WebClientResponseException.BadRequest.class); 29 | 30 | 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webclient/Lec06ExchangeTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webclient; 2 | 3 | import com.vinsguru.webfluxdemo.dto.InputFailedValidationResponse; 4 | import com.vinsguru.webfluxdemo.dto.Response; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.reactive.function.client.ClientResponse; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | import reactor.core.publisher.Mono; 10 | import reactor.test.StepVerifier; 11 | 12 | public class Lec06ExchangeTest extends BaseTest { 13 | 14 | @Autowired 15 | private WebClient webClient; 16 | 17 | // exchange = retrieve + additional info http status code 18 | @Test 19 | public void badRequestTest(){ 20 | 21 | Mono responseMono = this.webClient 22 | .get() 23 | .uri("reactive-math/square/{number}/throw", 5) 24 | .exchangeToMono(this::exchange) 25 | .doOnNext(System.out::println) 26 | .doOnError(err -> System.out.println(err.getMessage())); 27 | 28 | StepVerifier.create(responseMono) 29 | .expectNextCount(1) 30 | .verifyComplete(); 31 | 32 | } 33 | 34 | private Mono exchange(ClientResponse cr){ 35 | if(cr.statusCode().is4xxClientError()) 36 | return cr.bodyToMono(InputFailedValidationResponse.class); 37 | else 38 | return cr.bodyToMono(Response.class); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webclient/Lec07QueryParamsTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webclient; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import reactor.core.publisher.Flux; 7 | import reactor.test.StepVerifier; 8 | 9 | import java.util.Map; 10 | 11 | public class Lec07QueryParamsTest extends BaseTest{ 12 | 13 | @Autowired 14 | private WebClient webClient; 15 | 16 | String queryString = "http://localhost:8080/jobs/search?count={count}&page={page}"; 17 | 18 | @Test 19 | public void queryParamsTest(){ 20 | 21 | /* URI uri = UriComponentsBuilder.fromUriString(queryString) 22 | .build(10, 20);*/ 23 | 24 | Map map = Map.of( 25 | "count", 10, 26 | "page", 20 27 | ); 28 | 29 | Flux integerFlux = this.webClient 30 | .get() 31 | .uri(b -> b.path("jobs/search").query("count={count}&page={page}").build(map)) 32 | .retrieve() 33 | .bodyToFlux(Integer.class) 34 | .doOnNext(System.out::println); 35 | 36 | StepVerifier.create(integerFlux) 37 | .expectNextCount(2) 38 | .verifyComplete(); 39 | 40 | 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webclient/Lec08AttributesTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webclient; 2 | 3 | import com.vinsguru.webfluxdemo.dto.MultiplyRequestDto; 4 | import com.vinsguru.webfluxdemo.dto.Response; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.reactive.function.client.WebClient; 8 | import reactor.core.publisher.Mono; 9 | import reactor.test.StepVerifier; 10 | 11 | public class Lec08AttributesTest extends BaseTest { 12 | 13 | @Autowired 14 | private WebClient webClient; 15 | 16 | @Test 17 | public void headersTest(){ 18 | Mono responseMono = this.webClient 19 | .post() 20 | .uri("reactive-math/multiply") 21 | .bodyValue(buildRequestDto(5, 2)) 22 | // .attribute("auth", "oauth") 23 | .retrieve() 24 | .bodyToMono(Response.class) 25 | .doOnNext(System.out::println); 26 | 27 | StepVerifier.create(responseMono) 28 | .expectNextCount(1) 29 | .verifyComplete(); 30 | 31 | } 32 | 33 | private MultiplyRequestDto buildRequestDto(int a, int b){ 34 | MultiplyRequestDto dto = new MultiplyRequestDto(); 35 | dto.setFirst(a); 36 | dto.setSecond(b); 37 | return dto; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webclient/Lec09AssignmentTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webclient; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | import reactor.test.StepVerifier; 9 | 10 | public class Lec09AssignmentTest extends BaseTest { 11 | 12 | private static final String FORMAT = "%d %s %d = %s"; 13 | private static final int A = 10; 14 | 15 | @Autowired 16 | private WebClient webClient; 17 | 18 | @Test 19 | public void test(){ 20 | 21 | Flux flux = Flux.range(1, 5) 22 | .flatMap(b -> Flux.just("+", "-", "*", "/") 23 | .flatMap(op -> send(b, op))) 24 | .doOnNext(System.out::println); 25 | 26 | StepVerifier.create(flux) 27 | .expectNextCount(20) 28 | .verifyComplete(); 29 | 30 | } 31 | 32 | private Mono send(int b, String op){ 33 | return this.webClient 34 | .get() 35 | .uri("calculator/{a}/{b}", A, b) 36 | .headers(h -> h.set("OP", op)) 37 | .retrieve() 38 | .bodyToMono(String.class) 39 | .map(v -> String.format(FORMAT, A, op, b, v)); 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webtestclient/Lec03ControllerPostTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webtestclient; 2 | 3 | import com.vinsguru.webfluxdemo.controller.ReactiveMathController; 4 | import com.vinsguru.webfluxdemo.dto.MultiplyRequestDto; 5 | import com.vinsguru.webfluxdemo.dto.Response; 6 | import com.vinsguru.webfluxdemo.service.ReactiveMathService; 7 | import org.junit.jupiter.api.Test; 8 | import org.mockito.Mockito; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; 11 | import org.springframework.boot.test.mock.mockito.MockBean; 12 | import org.springframework.http.MediaType; 13 | import org.springframework.test.web.reactive.server.WebTestClient; 14 | import reactor.core.publisher.Mono; 15 | 16 | @WebFluxTest(ReactiveMathController.class) 17 | public class Lec03ControllerPostTest { 18 | 19 | @Autowired 20 | private WebTestClient client; 21 | 22 | @MockBean 23 | private ReactiveMathService reactiveMathService; 24 | 25 | @Test 26 | public void postTest(){ 27 | Mockito.when(reactiveMathService.multiply(Mockito.any())).thenReturn(Mono.just(new Response(1))); 28 | 29 | this.client 30 | .post() 31 | .uri("/reactive-math/multiply") 32 | .accept(MediaType.APPLICATION_JSON) 33 | .headers(h -> h.setBasicAuth("username", "password")) 34 | .headers(h -> h.set("somekey", "somevalue")) 35 | .bodyValue(new MultiplyRequestDto()) 36 | .exchange() 37 | .expectStatus().is2xxSuccessful(); 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /06-old-projects/webflux-demo/src/test/java/com/vinsguru/webfluxdemo/webtestclient/Lec04ErrorHandlingTest.java: -------------------------------------------------------------------------------- 1 | package com.vinsguru.webfluxdemo.webtestclient; 2 | 3 | import com.vinsguru.webfluxdemo.controller.ReactiveMathValidationController; 4 | import com.vinsguru.webfluxdemo.dto.MultiplyRequestDto; 5 | import com.vinsguru.webfluxdemo.dto.Response; 6 | import com.vinsguru.webfluxdemo.service.ReactiveMathService; 7 | import org.junit.jupiter.api.Test; 8 | import org.mockito.Mockito; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; 11 | import org.springframework.boot.test.mock.mockito.MockBean; 12 | import org.springframework.http.MediaType; 13 | import org.springframework.test.web.reactive.server.WebTestClient; 14 | import reactor.core.publisher.Mono; 15 | 16 | @WebFluxTest(ReactiveMathValidationController.class) 17 | public class Lec04ErrorHandlingTest { 18 | 19 | @Autowired 20 | private WebTestClient client; 21 | 22 | @MockBean 23 | private ReactiveMathService reactiveMathService; 24 | 25 | @Test 26 | public void errorHandlingTest(){ 27 | Mockito.when(reactiveMathService.findSquare(Mockito.anyInt())).thenReturn(Mono.just(new Response(1))); 28 | 29 | this.client 30 | .get() 31 | .uri("/reactive-math/square/{number}/throw", 5) // 10-20 32 | .exchange() 33 | .expectStatus().isBadRequest() 34 | .expectBody() 35 | .jsonPath("$.message").isEqualTo("allowed range is 10 - 20") 36 | .jsonPath("$.errorCode").isEqualTo(100) 37 | .jsonPath("$.input").isEqualTo(5); 38 | } 39 | 40 | } 41 | --------------------------------------------------------------------------------