├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── chapter-01 ├── build.gradle └── src │ └── main │ └── java │ └── org │ └── rpis5 │ └── chapters │ └── chapter_01 │ ├── ChapterFirstApplication.java │ ├── RootController.java │ ├── callbacks │ ├── AsyncShoppingCardService.java │ ├── OrdersService.java │ ├── ShoppingCardService.java │ └── SyncShoppingCardService.java │ ├── commons │ ├── ExamplesCollection.java │ ├── Input.java │ └── Output.java │ ├── communication │ ├── ServiceOne.java │ ├── ServiceTwo.java │ └── package-info.java │ ├── completion_stage │ ├── CompletionStageShoppingCardService.java │ ├── OrdersService.java │ └── ShoppingCardService.java │ ├── futures │ ├── FutureShoppingCardService.java │ ├── OrdersService.java │ └── ShoppingCardService.java │ ├── imperative │ ├── BlockingShoppingCardService.java │ ├── OrdersService.java │ └── ShoppingCardService.java │ └── spring_futures │ ├── AsyncServiceOne.java │ ├── AsyncServiceTwo.java │ └── package-info.java ├── chapter-02 ├── build.gradle └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_02 │ │ │ ├── observer │ │ │ ├── ConcreteObserverA.java │ │ │ ├── ConcreteObserverB.java │ │ │ ├── ConcreteSubject.java │ │ │ ├── Iterator.java │ │ │ ├── Observer.java │ │ │ ├── ParallelSubject.java │ │ │ ├── RxObserver.java │ │ │ └── Subject.java │ │ │ ├── pub_sub_app │ │ │ ├── Application.java │ │ │ ├── Temperature.java │ │ │ ├── TemperatureController.java │ │ │ └── TemperatureSensor.java │ │ │ ├── rx_app │ │ │ ├── Application.java │ │ │ ├── Temperature.java │ │ │ ├── TemperatureController.java │ │ │ └── TemperatureSensor.java │ │ │ └── seach_engine │ │ │ ├── FutureSearchEngine.java │ │ │ ├── IterableSearchEngine.java │ │ │ ├── RxSearchEngine.java │ │ │ └── SearchEngine.java │ └── resources │ │ ├── application.properties │ │ └── static │ │ ├── index-min.html │ │ └── index.html │ └── test │ └── java │ └── org │ └── rpis5 │ └── chapters │ └── chapter_02 │ ├── observer │ └── ObserverTest.java │ └── rxjava │ └── RxJavaExamplesTest.java ├── chapter-03 ├── async.vs.reactive │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── rpis5 │ │ │ │ └── chapters │ │ │ │ └── chapter_03 │ │ │ │ └── async_vs_reactive │ │ │ │ ├── CompletableFutureAsPublisher.java │ │ │ │ └── PublisherAsCompletableFuture.java │ │ └── resources │ │ │ └── application.yaml │ │ └── test │ │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_03 │ │ └── async_vs_reactive │ │ ├── CompletableFutureAsPublisherTest.java │ │ └── CompletionPublisherTest.java ├── conversion_problem │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── rpis5 │ │ │ │ └── chapters │ │ │ │ └── chapter_03 │ │ │ │ └── conversion_problem │ │ │ │ ├── AsyncAdapters.java │ │ │ │ ├── AsyncDatabaseClient.java │ │ │ │ ├── ConversionProblemApp.java │ │ │ │ ├── FakeAsyncDatabaseClient.java │ │ │ │ ├── MyController.java │ │ │ │ └── TestController.java │ │ └── resources │ │ │ └── application.yaml │ │ └── test │ │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_03 │ │ └── conversion_problem │ │ └── MyControllerTest.java ├── jdk9 │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_03 │ │ └── jdk9 │ │ └── AdapterExample.java ├── news-service │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── rpis5 │ │ │ │ └── chapters │ │ │ │ └── chapter_03 │ │ │ │ └── news_service │ │ │ │ ├── DBPublisher.java │ │ │ │ ├── NamedSubscriber.java │ │ │ │ ├── NewsPreparationOperator.java │ │ │ │ ├── NewsServiceApp.java │ │ │ │ ├── NewsServicePublisher.java │ │ │ │ ├── NewsServiceSubscriber.java │ │ │ │ ├── ResubscribableErrorLettter.java │ │ │ │ ├── ScheduledPublisher.java │ │ │ │ ├── SmartMulticastProcessor.java │ │ │ │ ├── SubscriptionUtils.java │ │ │ │ └── dto │ │ │ │ ├── News.java │ │ │ │ └── NewsLetter.java │ │ └── resources │ │ │ └── application.yaml │ │ └── test │ │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_03 │ │ ├── WithEmbeddedMongo.java │ │ └── news_service │ │ ├── DBPublisherTest.java │ │ ├── NewsHarness.java │ │ ├── NewsPreparationOperatorTest.java │ │ ├── NewsServicePublisherTest.java │ │ ├── NewsServiceSubscriberTest.java │ │ ├── NewsServiceSubscriberWhiteboxTest.java │ │ ├── ScheduledPublisherTest.java │ │ ├── SmartMulticastProcessorTest.java │ │ └── StubNewsLetter.java ├── push.vs.pull │ ├── build.gradle │ └── src │ │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_03 │ │ │ ├── batched_pull_model │ │ │ ├── AsyncDatabaseClient.java │ │ │ ├── DelayedFakeAsyncDatabaseClient.java │ │ │ ├── Item.java │ │ │ └── Puller.java │ │ │ ├── pure_pull_model │ │ │ ├── AsyncDatabaseClient.java │ │ │ ├── DelayedFakeAsyncDatabaseClient.java │ │ │ ├── Item.java │ │ │ └── Puller.java │ │ │ ├── push_model │ │ │ ├── AsyncDatabaseClient.java │ │ │ ├── DelayedFakeAsyncDatabaseClient.java │ │ │ ├── Item.java │ │ │ └── Puller.java │ │ │ └── push_pull_model │ │ │ ├── AsyncDatabaseClient.java │ │ │ ├── DelayedFakeAsyncDatabaseClient.java │ │ │ ├── Item.java │ │ │ ├── Puller.java │ │ │ ├── SubscriptionUtils.java │ │ │ └── TakeFilterOperator.java │ │ └── test │ │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_03 │ │ ├── batched_pull_model │ │ └── PullerTest.java │ │ ├── pure_pull_model │ │ └── PullerTest.java │ │ ├── push_model │ │ └── PullerTest.java │ │ └── push_pull_model │ │ ├── PullerTest.java │ │ └── TCKPullerTest.java ├── rxjava-reactivestreams-ratpack │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── rpis5 │ │ │ │ └── chapters │ │ │ │ └── chapter_03 │ │ │ │ └── rxjava_reactivestreams │ │ │ │ ├── AsyncFileSubscriber.java │ │ │ │ ├── FileService.java │ │ │ │ ├── LogService.java │ │ │ │ ├── LogServiceApplication.java │ │ │ │ ├── RxFileService.java │ │ │ │ └── RxLogService.java │ │ └── resources │ │ │ └── application.yaml │ │ └── test │ │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_03 │ │ └── rxjava_reactivestreams │ │ └── AsyncFileSubscriberTest.java └── vert.x │ ├── build.gradle │ └── src │ └── main │ └── java │ └── org │ └── rpis5 │ └── chapters │ └── chapter_03 │ └── vert │ └── x │ ├── LogService.java │ ├── MockLogService.java │ └── VertxDemoApp.java ├── chapter-04 ├── build.gradle └── src │ ├── main │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_04 │ │ └── Dummy.java │ └── test │ └── java │ └── org │ └── rpis5 │ └── chapters │ └── chapter_04 │ ├── CpuLoadTest.java │ ├── ReactiveContextTest.java │ ├── ReactorEssentialsTest.java │ └── ThreadLocalProblemShowcaseTest.java ├── chapter-05 ├── build.gradle └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_05 │ │ │ ├── core │ │ │ ├── MaybeReactiveAdapter.java │ │ │ └── ReactiveFileReader.java │ │ │ └── reactive_app │ │ │ ├── Chapter5ReactiveApplication.java │ │ │ ├── SensorReadingRepository.java │ │ │ ├── SensorsReadings.java │ │ │ └── SensorsSimulator.java │ └── resources │ │ ├── META-INF │ │ ├── spring-configuration-metadata-whitelist.properties │ │ ├── spring-configuration-metadata.json │ │ ├── spring.provides │ │ └── thin.properties │ │ ├── application.yaml │ │ └── hamlet.txt │ └── test │ └── java │ └── org │ └── rpis5 │ └── chapters │ └── chapter_05 │ └── core │ ├── MaybeReactiveAdapterTest.java │ └── ReactiveFileReaderTest.java ├── chapter-06 ├── functional-spring-boot │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── rpis5 │ │ │ │ └── chapters │ │ │ │ └── chapter_06 │ │ │ │ └── functional │ │ │ │ └── springboot │ │ │ │ ├── DemoApplication.java │ │ │ │ ├── InMemoryOrderRepository.java │ │ │ │ ├── Order.java │ │ │ │ ├── OrderHandler.java │ │ │ │ ├── OrderRepository.java │ │ │ │ └── ServerRedirectHandler.java │ │ └── resources │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_06 │ │ └── functional │ │ └── springboot │ │ └── DemoApplicationTest.java ├── samples │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── rpis5 │ │ │ │ └── chapters │ │ │ │ └── chapter_06 │ │ │ │ ├── functional │ │ │ │ └── password │ │ │ │ │ └── verification │ │ │ │ │ ├── client │ │ │ │ │ ├── DefaultPasswordVerificationService.java │ │ │ │ │ ├── PasswordDTO.java │ │ │ │ │ └── PasswordVerificationService.java │ │ │ │ │ └── server │ │ │ │ │ └── StandaloneApplication.java │ │ │ │ ├── json │ │ │ │ ├── Message.java │ │ │ │ └── ReactiveJsonParser.java │ │ │ │ ├── sse │ │ │ │ ├── ServerSentController.java │ │ │ │ ├── StockItem.java │ │ │ │ └── StocksService.java │ │ │ │ ├── webclient │ │ │ │ └── TestWebClient.java │ │ │ │ └── websocket │ │ │ │ ├── EchoWebSocketHandler.java │ │ │ │ ├── GreetingController.java │ │ │ │ ├── WebSocketApplication.java │ │ │ │ └── WebSocketConfiguration.java │ │ └── resources │ │ │ ├── LargeJsonFile.json │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_06 │ │ └── functional │ │ └── password │ │ └── verification │ │ └── client │ │ └── PasswordVerificationServiceTest.java └── security-samples │ ├── build.gradle │ └── src │ └── main │ ├── java │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_06 │ │ └── security │ │ ├── DefaultProfileService.java │ │ ├── Profile.java │ │ ├── ProfileService.java │ │ ├── SecuredProfileController.java │ │ ├── SecurityConfiguration.java │ │ └── SecuritySampleApplication.java │ └── resources │ └── application.properties ├── chapter-07 ├── section-01-jdbc │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── rpis5 │ │ │ │ └── chapters │ │ │ │ └── chapter_07 │ │ │ │ └── jdbc │ │ │ │ ├── Book.java │ │ │ │ ├── BookJdbcRepository.java │ │ │ │ ├── BookJdbiDao.java │ │ │ │ ├── BookSpringDataJdbcRepository.java │ │ │ │ └── Chapter7JdbcApplication.java │ │ └── resources │ │ │ ├── application.yml │ │ │ ├── data.sql │ │ │ └── schema.sql │ │ └── test │ │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_07 │ │ └── jdbc │ │ └── Chapter7JdbcTests.java ├── section-02-jpa │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_07 │ │ │ └── jpa │ │ │ ├── Book.java │ │ │ ├── BookSpringDataJpaRepository.java │ │ │ └── Chapter7JpaApplication.java │ │ └── resources │ │ ├── application.yml │ │ ├── data.sql │ │ └── schema.sql ├── section-03-mongo │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_07 │ │ └── mongo_repo │ │ ├── Book.java │ │ ├── BookSpringDataMongoRepository.java │ │ └── Chapter7RxMongoApplication.java ├── section-04-rx-mongo │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_07 │ │ │ └── mongo_rx_tx │ │ │ ├── Book.java │ │ │ ├── BookSpringDataMongoRxRepository.java │ │ │ ├── Chapter7RxMongoApplication.java │ │ │ ├── RxBookPublishingYearUpdatedExample.java │ │ │ ├── RxMongoDriverQueryService.java │ │ │ └── RxMongoTemplateQueryService.java │ │ └── resources │ │ └── application.yml ├── section-05-rx-mongo-tx │ ├── build.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── rpis5 │ │ │ │ └── chapters │ │ │ │ └── chapter_07 │ │ │ │ └── mongo_rx_tx │ │ │ │ ├── Chapter7RxMongoTransactions.java │ │ │ │ └── wallet │ │ │ │ ├── BaseWalletService.java │ │ │ │ ├── NaiveWalletService.java │ │ │ │ ├── TransactionalWalletService.java │ │ │ │ ├── Wallet.java │ │ │ │ ├── WalletRepository.java │ │ │ │ └── WalletService.java │ │ └── resources │ │ │ ├── application.yml │ │ │ └── replica-set-config.js │ │ └── test │ │ ├── java │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_07 │ │ │ └── mongo_rx_tx │ │ │ └── wallet │ │ │ ├── BaseWalletServiceTest.java │ │ │ ├── NaiveWalletServiceTest.java │ │ │ └── TransactionalWalletServiceTest.java │ │ └── resources │ │ ├── README.md │ │ └── docker-compose.yml ├── section-06-r2dbc │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_07 │ │ │ └── r2dbs │ │ │ ├── Book.java │ │ │ ├── BookRepository.java │ │ │ ├── Chapter7R2dbcApplication.java │ │ │ ├── DatabaseLocation.java │ │ │ ├── InfrastructureConfiguration.java │ │ │ └── PostgresConfiguration.java │ │ └── resources │ │ └── application.yml ├── section-07-rx-webflux │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_07 │ │ │ └── webflux │ │ │ ├── Chapter7SpringDataWebFluxIntegrationApplication.java │ │ │ ├── ChatHandler.java │ │ │ ├── Message.java │ │ │ ├── MessageRepository.java │ │ │ └── TalkSimulator.java │ │ └── resources │ │ └── application.yml ├── section-08-rx-dbs │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_07 │ │ │ └── rx_dbs │ │ │ ├── Chapter7ReactiveConnectorsApplication.java │ │ │ ├── User.java │ │ │ ├── cassandra │ │ │ └── UserRepository.java │ │ │ ├── couchbase │ │ │ └── UserRepository.java │ │ │ ├── mongo │ │ │ └── UserRepository.java │ │ │ └── redis │ │ │ ├── UserSession.java │ │ │ └── UserSessionCache.java │ │ └── resources │ │ └── application.yaml ├── section-09-rx-sync │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_07 │ │ │ └── wrapped_sync │ │ │ ├── Book.java │ │ │ ├── BookJpaRepository.java │ │ │ ├── Chapter7JpaApplication.java │ │ │ ├── ReactiveCrudRepositoryAdapter.java │ │ │ ├── RxBookRepository.java │ │ │ └── RxPersistenceConfiguration.java │ │ └── resources │ │ ├── application.yml │ │ ├── data.sql │ │ └── schema.sql └── section-10-rxjava2-jdbc │ ├── build.gradle │ └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_07 │ │ │ └── rxjava2jdbc │ │ │ ├── Chapter7RxJava2JdbcApplication.java │ │ │ ├── book │ │ │ ├── Book.java │ │ │ ├── DatabaseConfiguration.java │ │ │ └── RxBookRepository.java │ │ │ └── wallet │ │ │ ├── Wallet.java │ │ │ ├── WalletData.java │ │ │ ├── WalletService.java │ │ │ └── WalletServiceImpl.java │ └── resources │ │ ├── application.yml │ │ ├── data.sql │ │ └── schema.sql │ └── test │ └── java │ └── org │ └── rpis5 │ └── chapters │ └── chapter_07 │ └── rxjava2jdbc │ └── wallet │ └── TransactionalWalletServiceTest.java ├── chapter-08 ├── cloud-stream │ ├── build.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ ├── controller │ │ │ ├── ChatController.java │ │ │ ├── InfoResource.java │ │ │ └── vm │ │ │ │ ├── MessageVM.java │ │ │ │ ├── UserVM.java │ │ │ │ └── UsersStatisticVM.java │ │ │ ├── domain │ │ │ ├── Issue.java │ │ │ ├── Mention.java │ │ │ ├── Message.java │ │ │ └── User.java │ │ │ ├── repository │ │ │ ├── MessageRepository.java │ │ │ ├── UserRepository.java │ │ │ └── impl │ │ │ │ └── DefaultUserRepository.java │ │ │ └── service │ │ │ ├── ChatService.java │ │ │ ├── StatisticService.java │ │ │ ├── gitter │ │ │ ├── GitterProperties.java │ │ │ ├── GitterUriBuilder.java │ │ │ └── dto │ │ │ │ ├── Issue.java │ │ │ │ ├── Mention.java │ │ │ │ ├── MessageResponse.java │ │ │ │ ├── Meta.java │ │ │ │ ├── Role.java │ │ │ │ ├── Url.java │ │ │ │ └── UserResponse.java │ │ │ └── impl │ │ │ ├── DefaultStatisticService.java │ │ │ ├── GitterService.java │ │ │ └── utils │ │ │ ├── MessageMapper.java │ │ │ └── UserMapper.java │ │ └── resources │ │ ├── META-INF │ │ └── additional-spring-configuration-metadata.json │ │ ├── application-dev.yaml │ │ ├── application.yaml │ │ └── templates │ │ └── chat.html └── dataflow │ └── mongodb-processor │ ├── build.gradle │ └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── rpis5 │ │ │ └── chapters │ │ │ └── chapter_08 │ │ │ └── dataflow │ │ │ └── mongodb │ │ │ └── processor │ │ │ ├── MongodbProcessorApplication.java │ │ │ ├── MongodbProcessorConfiguration.java │ │ │ └── MongodbProcessorProperties.java │ └── resources │ │ ├── META-INF │ │ ├── spring-configuration-metadata-whitelist.properties │ │ ├── spring-configuration-metadata.json │ │ ├── spring.provides │ │ └── thin.properties │ │ └── application.yaml │ └── test │ └── java │ └── org │ └── rpis5 │ └── chapters │ └── chapter_08 │ └── dataflow │ └── mongodb │ └── processor │ └── MongodbProcessorApplicationTests.java ├── chapter-09 ├── build.gradle └── src │ ├── main │ └── java │ │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_09 │ │ ├── DefaultPaymentService.java │ │ ├── DemoApplication.java │ │ ├── Payment.java │ │ ├── PaymentController.java │ │ ├── PaymentRepository.java │ │ ├── PaymentService.java │ │ ├── RootController.java │ │ └── SecurityConfiguration.java │ └── test │ ├── java │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_09 │ │ ├── MockClientResponse.java │ │ ├── PaymentControllerTests.java │ │ ├── TestSecurityConfiguration.java │ │ └── TestWebClientBuilderConfiguration.java │ └── resources │ └── application.yaml ├── chapter-10 ├── README.md ├── build.gradle ├── docker │ ├── docker-compose.yml │ ├── grafana │ │ ├── config.ini │ │ ├── dashboards │ │ │ └── reactive-application.json │ │ └── provisioning │ │ │ ├── dashboards │ │ │ └── all.yml │ │ │ └── datasources │ │ │ └── all.yml │ └── prometheus │ │ └── prometheus.yml └── src │ └── main │ ├── java │ └── org │ │ └── rpis5 │ │ └── chapters │ │ └── chapter_10 │ │ ├── Chapter10CloudReadyApplication.java │ │ ├── acturator │ │ ├── AppModeInfoProvider.java │ │ ├── SensorBatteryHealthIndicator.java │ │ └── ServerTimeEndpoint.java │ │ ├── controller │ │ ├── EventStreamController.java │ │ ├── ProxyController.java │ │ └── WebConfiguration.java │ │ ├── scheduler │ │ ├── MeteredScheduledThreadPoolExecutor.java │ │ ├── MeteredScheduledThreadPoolExecutorMinimal.java │ │ └── MeteredSchedulersFactory.java │ │ └── service │ │ ├── Temperature.java │ │ └── TemperatureSensor.java │ └── resources │ ├── application.yml │ └── static │ └── index.html ├── doc └── img │ ├── book-title.png │ ├── ch10-grafana.png │ ├── ch10-spring-boot-admin.png │ ├── ch10-web-page.png │ ├── ch10-zipkin.png │ ├── idea-annotation-processing.png │ └── idea-lombok-plugin.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | out 4 | build 5 | package-lock.json 6 | !./Content 7 | 8 | bin 9 | .classpath 10 | .project 11 | .settings 12 | .idea 13 | .vscode 14 | .gradle 15 | *.iml 16 | 17 | cloud-ready-reactive-app.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group = 'org.rpis5.chapters' 2 | version = '0.0.1-SNAPSHOT' 3 | 4 | apply plugin: 'java' 5 | 6 | sourceCompatibility = 1.8 7 | 8 | repositories { 9 | mavenCentral() 10 | maven { url "https://repo.spring.io/snapshot" } 11 | maven { url "https://repo.spring.io/milestone" } 12 | } -------------------------------------------------------------------------------- /chapter-01/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '1.5.16.RELEASE' 4 | } 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 10 | } 11 | } 12 | 13 | apply plugin: 'java' 14 | apply plugin: 'eclipse' 15 | apply plugin: 'org.springframework.boot' 16 | 17 | sourceCompatibility = 1.8 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | 24 | dependencies { 25 | compile('org.springframework.boot:spring-boot-starter-web') 26 | testCompile('org.springframework.boot:spring-boot-starter-test') 27 | } 28 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/ChapterFirstApplication.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ChapterFirstApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ChapterFirstApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/RootController.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Zoomdata, Inc. 2012-2018. All rights reserved. 3 | */ 4 | package org.rpis5.chapters.chapter_01; 5 | 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 | 10 | @RestController 11 | @RequestMapping("/") 12 | public class RootController { 13 | 14 | @GetMapping("") 15 | public String root() { 16 | return "Please go to http://localhost:8080/api/v1/resource/a or to http://localhost:8080/api/v2/resource/a"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/callbacks/AsyncShoppingCardService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.callbacks; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | import org.rpis5.chapters.chapter_01.commons.Output; 5 | 6 | import java.util.function.Consumer; 7 | 8 | public class AsyncShoppingCardService implements ShoppingCardService { 9 | 10 | @Override 11 | public void calculate(Input value, Consumer c) { 12 | // blocking operation is presented, better to provide answer asynchronously 13 | new Thread(() -> { 14 | try { 15 | Thread.sleep(1000); 16 | } catch (InterruptedException e) { 17 | e.printStackTrace(); 18 | } 19 | 20 | c.accept(new Output()); 21 | }).start(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/callbacks/OrdersService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.callbacks; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | 5 | public class OrdersService { 6 | private final ShoppingCardService shoppingCardService; 7 | 8 | public OrdersService(ShoppingCardService shoppingCardService) { 9 | this.shoppingCardService = shoppingCardService; 10 | } 11 | 12 | void process() { 13 | Input input = new Input(); 14 | shoppingCardService.calculate(input, output -> { 15 | System.out.println(shoppingCardService.getClass().getSimpleName() + " execution completed"); 16 | }); 17 | } 18 | 19 | public static void main(String[] args) throws InterruptedException { 20 | long start = System.currentTimeMillis(); 21 | 22 | OrdersService ordersServiceAsync = new OrdersService(new AsyncShoppingCardService()); 23 | OrdersService ordersServiceSync = new OrdersService(new SyncShoppingCardService()); 24 | 25 | ordersServiceAsync.process(); 26 | ordersServiceAsync.process(); 27 | ordersServiceSync.process(); 28 | 29 | System.out.println("Total elapsed time in millis is : " + (System.currentTimeMillis() - start)); 30 | 31 | Thread.sleep(1000); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/callbacks/ShoppingCardService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.callbacks; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | import org.rpis5.chapters.chapter_01.commons.Output; 5 | 6 | import java.util.function.Consumer; 7 | 8 | public interface ShoppingCardService { 9 | void calculate(Input value, Consumer c); 10 | } 11 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/callbacks/SyncShoppingCardService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.callbacks; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | import org.rpis5.chapters.chapter_01.commons.Output; 5 | 6 | import java.util.function.Consumer; 7 | 8 | public class SyncShoppingCardService implements ShoppingCardService { 9 | 10 | @Override 11 | public void calculate(Input value, Consumer c) { 12 | // No blocking operation, better to immediately provide answer 13 | c.accept(new Output()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/commons/ExamplesCollection.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.commons; 2 | 3 | public class ExamplesCollection { 4 | private String value; 5 | 6 | public String getValue() { 7 | return value; 8 | } 9 | 10 | public void setValue(String value) { 11 | this.value = value; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/commons/Input.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.commons; 2 | 3 | public class Input { 4 | } 5 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/commons/Output.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.commons; 2 | 3 | public class Output { 4 | } 5 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/communication/ServiceOne.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.communication; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.ExamplesCollection; 4 | 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | import org.springframework.web.client.RestTemplate; 9 | 10 | /** 11 | * Example of blocking communication 12 | */ 13 | @RestController 14 | @RequestMapping("api/v1/resource/a") 15 | public class ServiceOne { 16 | private static final String PORT = "8080"; 17 | 18 | @GetMapping 19 | public ExamplesCollection processRequest() { 20 | RestTemplate template = new RestTemplate(); 21 | ExamplesCollection result = template.getForObject( 22 | "http://localhost:" + PORT + "/api/v1/resource/b", 23 | ExamplesCollection.class 24 | ); 25 | 26 | processResultFurther(result); 27 | 28 | return result; 29 | } 30 | 31 | private void processResultFurther(ExamplesCollection result) { 32 | // Do some processing 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/communication/ServiceTwo.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.communication; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.ExamplesCollection; 4 | 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | @RequestMapping("api/v1/resource/b") 11 | public class ServiceTwo { 12 | 13 | @GetMapping 14 | public ExamplesCollection process() throws InterruptedException { 15 | Thread.sleep(1000); 16 | 17 | ExamplesCollection ec = new ExamplesCollection(); 18 | ec.setValue("test"); 19 | 20 | return ec; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/communication/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Example that shows cross service communication with expanding problems, related with blocking I/O. 3 | * This package contains examples of cross-services communication. To simplify the task 4 | * @see org.rpis5.chapters.chapter_01.communication.ServiceOne calls himself 5 | * @see org.rpis5.chapters.chapter_01.communication.ServiceTwo to imitate network bounds and awaiting 6 | */ 7 | package org.rpis5.chapters.chapter_01.communication; -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/completion_stage/CompletionStageShoppingCardService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.completion_stage; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | import org.rpis5.chapters.chapter_01.commons.Output; 5 | 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.CompletionStage; 8 | 9 | public class CompletionStageShoppingCardService implements ShoppingCardService { 10 | 11 | @Override 12 | public CompletionStage calculate(Input value) { 13 | 14 | return CompletableFuture.supplyAsync(() -> { 15 | try { 16 | Thread.sleep(1000); 17 | } catch (InterruptedException e) { 18 | e.printStackTrace(); 19 | } 20 | 21 | return new Output(); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/completion_stage/OrdersService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.completion_stage; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | 5 | public class OrdersService { 6 | private final ShoppingCardService shoppingCardService; 7 | 8 | public OrdersService(ShoppingCardService shoppingCardService) { 9 | this.shoppingCardService = shoppingCardService; 10 | } 11 | 12 | void process() { 13 | Input input = new Input(); 14 | 15 | shoppingCardService.calculate(input) 16 | .thenAccept(v -> System.out.println(shoppingCardService.getClass().getSimpleName() + " execution completed")); 17 | 18 | System.out.println(shoppingCardService.getClass().getSimpleName() + " calculate called"); 19 | } 20 | 21 | public static void main(String[] args) throws InterruptedException { 22 | long start = System.currentTimeMillis(); 23 | 24 | OrdersService ordersService1 = new OrdersService(new CompletionStageShoppingCardService()); 25 | 26 | ordersService1.process(); 27 | ordersService1.process(); 28 | 29 | System.out.println("Total elapsed time in millis is : " + (System.currentTimeMillis() - start)); 30 | 31 | Thread.sleep(1000); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/completion_stage/ShoppingCardService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.completion_stage; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | import org.rpis5.chapters.chapter_01.commons.Output; 5 | 6 | import java.util.concurrent.CompletionStage; 7 | 8 | public interface ShoppingCardService { 9 | CompletionStage calculate(Input value); 10 | } 11 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/futures/FutureShoppingCardService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.futures; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | import org.rpis5.chapters.chapter_01.commons.Output; 5 | 6 | import java.util.concurrent.Future; 7 | import java.util.concurrent.FutureTask; 8 | 9 | public class FutureShoppingCardService implements ShoppingCardService { 10 | 11 | @Override 12 | public Future calculate(Input value) { 13 | FutureTask future = new FutureTask<>(() -> { 14 | Thread.sleep(1000); 15 | return new Output(); 16 | }); 17 | 18 | new Thread(future).start(); 19 | 20 | return future; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/futures/OrdersService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.futures; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | import org.rpis5.chapters.chapter_01.commons.Output; 5 | 6 | import java.util.concurrent.ExecutionException; 7 | import java.util.concurrent.Future; 8 | 9 | public class OrdersService { 10 | private final ShoppingCardService shoppingCardService; 11 | 12 | public OrdersService(ShoppingCardService shoppingCardService) { 13 | this.shoppingCardService = shoppingCardService; 14 | } 15 | 16 | void process() { 17 | Input input = new Input(); 18 | Future result = shoppingCardService.calculate(input); 19 | 20 | System.out.println(shoppingCardService.getClass().getSimpleName() + " execution completed"); 21 | 22 | try { 23 | result.get(); 24 | } catch (InterruptedException | ExecutionException e) { 25 | e.printStackTrace(); 26 | } 27 | } 28 | 29 | public static void main(String[] args) { 30 | long start = System.currentTimeMillis(); 31 | 32 | OrdersService ordersService1 = new OrdersService(new FutureShoppingCardService()); 33 | 34 | ordersService1.process(); 35 | ordersService1.process(); 36 | 37 | System.out.println("Total elapsed time in millis is : " + (System.currentTimeMillis() - start)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/futures/ShoppingCardService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.futures; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | import org.rpis5.chapters.chapter_01.commons.Output; 5 | 6 | import java.util.concurrent.Future; 7 | 8 | public interface ShoppingCardService { 9 | Future calculate(Input value); 10 | } 11 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/imperative/BlockingShoppingCardService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.imperative; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | import org.rpis5.chapters.chapter_01.commons.Output; 5 | 6 | public class BlockingShoppingCardService implements ShoppingCardService { 7 | 8 | @Override 9 | public Output calculate(Input value) { 10 | try { 11 | Thread.sleep(1000); 12 | } catch (InterruptedException e) { 13 | e.printStackTrace(); 14 | } 15 | 16 | return new Output(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/imperative/OrdersService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.imperative; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | import org.rpis5.chapters.chapter_01.commons.Output; 5 | 6 | public class OrdersService { 7 | 8 | private final ShoppingCardService scService; 9 | 10 | public OrdersService(ShoppingCardService scService) { 11 | this.scService = scService; 12 | } 13 | 14 | void process() { 15 | Input input = new Input(); 16 | Output output = scService.calculate(input); 17 | 18 | System.out.println(scService.getClass().getSimpleName() + " execution completed"); 19 | } 20 | 21 | public static void main(String[] args) { 22 | long start = System.currentTimeMillis(); 23 | 24 | new OrdersService(new BlockingShoppingCardService()).process(); 25 | new OrdersService(new BlockingShoppingCardService()).process(); 26 | 27 | System.out.println("Total elapsed time in millis is : " + (System.currentTimeMillis() - start)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/imperative/ShoppingCardService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.imperative; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.Input; 4 | import org.rpis5.chapters.chapter_01.commons.Output; 5 | 6 | public interface ShoppingCardService { 7 | Output calculate(Input value); 8 | } 9 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/spring_futures/AsyncServiceOne.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.spring_futures; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.ExamplesCollection; 4 | 5 | import org.springframework.util.concurrent.FailureCallback; 6 | import org.springframework.util.concurrent.ListenableFuture; 7 | import org.springframework.util.concurrent.SuccessCallback; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import org.springframework.web.client.AsyncRestTemplate; 12 | 13 | import java.util.concurrent.Future; 14 | 15 | /** 16 | * Example of dirty async communication 17 | */ 18 | @RestController 19 | @RequestMapping("api/v2/resource/a") 20 | public class AsyncServiceOne { 21 | private static final String PORT = "8080"; 22 | 23 | @GetMapping 24 | public Future process() { 25 | AsyncRestTemplate template = new AsyncRestTemplate(); 26 | SuccessCallback onSuccess = r -> System.out.println("Success"); 27 | FailureCallback onFailure = e -> System.out.println("Failure"); 28 | ListenableFuture response = template.getForEntity( 29 | "http://localhost:" + PORT + "/api/v2/resource/b", 30 | ExamplesCollection.class 31 | ); 32 | 33 | response.addCallback(onSuccess, onFailure); 34 | 35 | return response; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/spring_futures/AsyncServiceTwo.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_01.spring_futures; 2 | 3 | import org.rpis5.chapters.chapter_01.commons.ExamplesCollection; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | @RequestMapping("api/v2/resource/b") 10 | public class AsyncServiceTwo { 11 | 12 | @GetMapping 13 | public ExamplesCollection process() throws InterruptedException { 14 | Thread.sleep(1000); 15 | 16 | ExamplesCollection ec = new ExamplesCollection(); 17 | ec.setValue("test"); 18 | 19 | return ec; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chapter-01/src/main/java/org/rpis5/chapters/chapter_01/spring_futures/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Example that shows cross service communication with expanding problems, related with async blocking I/O. 3 | * This package contains examples of cross-services communication. To simplify the task 4 | * @see org.rpis5.chapters.chapter_01.spring_futures.AsyncServiceOne calls himself 5 | * @see org.rpis5.chapters.chapter_01.spring_futures.AsyncServiceTwo to imitate network bounds and awaiting 6 | */ 7 | package org.rpis5.chapters.chapter_01.spring_futures; -------------------------------------------------------------------------------- /chapter-02/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '1.5.16.RELEASE' 4 | } 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 10 | } 11 | } 12 | 13 | apply plugin: 'java' 14 | apply plugin: 'eclipse' 15 | apply plugin: 'org.springframework.boot' 16 | 17 | sourceCompatibility = 1.8 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | 24 | dependencies { 25 | compile('org.springframework.boot:spring-boot-starter-web') 26 | compile('io.reactivex:rxjava:1.3.8') 27 | testCompile('org.springframework.boot:spring-boot-starter-test') 28 | } 29 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/observer/ConcreteObserverA.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.observer; 2 | 3 | public class ConcreteObserverA implements Observer { 4 | @Override 5 | public void observe(String event) { 6 | System.out.println("Observer A: " + event); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/observer/ConcreteObserverB.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.observer; 2 | 3 | public class ConcreteObserverB implements Observer { 4 | @Override 5 | public void observe(String event) { 6 | System.out.println("Observer B: " + event); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/observer/ConcreteSubject.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.observer; 2 | 3 | import java.util.Set; 4 | import java.util.concurrent.CopyOnWriteArraySet; 5 | 6 | public class ConcreteSubject implements Subject { 7 | private final Set> observers = 8 | new CopyOnWriteArraySet<>(); 9 | 10 | public void registerObserver(Observer observer) { 11 | observers.add(observer); 12 | } 13 | 14 | public void unregisterObserver(Observer observer) { 15 | observers.remove(observer); 16 | } 17 | 18 | public void notifyObservers(String event) { 19 | observers.forEach(observer -> observer.observe(event)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/observer/Iterator.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.observer; 2 | 3 | public interface Iterator { 4 | boolean hasNext(); 5 | T next(); 6 | } 7 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/observer/Observer.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.observer; 2 | 3 | public interface Observer { 4 | void observe(T event); 5 | } 6 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/observer/ParallelSubject.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.observer; 2 | 3 | import java.util.Set; 4 | import java.util.concurrent.CopyOnWriteArraySet; 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.Executors; 7 | 8 | public class ParallelSubject implements Subject { 9 | private final Set> observers = 10 | new CopyOnWriteArraySet<>(); 11 | 12 | public void registerObserver(Observer observer) { 13 | observers.add(observer); 14 | } 15 | 16 | public void unregisterObserver(Observer observer) { 17 | observers.remove(observer); 18 | } 19 | 20 | private final ExecutorService executorService = Executors.newCachedThreadPool(); 21 | 22 | public void notifyObservers(String event) { 23 | observers.forEach(observer -> 24 | executorService.submit( 25 | () -> observer.observe(event) 26 | ) 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/observer/RxObserver.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.observer; 2 | 3 | public interface RxObserver { 4 | void onNext(T next); 5 | void onComplete(); 6 | void onError(Exception e); 7 | } 8 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/observer/Subject.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.observer; 2 | 3 | public interface Subject { 4 | void registerObserver(Observer observer); 5 | 6 | void unregisterObserver(Observer observer); 7 | 8 | void notifyObservers(T event); 9 | } 10 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/pub_sub_app/Application.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.pub_sub_app; 2 | 3 | import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; 4 | import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.ComponentScan; 8 | import org.springframework.scheduling.annotation.AsyncConfigurer; 9 | import org.springframework.scheduling.annotation.EnableAsync; 10 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 11 | 12 | import java.util.concurrent.Executor; 13 | 14 | @EnableAsync 15 | @SpringBootApplication 16 | public class Application implements AsyncConfigurer { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(Application.class, args); 20 | } 21 | 22 | @Override 23 | public Executor getAsyncExecutor() { 24 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 25 | executor.setThreadNamePrefix("sse-"); 26 | executor.setCorePoolSize(2); 27 | executor.setMaxPoolSize(100); 28 | executor.setQueueCapacity(5); 29 | executor.initialize(); 30 | return executor; 31 | } 32 | 33 | @Override 34 | public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { 35 | return new SimpleAsyncUncaughtExceptionHandler(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/pub_sub_app/Temperature.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.pub_sub_app; 2 | 3 | /** 4 | * Temperature in Celsius. 5 | */ 6 | public final class Temperature { 7 | private final double value; 8 | 9 | public Temperature(double value) { 10 | this.value = value; 11 | } 12 | 13 | public double getValue() { 14 | return value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/pub_sub_app/TemperatureSensor.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.pub_sub_app; 2 | 3 | import org.springframework.context.ApplicationEventPublisher; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.annotation.PostConstruct; 7 | import java.util.Random; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.ScheduledExecutorService; 10 | 11 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 12 | import static java.util.concurrent.TimeUnit.SECONDS; 13 | 14 | @Component 15 | public class TemperatureSensor { 16 | private final ApplicationEventPublisher publisher; 17 | private final Random rnd = new Random(); 18 | private final ScheduledExecutorService executor = 19 | Executors.newSingleThreadScheduledExecutor(); 20 | 21 | public TemperatureSensor(ApplicationEventPublisher publisher) { 22 | this.publisher = publisher; 23 | } 24 | 25 | @PostConstruct 26 | public void startProcessing() { 27 | this.executor.schedule(this::probe, 1, SECONDS); 28 | } 29 | 30 | private void probe() { 31 | double temperature = 16 + rnd.nextGaussian() * 10; 32 | publisher.publishEvent(new Temperature(temperature)); 33 | 34 | // schedule the next read after some random delay (0-5 seconds) 35 | executor.schedule(this::probe, rnd.nextInt(5000), MILLISECONDS); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/rx_app/Application.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.rx_app; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.ComponentScan; 6 | 7 | @SpringBootApplication 8 | public class Application { 9 | 10 | public static void main(String[] args) { 11 | SpringApplication.run(Application.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/rx_app/Temperature.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.rx_app; 2 | 3 | /** 4 | * Temperature in Celsius. 5 | */ 6 | public final class Temperature { 7 | private final double value; 8 | 9 | public Temperature(double value) { 10 | this.value = value; 11 | } 12 | 13 | public double getValue() { 14 | return value; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/rx_app/TemperatureSensor.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.rx_app; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.stereotype.Component; 6 | import rx.Observable; 7 | 8 | import java.util.Random; 9 | 10 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 11 | 12 | @Component 13 | public class TemperatureSensor { 14 | private static final Logger log = LoggerFactory.getLogger(TemperatureSensor.class); 15 | private final Random rnd = new Random(); 16 | 17 | private final Observable dataStream = 18 | Observable 19 | .range(0, Integer.MAX_VALUE) 20 | .concatMap(ignore -> Observable 21 | .just(1) 22 | .delay(rnd.nextInt(5000), MILLISECONDS) 23 | .map(ignore2 -> this.probe())) 24 | .publish() 25 | .refCount(); 26 | 27 | public Observable temperatureStream() { 28 | return dataStream; 29 | } 30 | 31 | private Temperature probe() { 32 | double actualTemp = 16 + rnd.nextGaussian() * 10; 33 | log.info("Asking sensor, sensor value: {}", actualTemp); 34 | return new Temperature(actualTemp); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/seach_engine/FutureSearchEngine.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.seach_engine; 2 | 3 | import java.net.URL; 4 | import java.util.List; 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | @SuppressWarnings("unused") 8 | public interface FutureSearchEngine { 9 | CompletableFuture> search(String query, int limit); 10 | } 11 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/seach_engine/IterableSearchEngine.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.seach_engine; 2 | 3 | import java.net.URL; 4 | 5 | @SuppressWarnings("unused") 6 | public interface IterableSearchEngine { 7 | Iterable search(String query, int limit); 8 | } 9 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/seach_engine/RxSearchEngine.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.seach_engine; 2 | 3 | import rx.Observable; 4 | 5 | import java.net.URL; 6 | 7 | @SuppressWarnings("unused") 8 | public interface RxSearchEngine { 9 | Observable search(String query); 10 | } 11 | -------------------------------------------------------------------------------- /chapter-02/src/main/java/org/rpis5/chapters/chapter_02/seach_engine/SearchEngine.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_02.seach_engine; 2 | 3 | import java.net.URL; 4 | import java.util.List; 5 | 6 | @SuppressWarnings("unused") 7 | public interface SearchEngine { 8 | List search(String query, int limit); 9 | } 10 | -------------------------------------------------------------------------------- /chapter-02/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # In order to omit INFO messages with stack-trace about unsuccessful SSE send operation 2 | logging.level.org.apache.coyote.http11.Http11Processor=WARN 3 | -------------------------------------------------------------------------------- /chapter-02/src/main/resources/static/index-min.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 19 | -------------------------------------------------------------------------------- /chapter-03/async.vs.reactive/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'eclipse' 3 | 4 | group = 'com.example' 5 | version = '0.0.1-SNAPSHOT' 6 | sourceCompatibility = 1.8 7 | 8 | repositories { 9 | mavenCentral() 10 | maven { url 'https://repo.spring.io/milestone' } 11 | maven { url 'https://repo.spring.io/snapshot' } 12 | } 13 | 14 | 15 | dependencies { 16 | compile('org.reactivestreams:reactive-streams:1.0.2') 17 | 18 | 19 | testCompile('org.reactivestreams:reactive-streams-tck:1.0.2') 20 | testCompile('org.hamcrest:java-hamcrest:2.0.0.0') 21 | testCompile('io.projectreactor:reactor-core:3.2.0.RELEASE') 22 | testCompile('io.projectreactor:reactor-test:3.2.0.RELEASE') 23 | testCompile('junit:junit:4.12') 24 | 25 | } 26 | -------------------------------------------------------------------------------- /chapter-03/async.vs.reactive/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | data: 3 | mongodb: 4 | port: 27017 5 | database: news 6 | 7 | server: 8 | port: 8080 -------------------------------------------------------------------------------- /chapter-03/async.vs.reactive/src/test/java/org/rpis5/chapters/chapter_03/async_vs_reactive/CompletableFutureAsPublisherTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.async_vs_reactive; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | 5 | import org.reactivestreams.Publisher; 6 | import org.reactivestreams.tck.PublisherVerification; 7 | import org.reactivestreams.tck.TestEnvironment; 8 | 9 | public class CompletableFutureAsPublisherTest extends PublisherVerification { 10 | 11 | public CompletableFutureAsPublisherTest() { 12 | super(new TestEnvironment()); 13 | } 14 | 15 | @Override 16 | public Publisher createPublisher(long elements) { 17 | return new CompletableFutureAsPublisher<>(CompletableFuture.supplyAsync(() -> 1)); 18 | } 19 | 20 | @Override 21 | public Publisher createFailedPublisher() { 22 | return null; 23 | } 24 | 25 | @Override 26 | public long maxElementsFromPublisher() { 27 | return 1; 28 | } 29 | } -------------------------------------------------------------------------------- /chapter-03/conversion_problem/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '1.5.16.RELEASE' 4 | } 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 10 | } 11 | } 12 | 13 | apply plugin: 'java' 14 | apply plugin: 'eclipse' 15 | apply plugin: 'org.springframework.boot' 16 | 17 | group = 'com.example' 18 | version = '0.0.1-SNAPSHOT' 19 | sourceCompatibility = 1.8 20 | 21 | repositories { 22 | mavenCentral() 23 | } 24 | 25 | 26 | dependencies { 27 | compile('org.springframework.boot:spring-boot-starter-web') 28 | testCompile('org.springframework.boot:spring-boot-starter-test') 29 | } 30 | -------------------------------------------------------------------------------- /chapter-03/conversion_problem/src/main/java/org/rpis5/chapters/chapter_03/conversion_problem/AsyncAdapters.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.conversion_problem; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.concurrent.CompletionStage; 5 | 6 | import org.springframework.util.concurrent.ListenableFuture; 7 | import org.springframework.util.concurrent.SettableListenableFuture; 8 | 9 | public final class AsyncAdapters { 10 | 11 | public static CompletionStage toCompletion(ListenableFuture future) { 12 | 13 | CompletableFuture completableFuture = new CompletableFuture<>(); 14 | 15 | future.addCallback(completableFuture::complete, 16 | completableFuture::completeExceptionally); 17 | 18 | return completableFuture; 19 | } 20 | 21 | public static ListenableFuture toListenable(CompletionStage stage) { 22 | SettableListenableFuture future = new SettableListenableFuture<>(); 23 | 24 | stage.whenComplete((v, t) -> { 25 | if (t == null) { 26 | future.set(v); 27 | } 28 | else { 29 | future.setException(t); 30 | } 31 | }); 32 | 33 | return future; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter-03/conversion_problem/src/main/java/org/rpis5/chapters/chapter_03/conversion_problem/AsyncDatabaseClient.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.conversion_problem; 2 | 3 | import java.util.concurrent.CompletionStage; 4 | 5 | public interface AsyncDatabaseClient { 6 | 7 | CompletionStage store(CompletionStage stage); 8 | } 9 | -------------------------------------------------------------------------------- /chapter-03/conversion_problem/src/main/java/org/rpis5/chapters/chapter_03/conversion_problem/ConversionProblemApp.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.conversion_problem; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ConversionProblemApp { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ConversionProblemApp.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-03/conversion_problem/src/main/java/org/rpis5/chapters/chapter_03/conversion_problem/FakeAsyncDatabaseClient.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.conversion_problem; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.concurrent.CompletionStage; 5 | 6 | public class FakeAsyncDatabaseClient implements AsyncDatabaseClient { 7 | 8 | @Override 9 | public CompletionStage store(CompletionStage stage) { 10 | return stage.thenCompose(e -> CompletableFuture.supplyAsync(() -> e)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-03/conversion_problem/src/main/java/org/rpis5/chapters/chapter_03/conversion_problem/TestController.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.conversion_problem; 2 | 3 | import org.springframework.http.MediaType; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class TestController { 9 | 10 | @RequestMapping(value = "/hello", produces = MediaType.TEXT_PLAIN_VALUE) 11 | public String hello() { 12 | return "Hello World"; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /chapter-03/conversion_problem/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | data: 3 | mongodb: 4 | port: 27017 5 | database: news 6 | 7 | server: 8 | port: 8080 -------------------------------------------------------------------------------- /chapter-03/conversion_problem/src/test/java/org/rpis5/chapters/chapter_03/conversion_problem/MyControllerTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.conversion_problem; 2 | 3 | import org.hamcrest.MatcherAssert; 4 | import org.hamcrest.Matchers; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | 13 | @SpringBootTest( 14 | webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, 15 | classes = {ConversionProblemApp.class, MyController.class, TestController.class} 16 | ) 17 | @RunWith(SpringRunner.class) 18 | public class MyControllerTest { 19 | 20 | @Test 21 | public void testMyControllerResponse() { 22 | RestTemplate template = new RestTemplate(); 23 | 24 | String object = template.getForObject("http://localhost:8080", String.class); 25 | 26 | MatcherAssert.assertThat(object, Matchers.equalTo("Hello World")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chapter-03/jdk9/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '1.5.16.RELEASE' 4 | } 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 10 | } 11 | } 12 | 13 | apply plugin: 'java' 14 | apply plugin: 'eclipse' 15 | apply plugin: 'org.springframework.boot' 16 | 17 | group = 'com.example' 18 | version = '0.0.1-SNAPSHOT' 19 | sourceCompatibility = 9 20 | 21 | repositories { 22 | mavenCentral() 23 | } 24 | 25 | 26 | dependencies { 27 | compile(project(':chapter-03/news-service')) 28 | compile('org.reactivestreams:reactive-streams-flow-adapters:1.0.2') 29 | compile('io.reactivex.rxjava2:rxjava:2.2.2') 30 | } 31 | -------------------------------------------------------------------------------- /chapter-03/news-service/src/main/java/org/rpis5/chapters/chapter_03/news_service/DBPublisher.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.news_service; 2 | 3 | import org.rpis5.chapters.chapter_03.news_service.dto.News; 4 | import com.mongodb.client.model.Filters; 5 | import com.mongodb.client.model.Sorts; 6 | import com.mongodb.reactivestreams.client.FindPublisher; 7 | import com.mongodb.reactivestreams.client.MongoCollection; 8 | import org.reactivestreams.Publisher; 9 | import org.reactivestreams.Subscriber; 10 | 11 | import java.util.Date; 12 | 13 | public class DBPublisher implements Publisher { 14 | private final MongoCollection collection; 15 | private final String category; 16 | 17 | public DBPublisher(MongoCollection collection, String category) { 18 | this.collection = collection; 19 | this.category = category; 20 | } 21 | 22 | @Override 23 | public void subscribe(Subscriber s) { 24 | FindPublisher findPublisher = collection.find(News.class); 25 | findPublisher.sort(Sorts.descending("publishedOn")) 26 | .filter(Filters.and( 27 | Filters.eq("category", category), 28 | Filters.gt("publishedOn", today()) 29 | )) 30 | .subscribe(s); 31 | } 32 | 33 | private Date today() { 34 | Date date = new Date(); 35 | return new Date(date.getYear(), date.getMonth(), date.getDate()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter-03/news-service/src/main/java/org/rpis5/chapters/chapter_03/news_service/NamedSubscriber.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.news_service; 2 | 3 | public interface NamedSubscriber { 4 | String getName(); 5 | } 6 | -------------------------------------------------------------------------------- /chapter-03/news-service/src/main/java/org/rpis5/chapters/chapter_03/news_service/NewsServicePublisher.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.news_service; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.function.Consumer; 5 | 6 | import org.rpis5.chapters.chapter_03.news_service.dto.News; 7 | import org.rpis5.chapters.chapter_03.news_service.dto.NewsLetter; 8 | import com.mongodb.reactivestreams.client.MongoClient; 9 | import org.reactivestreams.Publisher; 10 | import org.reactivestreams.Subscriber; 11 | 12 | public class NewsServicePublisher implements Publisher { 13 | 14 | final SmartMulticastProcessor processor; 15 | 16 | public NewsServicePublisher(MongoClient client, String categoryOfInterests) { 17 | ScheduledPublisher scheduler = new ScheduledPublisher<>( 18 | () -> new NewsPreparationOperator( 19 | new DBPublisher( 20 | client.getDatabase("news") 21 | .getCollection("news", News.class), 22 | categoryOfInterests 23 | ), 24 | "Some Digest" 25 | ), 26 | 1, TimeUnit.DAYS 27 | ); 28 | 29 | SmartMulticastProcessor processor = new SmartMulticastProcessor(); 30 | scheduler.subscribe(processor); 31 | 32 | this.processor = processor; 33 | } 34 | 35 | public NewsServicePublisher(Consumer setup) { 36 | this.processor = new SmartMulticastProcessor(); 37 | 38 | setup.accept(processor); 39 | } 40 | 41 | @Override 42 | public void subscribe(Subscriber s) { 43 | processor.subscribe(s); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter-03/news-service/src/main/java/org/rpis5/chapters/chapter_03/news_service/ResubscribableErrorLettter.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.news_service; 2 | 3 | import org.rpis5.chapters.chapter_03.news_service.dto.NewsLetter; 4 | import org.reactivestreams.Subscriber; 5 | 6 | public interface ResubscribableErrorLettter { 7 | 8 | void resubscribe(Subscriber subscriber); 9 | } 10 | -------------------------------------------------------------------------------- /chapter-03/news-service/src/main/java/org/rpis5/chapters/chapter_03/news_service/SubscriptionUtils.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.news_service; 2 | 3 | import java.util.concurrent.atomic.AtomicLongFieldUpdater; 4 | 5 | public final class SubscriptionUtils { 6 | private SubscriptionUtils() { 7 | } 8 | 9 | 10 | public static long addCap(long current, long requested) { 11 | long cap = current + requested; 12 | 13 | if (cap < 0L) { 14 | cap = Long.MAX_VALUE; 15 | } 16 | 17 | return cap; 18 | } 19 | 20 | 21 | @SuppressWarnings("unchecked") 22 | public static long request(long n, Object instance, AtomicLongFieldUpdater updater) { 23 | for (;;) { 24 | long currentDemand = updater.get(instance); 25 | 26 | if (currentDemand == Long.MAX_VALUE) { 27 | return Long.MAX_VALUE; 28 | } 29 | 30 | long adjustedDemand = addCap(currentDemand, n); 31 | 32 | if (updater.compareAndSet(instance, currentDemand, adjustedDemand)) { 33 | return currentDemand; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter-03/news-service/src/main/java/org/rpis5/chapters/chapter_03/news_service/dto/News.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.news_service.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.mongodb.annotations.Immutable; 5 | import lombok.*; 6 | import org.bson.types.ObjectId; 7 | import org.springframework.data.annotation.Id; 8 | import org.springframework.data.mongodb.core.mapping.Document; 9 | 10 | import java.util.Date; 11 | 12 | @Data 13 | @Document 14 | @Immutable 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Builder 18 | public class News { 19 | 20 | @Id 21 | @JsonIgnore 22 | private ObjectId id; 23 | 24 | private @NonNull String title; 25 | private @NonNull String content; 26 | private @NonNull Date publishedOn; 27 | private @NonNull String category; 28 | private @NonNull String author; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /chapter-03/news-service/src/main/java/org/rpis5/chapters/chapter_03/news_service/dto/NewsLetter.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.news_service.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NonNull; 7 | import lombok.experimental.Wither; 8 | 9 | import java.util.Collection; 10 | 11 | @Data(staticConstructor = "of") 12 | @Builder(builderClassName = "NewsLetterTemplate", builderMethodName = "template") 13 | @AllArgsConstructor 14 | @Wither 15 | public class NewsLetter { 16 | 17 | private final @NonNull String title; 18 | private final String recipient; 19 | private final @NonNull Collection digest; 20 | } -------------------------------------------------------------------------------- /chapter-03/news-service/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | data: 3 | mongodb: 4 | port: 27017 5 | database: news 6 | 7 | server: 8 | port: 8080 -------------------------------------------------------------------------------- /chapter-03/news-service/src/test/java/org/rpis5/chapters/chapter_03/news_service/NewsHarness.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.news_service; 2 | 3 | import org.rpis5.chapters.chapter_03.news_service.dto.News; 4 | 5 | import java.util.Date; 6 | import java.util.Random; 7 | 8 | public interface NewsHarness { 9 | Random RANDOM = new Random(); 10 | 11 | static News generate() { 12 | return News.builder() 13 | .author(String.valueOf(RANDOM.nextGaussian())) 14 | .category("tech") 15 | .publishedOn(new Date()) 16 | .content(String.valueOf(RANDOM.nextGaussian())) 17 | .title(String.valueOf(RANDOM.nextGaussian())) 18 | .build(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter-03/news-service/src/test/java/org/rpis5/chapters/chapter_03/news_service/NewsPreparationOperatorTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.news_service; 2 | 3 | import org.rpis5.chapters.chapter_03.news_service.dto.News; 4 | import org.rpis5.chapters.chapter_03.news_service.dto.NewsLetter; 5 | import io.reactivex.Flowable; 6 | import org.reactivestreams.Publisher; 7 | import org.reactivestreams.tck.PublisherVerification; 8 | import org.reactivestreams.tck.TestEnvironment; 9 | 10 | public class NewsPreparationOperatorTest extends PublisherVerification { 11 | 12 | public NewsPreparationOperatorTest() { 13 | super(new TestEnvironment()); 14 | } 15 | 16 | @Override 17 | public Publisher createPublisher(long elements) { 18 | return new NewsPreparationOperator( 19 | Flowable.just(new News()), 20 | "test" 21 | ); 22 | } 23 | 24 | @Override 25 | public Publisher createFailedPublisher() { 26 | return new NewsPreparationOperator( 27 | Flowable.error(new RuntimeException()), 28 | "test" 29 | ); 30 | } 31 | 32 | @Override 33 | public long maxElementsFromPublisher() { 34 | return 1; 35 | } 36 | } 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /chapter-03/news-service/src/test/java/org/rpis5/chapters/chapter_03/news_service/NewsServiceSubscriberTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.news_service; 2 | 3 | import org.rpis5.chapters.chapter_03.news_service.dto.NewsLetter; 4 | import org.reactivestreams.Subscriber; 5 | import org.reactivestreams.tck.SubscriberBlackboxVerification; 6 | import org.reactivestreams.tck.TestEnvironment; 7 | 8 | public class NewsServiceSubscriberTest 9 | extends SubscriberBlackboxVerification { 10 | 11 | public NewsServiceSubscriberTest() { 12 | super(new TestEnvironment()); 13 | } 14 | 15 | @Override 16 | public Subscriber createSubscriber() { 17 | return new NewsServiceSubscriber(Integer.MAX_VALUE); 18 | } 19 | 20 | @Override 21 | public NewsLetter createElement(int element) { 22 | return new StubNewsLetter(element); 23 | } 24 | 25 | @Override 26 | public void triggerRequest(Subscriber s) { 27 | ((NewsServiceSubscriber) s).eventuallyReadDigest(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chapter-03/news-service/src/test/java/org/rpis5/chapters/chapter_03/news_service/StubNewsLetter.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.news_service; 2 | 3 | import java.util.Collections; 4 | 5 | import org.rpis5.chapters.chapter_03.news_service.dto.NewsLetter; 6 | 7 | public class StubNewsLetter extends NewsLetter { 8 | 9 | StubNewsLetter(int element) { 10 | super(String.valueOf(element), null, Collections.emptyList()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'eclipse' 3 | 4 | group = 'com.example' 5 | version = '0.0.1-SNAPSHOT' 6 | sourceCompatibility = 9 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | 13 | dependencies { 14 | compile('io.reactivex:rxjava:1.3.3') 15 | compile('io.reactivex:rxnetty-http:0.5.3-rc.1') 16 | compile('io.reactivex.rxjava2:rxjava:2.2.2') 17 | 18 | 19 | compileOnly('org.projectlombok:lombok:1.18.2') 20 | testCompile('org.reactivestreams:reactive-streams-tck:1.0.2') 21 | testCompile('org.hamcrest:java-hamcrest:2.0.0.0') 22 | testCompile('junit:junit:4.12') 23 | 24 | } 25 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/batched_pull_model/AsyncDatabaseClient.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.batched_pull_model; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.CompletionStage; 5 | 6 | public interface AsyncDatabaseClient { 7 | 8 | CompletionStage> getNextBatchAfterId(String id, int count); 9 | } 10 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/batched_pull_model/DelayedFakeAsyncDatabaseClient.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.batched_pull_model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.CompletableFuture; 6 | import java.util.concurrent.CompletionStage; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import io.reactivex.Flowable; 10 | 11 | public class DelayedFakeAsyncDatabaseClient implements AsyncDatabaseClient { 12 | 13 | @Override 14 | public CompletionStage> getNextBatchAfterId(String id, int count) { 15 | CompletableFuture> future = new CompletableFuture<>(); 16 | 17 | Flowable.range(Integer.parseInt(id) + 1, count) 18 | .map(i -> new Item("" + i)) 19 | .collectInto(new ArrayList(), ArrayList::add) 20 | .delay(1000, TimeUnit.MILLISECONDS) 21 | .subscribe(future::complete); 22 | 23 | return future; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/batched_pull_model/Item.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.batched_pull_model; 2 | 3 | import lombok.Value; 4 | 5 | @Value 6 | public class Item { 7 | 8 | final String id; 9 | } 10 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/batched_pull_model/Puller.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.batched_pull_model; 2 | 3 | import java.util.Queue; 4 | import java.util.concurrent.ArrayBlockingQueue; 5 | import java.util.concurrent.BlockingQueue; 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.CompletionStage; 8 | 9 | public class Puller { 10 | 11 | final AsyncDatabaseClient dbClient = new DelayedFakeAsyncDatabaseClient(); 12 | 13 | public CompletionStage> list(int count) { 14 | BlockingQueue storage = new ArrayBlockingQueue<>(count); 15 | CompletableFuture> result = new CompletableFuture<>(); 16 | 17 | pull("1", storage, result, count); 18 | 19 | return result; 20 | } 21 | 22 | void pull(String elementId, 23 | Queue queue, 24 | CompletableFuture resultFuture, 25 | int count) { 26 | 27 | dbClient.getNextBatchAfterId(elementId, count) 28 | .thenAccept(items -> { 29 | for (Item item : items) { 30 | if (isValid(item)) { 31 | queue.offer(item); 32 | 33 | if (queue.size() == count) { 34 | resultFuture.complete(queue); 35 | return; 36 | } 37 | } 38 | } 39 | 40 | pull(items.get(items.size() - 1) 41 | .getId(), queue, resultFuture, count); 42 | }); 43 | } 44 | 45 | boolean isValid(Item item) { 46 | return Integer.parseInt(item.getId()) % 2 == 0; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/pure_pull_model/AsyncDatabaseClient.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.pure_pull_model; 2 | 3 | import java.util.concurrent.CompletionStage; 4 | 5 | public interface AsyncDatabaseClient { 6 | 7 | CompletionStage getNextAfterId(String id); 8 | } 9 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/pure_pull_model/DelayedFakeAsyncDatabaseClient.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.pure_pull_model; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.concurrent.CompletionStage; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import io.reactivex.Flowable; 8 | 9 | public class DelayedFakeAsyncDatabaseClient implements AsyncDatabaseClient { 10 | 11 | @Override 12 | public CompletionStage getNextAfterId(String id) { 13 | CompletableFuture future = new CompletableFuture<>(); 14 | 15 | Flowable.just(new Item("" + (Integer.parseInt(id) + 1))) 16 | .delay(500, TimeUnit.MILLISECONDS) 17 | .subscribe(future::complete); 18 | 19 | return future; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/pure_pull_model/Item.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.pure_pull_model; 2 | 3 | import lombok.Value; 4 | 5 | @Value 6 | public class Item { 7 | 8 | final String id; 9 | } 10 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/pure_pull_model/Puller.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.pure_pull_model; 2 | 3 | import java.util.Queue; 4 | import java.util.concurrent.ArrayBlockingQueue; 5 | import java.util.concurrent.BlockingQueue; 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.CompletionStage; 8 | 9 | public class Puller { 10 | 11 | final AsyncDatabaseClient dbClient = new DelayedFakeAsyncDatabaseClient(); 12 | 13 | public CompletionStage> list(int count) { 14 | BlockingQueue storage = new ArrayBlockingQueue<>(count); 15 | CompletableFuture> result = new CompletableFuture<>(); 16 | 17 | pull("1", storage, result, count); 18 | 19 | return result; 20 | } 21 | 22 | void pull(String elementId, 23 | Queue queue, 24 | CompletableFuture resultFuture, 25 | int count) { 26 | dbClient.getNextAfterId(elementId) 27 | .thenAccept(item -> { 28 | if (isValid(item)) { 29 | queue.offer(item); 30 | 31 | if (queue.size() == count) { 32 | resultFuture.complete(queue); 33 | return; 34 | } 35 | } 36 | 37 | pull(item.getId(), queue, resultFuture, count); 38 | }); 39 | } 40 | 41 | boolean isValid(Item item) { 42 | return Integer.parseInt(item.getId()) % 2 == 0; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/push_model/AsyncDatabaseClient.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.push_model; 2 | 3 | import rx.Observable; 4 | 5 | public interface AsyncDatabaseClient { 6 | 7 | Observable getStreamOfItems(); 8 | } 9 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/push_model/DelayedFakeAsyncDatabaseClient.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.push_model; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import rx.Observable; 6 | 7 | public class DelayedFakeAsyncDatabaseClient implements AsyncDatabaseClient { 8 | 9 | @Override 10 | public Observable getStreamOfItems() { 11 | return Observable.range(1, Integer.MAX_VALUE) 12 | .map(i -> new Item("" + i)) 13 | .delay(50, TimeUnit.MILLISECONDS) 14 | .delaySubscription(100, TimeUnit.MILLISECONDS); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/push_model/Item.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.push_model; 2 | 3 | import lombok.Value; 4 | 5 | @Value 6 | public class Item { 7 | 8 | final String id; 9 | } 10 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/push_model/Puller.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.push_model; 2 | 3 | import rx.Observable; 4 | 5 | public class Puller { 6 | 7 | final AsyncDatabaseClient dbClient = new DelayedFakeAsyncDatabaseClient(); 8 | 9 | public Observable list(int count) { 10 | return dbClient.getStreamOfItems() 11 | .filter(this::isValid) 12 | .take(count); 13 | } 14 | 15 | boolean isValid(Item item) { 16 | return Integer.parseInt(item.getId()) % 2 == 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/push_pull_model/AsyncDatabaseClient.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.push_pull_model; 2 | 3 | import org.reactivestreams.Publisher; 4 | 5 | public interface AsyncDatabaseClient { 6 | 7 | Publisher getStreamOfItems(); 8 | } 9 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/push_pull_model/DelayedFakeAsyncDatabaseClient.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.push_pull_model; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import io.reactivex.Flowable; 6 | import io.reactivex.schedulers.Schedulers; 7 | import org.reactivestreams.Publisher; 8 | 9 | public class DelayedFakeAsyncDatabaseClient implements AsyncDatabaseClient { 10 | 11 | @Override 12 | public Publisher getStreamOfItems() { 13 | return Flowable.range(1, Integer.MAX_VALUE) 14 | .map(i -> new Item("" + i)) 15 | .delay(50, TimeUnit.MILLISECONDS) 16 | .hide() 17 | .subscribeOn(Schedulers.io()) 18 | .delaySubscription(100, TimeUnit.MILLISECONDS); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/push_pull_model/Item.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.push_pull_model; 2 | 3 | import lombok.Value; 4 | 5 | @Value 6 | public class Item { 7 | 8 | final String id; 9 | } 10 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/push_pull_model/Puller.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.push_pull_model; 2 | 3 | import org.reactivestreams.Publisher; 4 | 5 | public class Puller { 6 | 7 | final AsyncDatabaseClient dbClient = new DelayedFakeAsyncDatabaseClient(); 8 | 9 | public Publisher list(int count) { 10 | Publisher source = dbClient.getStreamOfItems(); 11 | TakeFilterOperator takeFilter = 12 | new TakeFilterOperator<>(source, count, this::isValid); 13 | 14 | return takeFilter; 15 | } 16 | 17 | boolean isValid(Item item) { 18 | return Integer.parseInt(item.getId()) % 2 == 0; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/main/java/org/rpis5/chapters/chapter_03/push_pull_model/SubscriptionUtils.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.push_pull_model; 2 | 3 | import java.util.concurrent.atomic.AtomicLongFieldUpdater; 4 | 5 | public final class SubscriptionUtils { 6 | private SubscriptionUtils() { 7 | } 8 | 9 | 10 | public static long addCap(long current, long requested) { 11 | long cap = current + requested; 12 | 13 | if (cap < 0L) { 14 | cap = Long.MAX_VALUE; 15 | } 16 | 17 | return cap; 18 | } 19 | 20 | 21 | @SuppressWarnings("unchecked") 22 | public static long request(long n, Object instance, AtomicLongFieldUpdater updater) { 23 | for (;;) { 24 | long currentDemand = updater.get(instance); 25 | 26 | if (currentDemand == Long.MAX_VALUE) { 27 | return Long.MAX_VALUE; 28 | } 29 | 30 | long adjustedDemand = addCap(currentDemand, n); 31 | 32 | if (updater.compareAndSet(instance, currentDemand, adjustedDemand)) { 33 | return currentDemand; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/test/java/org/rpis5/chapters/chapter_03/batched_pull_model/PullerTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.batched_pull_model; 2 | 3 | import java.util.Queue; 4 | import java.util.concurrent.CompletionStage; 5 | import java.util.concurrent.CountDownLatch; 6 | 7 | import org.hamcrest.MatcherAssert; 8 | import org.hamcrest.Matchers; 9 | import org.junit.Test; 10 | 11 | public class PullerTest { 12 | 13 | @Test 14 | public void pullTest() throws InterruptedException { 15 | CountDownLatch l = new CountDownLatch(1); 16 | Puller puller = new Puller(); 17 | 18 | CompletionStage> list = puller.list(10); 19 | 20 | list.thenAccept(q -> { 21 | MatcherAssert.assertThat(q, Matchers.allOf( 22 | Matchers.hasSize(10), 23 | Matchers.contains( 24 | Matchers.hasProperty("id", Matchers.equalTo("2")), 25 | Matchers.hasProperty("id", Matchers.equalTo("4")), 26 | Matchers.hasProperty("id", Matchers.equalTo("6")), 27 | Matchers.hasProperty("id", Matchers.equalTo("8")), 28 | Matchers.hasProperty("id", Matchers.equalTo("10")), 29 | Matchers.hasProperty("id", Matchers.equalTo("12")), 30 | Matchers.hasProperty("id", Matchers.equalTo("14")), 31 | Matchers.hasProperty("id", Matchers.equalTo("16")), 32 | Matchers.hasProperty("id", Matchers.equalTo("18")), 33 | Matchers.hasProperty("id", Matchers.equalTo("20")) 34 | ) 35 | )); 36 | l.countDown(); 37 | }) 38 | .exceptionally(t -> { 39 | l.countDown(); 40 | throw new RuntimeException(t); 41 | }); 42 | 43 | l.await(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/test/java/org/rpis5/chapters/chapter_03/pure_pull_model/PullerTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.pure_pull_model; 2 | 3 | import java.util.Queue; 4 | import java.util.concurrent.CompletionStage; 5 | import java.util.concurrent.CountDownLatch; 6 | 7 | import org.hamcrest.MatcherAssert; 8 | import org.hamcrest.Matchers; 9 | import org.junit.Test; 10 | 11 | public class PullerTest { 12 | 13 | @Test 14 | public void pullTest() throws InterruptedException { 15 | CountDownLatch l = new CountDownLatch(1); 16 | Puller puller = new Puller(); 17 | 18 | CompletionStage> list = puller.list(10); 19 | 20 | list.thenAccept(q -> { 21 | MatcherAssert.assertThat(q, Matchers.allOf( 22 | Matchers.hasSize(10), 23 | Matchers.contains( 24 | Matchers.hasProperty("id", Matchers.equalTo("2")), 25 | Matchers.hasProperty("id", Matchers.equalTo("4")), 26 | Matchers.hasProperty("id", Matchers.equalTo("6")), 27 | Matchers.hasProperty("id", Matchers.equalTo("8")), 28 | Matchers.hasProperty("id", Matchers.equalTo("10")), 29 | Matchers.hasProperty("id", Matchers.equalTo("12")), 30 | Matchers.hasProperty("id", Matchers.equalTo("14")), 31 | Matchers.hasProperty("id", Matchers.equalTo("16")), 32 | Matchers.hasProperty("id", Matchers.equalTo("18")), 33 | Matchers.hasProperty("id", Matchers.equalTo("20")) 34 | ) 35 | )); 36 | l.countDown(); 37 | }) 38 | .exceptionally(t -> { 39 | l.countDown(); 40 | throw new RuntimeException(t); 41 | }); 42 | 43 | l.await(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/test/java/org/rpis5/chapters/chapter_03/push_model/PullerTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.push_model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.concurrent.CountDownLatch; 5 | 6 | import org.hamcrest.MatcherAssert; 7 | import org.hamcrest.Matchers; 8 | import org.junit.Test; 9 | 10 | public class PullerTest { 11 | 12 | @Test 13 | public void pullTest() throws InterruptedException { 14 | CountDownLatch l = new CountDownLatch(1); 15 | Puller puller = new Puller(); 16 | 17 | puller.list(10) 18 | .collect(ArrayList::new, ArrayList::add) 19 | .subscribe(al -> { 20 | MatcherAssert.assertThat(al, Matchers.allOf( 21 | Matchers.hasSize(10), 22 | Matchers.contains( 23 | Matchers.hasProperty("id", Matchers.equalTo("2")), 24 | Matchers.hasProperty("id", Matchers.equalTo("4")), 25 | Matchers.hasProperty("id", Matchers.equalTo("6")), 26 | Matchers.hasProperty("id", Matchers.equalTo("8")), 27 | Matchers.hasProperty("id", Matchers.equalTo("10")), 28 | Matchers.hasProperty("id", Matchers.equalTo("12")), 29 | Matchers.hasProperty("id", Matchers.equalTo("14")), 30 | Matchers.hasProperty("id", Matchers.equalTo("16")), 31 | Matchers.hasProperty("id", Matchers.equalTo("18")), 32 | Matchers.hasProperty("id", Matchers.equalTo("20")) 33 | ) 34 | )); 35 | l.countDown(); 36 | }); 37 | 38 | l.await(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chapter-03/push.vs.pull/src/test/java/org/rpis5/chapters/chapter_03/push_pull_model/TCKPullerTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.push_pull_model; 2 | 3 | import org.reactivestreams.Publisher; 4 | import org.reactivestreams.tck.PublisherVerification; 5 | import org.reactivestreams.tck.TestEnvironment; 6 | 7 | public class TCKPullerTest extends PublisherVerification { 8 | 9 | public TCKPullerTest() { 10 | super(new TestEnvironment(1000, 1000)); 11 | } 12 | 13 | @Override 14 | public Publisher createPublisher(long elements) { 15 | return new Puller().list(elements > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)elements); 16 | } 17 | 18 | @Override 19 | public Publisher createFailedPublisher() { 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chapter-03/rxjava-reactivestreams-ratpack/src/main/java/org/rpis5/chapters/chapter_03/rxjava_reactivestreams/FileService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.rxjava_reactivestreams; 2 | 3 | import org.reactivestreams.Publisher; 4 | 5 | public interface FileService { 6 | 7 | void writeTo(String file, Publisher content); 8 | } 9 | -------------------------------------------------------------------------------- /chapter-03/rxjava-reactivestreams-ratpack/src/main/java/org/rpis5/chapters/chapter_03/rxjava_reactivestreams/LogService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.rxjava_reactivestreams; 2 | 3 | import org.reactivestreams.Publisher; 4 | 5 | public interface LogService { 6 | Publisher stream(); 7 | } 8 | -------------------------------------------------------------------------------- /chapter-03/rxjava-reactivestreams-ratpack/src/main/java/org/rpis5/chapters/chapter_03/rxjava_reactivestreams/RxFileService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.rxjava_reactivestreams; 2 | 3 | import org.reactivestreams.Publisher; 4 | import rx.RxReactiveStreams; 5 | 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class RxFileService implements FileService { 10 | 11 | @Override 12 | public void writeTo( 13 | String file, 14 | Publisher content 15 | ) { 16 | 17 | AsyncFileSubscriber rxSubscriber = 18 | new AsyncFileSubscriber(file); 19 | 20 | content.subscribe(RxReactiveStreams.toSubscriber(rxSubscriber)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chapter-03/rxjava-reactivestreams-ratpack/src/main/java/org/rpis5/chapters/chapter_03/rxjava_reactivestreams/RxLogService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.rxjava_reactivestreams; 2 | 3 | import java.net.InetSocketAddress; 4 | 5 | import io.netty.buffer.ByteBuf; 6 | import io.reactivex.netty.protocol.http.client.HttpClient; 7 | import io.reactivex.netty.protocol.http.client.HttpClientResponse; 8 | import io.reactivex.netty.protocol.http.sse.ServerSentEvent; 9 | import org.reactivestreams.Publisher; 10 | import rx.Observable; 11 | import rx.RxReactiveStreams; 12 | 13 | import org.springframework.stereotype.Service; 14 | 15 | @Service 16 | public class RxLogService implements LogService { 17 | 18 | final HttpClient rxClient = 19 | HttpClient.newClient(new InetSocketAddress(8080)); 20 | 21 | @Override 22 | public Publisher stream() { 23 | Observable rxStream = rxClient.createGet("/logs") 24 | .flatMap(HttpClientResponse::getContentAsServerSentEvents) 25 | .map(ServerSentEvent::contentAsString); 26 | 27 | return RxReactiveStreams.toPublisher(rxStream); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chapter-03/rxjava-reactivestreams-ratpack/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | data: 3 | mongodb: 4 | port: 27017 5 | database: news 6 | 7 | server: 8 | port: 8080 -------------------------------------------------------------------------------- /chapter-03/vert.x/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'eclipse' 3 | 4 | group = 'com.example' 5 | version = '0.0.1-SNAPSHOT' 6 | sourceCompatibility = 1.8 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | 13 | dependencies { 14 | compile('io.vertx:vertx-web:3.5.3') 15 | compile('io.vertx:vertx-reactive-streams:3.5.3') 16 | compile('io.reactivex.rxjava2:rxjava:2.2.2') 17 | } 18 | -------------------------------------------------------------------------------- /chapter-03/vert.x/src/main/java/org/rpis5/chapters/chapter_03/vert/x/LogService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.vert.x; 2 | 3 | import org.reactivestreams.Publisher; 4 | 5 | public interface LogService { 6 | Publisher stream(); 7 | } 8 | -------------------------------------------------------------------------------- /chapter-03/vert.x/src/main/java/org/rpis5/chapters/chapter_03/vert/x/MockLogService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_03.vert.x; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import io.reactivex.Flowable; 6 | import org.reactivestreams.Publisher; 7 | 8 | public class MockLogService implements LogService { 9 | 10 | @Override 11 | public Publisher stream() { 12 | return Flowable.interval(300, TimeUnit.MILLISECONDS) 13 | .map(i -> "[" + System.nanoTime() + "] [LogServiceApplication] " + "[Thread " + Thread.currentThread() + "] Some loge here " + i + "\n"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter-04/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.0.M4' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url 'https://repo.spring.io/milestone' } 25 | maven { url 'https://repo.spring.io/snapshot' } 26 | } 27 | 28 | ext { 29 | reactorVersion = '3.2.0.RELEASE' 30 | } 31 | 32 | dependencies { 33 | compile('org.springframework.boot:spring-boot-starter') 34 | 35 | compile("io.projectreactor:reactor-core:${reactorVersion}") 36 | compile("io.projectreactor:reactor-test:${reactorVersion}") 37 | 38 | compile('org.projectlombok:lombok') 39 | 40 | testCompile('org.springframework.boot:spring-boot-starter-test') 41 | } 42 | -------------------------------------------------------------------------------- /chapter-04/src/main/java/org/rpis5/chapters/chapter_04/Dummy.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_04; 2 | 3 | public class Dummy { 4 | } 5 | -------------------------------------------------------------------------------- /chapter-04/src/test/java/org/rpis5/chapters/chapter_04/CpuLoadTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_04; 2 | 3 | import com.sun.management.OperatingSystemMXBean; 4 | import org.junit.Test; 5 | import reactor.core.publisher.Flux; 6 | 7 | import java.lang.management.ManagementFactory; 8 | import java.time.Duration; 9 | 10 | import static java.lang.String.format; 11 | import static java.time.Instant.now; 12 | 13 | 14 | public class CpuLoadTest { 15 | 16 | @Test 17 | public void loadCpuData() throws InterruptedException { 18 | 19 | OperatingSystemMXBean osMXBean = 20 | (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); 21 | 22 | Flux loadStream = Flux.interval(Duration.ofMillis(100)) 23 | .map(ignore -> osMXBean.getSystemCpuLoad()); 24 | 25 | System.out.println("Application pid: " + applicationPid()); 26 | loadStream 27 | .filter(load -> !load.isNaN()) 28 | .subscribe(load -> 29 | System.out.println(format("[%s] System CPU load: %2.2f %%", now(), load * 100.0))); 30 | 31 | Thread.sleep(10_000); 32 | } 33 | 34 | private int applicationPid() { 35 | String appName = ManagementFactory.getRuntimeMXBean().getName(); 36 | String pidInString = appName.split("@")[0]; 37 | return Integer.parseInt(pidInString); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chapter-04/src/test/java/org/rpis5/chapters/chapter_04/ReactiveContextTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_04; 2 | 3 | import org.junit.Test; 4 | import reactor.core.publisher.Mono; 5 | import reactor.util.context.Context; 6 | 7 | public class ReactiveContextTest { 8 | 9 | @Test 10 | public void showcaseContext() { 11 | printCurrentContext("top") 12 | .subscriberContext(Context.of("top", "context")) 13 | .flatMap(__ -> printCurrentContext("middle")) 14 | .subscriberContext(Context.of("middle", "context")) 15 | .flatMap(__ -> printCurrentContext("bottom")) 16 | .subscriberContext(Context.of("bottom", "context")) 17 | .flatMap(__ -> printCurrentContext("initial")) 18 | .block(); 19 | } 20 | 21 | void print(String id, Context context) { 22 | System.out.println(id + " {"); 23 | System.out.print(" "); 24 | System.out.println(context); 25 | System.out.println("}"); 26 | System.out.println(); 27 | } 28 | 29 | Mono printCurrentContext(String id) { 30 | return Mono 31 | .subscriberContext() 32 | .doOnNext(context -> print(id, context)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /chapter-04/src/test/java/org/rpis5/chapters/chapter_04/ThreadLocalProblemShowcaseTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Zoomdata, Inc. 2012-2018. All rights reserved. 3 | */ 4 | package org.rpis5.chapters.chapter_04; 5 | 6 | import org.junit.Test; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.scheduler.Schedulers; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.Random; 13 | 14 | public class ThreadLocalProblemShowcaseTest { 15 | 16 | @Test(expected = NullPointerException.class) 17 | public void shouldFailDueToDifferentThread() { 18 | ThreadLocal> threadLocal = new ThreadLocal<>(); 19 | threadLocal.set(new HashMap<>()); 20 | 21 | Flux.range(0, 10) 22 | .doOnNext(k -> threadLocal.get().put(k, new Random(k).nextGaussian())) 23 | .publishOn(Schedulers.parallel()) 24 | .map(k -> threadLocal.get().get(k)) 25 | .blockLast(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chapter-05/src/main/java/org/rpis5/chapters/chapter_05/core/MaybeReactiveAdapter.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_05.core; 2 | 3 | import io.reactivex.Flowable; 4 | import io.reactivex.Maybe; 5 | 6 | import org.springframework.core.ReactiveAdapter; 7 | import org.springframework.core.ReactiveAdapterRegistry; 8 | import org.springframework.core.ReactiveTypeDescriptor; 9 | 10 | public class MaybeReactiveAdapter extends ReactiveAdapter { 11 | 12 | static { 13 | ReactiveAdapterRegistry 14 | .getSharedInstance() 15 | .registerReactiveType( 16 | ReactiveTypeDescriptor.singleOptionalValue(Maybe.class, Maybe::empty), 17 | rawMaybe -> ((Maybe)rawMaybe).toFlowable(), 18 | publisher -> Flowable.fromPublisher(publisher).singleElement() 19 | ); 20 | } 21 | 22 | public MaybeReactiveAdapter() { 23 | super( 24 | ReactiveTypeDescriptor.singleOptionalValue(Maybe.class, Maybe::empty), 25 | rawMaybe -> ((Maybe)rawMaybe).toFlowable(), 26 | publisher -> Flowable.fromPublisher(publisher).singleElement() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chapter-05/src/main/java/org/rpis5/chapters/chapter_05/core/ReactiveFileReader.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_05.core; 2 | 3 | import reactor.core.publisher.Flux; 4 | 5 | import org.springframework.core.io.DefaultResourceLoader; 6 | import org.springframework.core.io.buffer.DataBuffer; 7 | import org.springframework.core.io.buffer.DataBufferUtils; 8 | import org.springframework.core.io.buffer.DefaultDataBufferFactory; 9 | 10 | public class ReactiveFileReader { 11 | 12 | public Flux backpressuredShakespeare() { 13 | return DataBufferUtils 14 | .read( 15 | new DefaultResourceLoader().getResource("hamlet.txt"), 16 | new DefaultDataBufferFactory(), 17 | 1024 18 | ) 19 | .log(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /chapter-05/src/main/java/org/rpis5/chapters/chapter_05/reactive_app/SensorReadingRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_05.reactive_app; 2 | 3 | import org.bson.types.ObjectId; 4 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 5 | import org.springframework.data.mongodb.repository.Tailable; 6 | import reactor.core.publisher.Flux; 7 | 8 | public interface SensorReadingRepository 9 | extends ReactiveMongoRepository { 10 | 11 | @Tailable 12 | Flux findBy(); 13 | } 14 | -------------------------------------------------------------------------------- /chapter-05/src/main/java/org/rpis5/chapters/chapter_05/reactive_app/SensorsReadings.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_05.reactive_app; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.bson.types.ObjectId; 8 | import org.springframework.data.annotation.Id; 9 | import org.springframework.data.mongodb.core.mapping.Document; 10 | 11 | import java.time.LocalDateTime; 12 | 13 | @Document(collection = SensorsReadings.COLLECTION_NAME) 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Data 17 | public class SensorsReadings { 18 | public static final String COLLECTION_NAME = "iot-readings"; 19 | 20 | @JsonIgnore 21 | @Id private ObjectId id; 22 | 23 | private LocalDateTime readingTime; 24 | 25 | private Double temperature; 26 | private Double humidity; 27 | private Double luminosity; 28 | } 29 | -------------------------------------------------------------------------------- /chapter-05/src/main/resources/META-INF/spring-configuration-metadata-whitelist.properties: -------------------------------------------------------------------------------- 1 | configuration-properties.classes=org.rpis5.chapters.chapter_08.dataflow.mongodb.processor.MongodbProcessorProperties, \ 2 | org.springframework.boot.autoconfigure.mongo.MongoProperties 3 | 4 | -------------------------------------------------------------------------------- /chapter-05/src/main/resources/META-INF/spring.provides: -------------------------------------------------------------------------------- 1 | provides: spring-cloud-starter-stream-processor-mongodb 2 | -------------------------------------------------------------------------------- /chapter-05/src/main/resources/META-INF/thin.properties: -------------------------------------------------------------------------------- 1 | boms.spring-cloud-dependencies: org.springframework.cloud:spring-cloud-dependencies:Finchley.RELEASE 2 | dependencies.spring-integration-mongodb: org.springframework.integration:spring-integration-mongodb 3 | dependencies.spring-boot-starter-data-mongodb-reactive: org.springframework.boot:spring-boot-starter-data-mongodb-reactive 4 | dependencies.spring-cloud-stream-rabbit: org.springframework.cloud:spring-cloud-starter-stream-rabbit 5 | dependencies.spring-cloud-stream-reactive: org.springframework.cloud:spring-cloud-stream-reactive -------------------------------------------------------------------------------- /chapter-05/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | application.name: ${vcap.application.name:mongodb-sink} 3 | autoconfigure.exclude: org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration 4 | mongodb: 5 | embedded: 6 | version: 3.4.0 7 | 8 | info.app: 9 | name: mongodb-sink-rabbit 10 | description: Spring Cloud Stream Mongodb Processor Rabbit Binder Application 11 | version: 1.0.0.BUILD-SNAPSHOT 12 | 13 | management.endpoints.web.exposure.include: health,info,bindings 14 | 15 | -------------------------------------------------------------------------------- /chapter-05/src/test/java/org/rpis5/chapters/chapter_05/core/MaybeReactiveAdapterTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_05.core; 2 | 3 | import io.reactivex.Maybe; 4 | import org.assertj.core.api.Assertions; 5 | import org.junit.Test; 6 | import org.reactivestreams.Publisher; 7 | import reactor.core.publisher.Mono; 8 | 9 | import org.springframework.core.ReactiveAdapter; 10 | 11 | public class MaybeReactiveAdapterTest { 12 | 13 | final ReactiveAdapter maybeAdapter = new MaybeReactiveAdapter(); 14 | 15 | @Test 16 | public void convertFromMaybeToPublisherTest() { 17 | Assertions.assertThat(maybeAdapter.toPublisher(Maybe.just(1))) 18 | .isInstanceOf(Publisher.class); 19 | } 20 | 21 | @Test 22 | public void convertFromPublisherToMaybeTest() { 23 | Assertions.assertThat(maybeAdapter.fromPublisher(Mono.just(1))) 24 | .isInstanceOf(Maybe.class); 25 | } 26 | } -------------------------------------------------------------------------------- /chapter-05/src/test/java/org/rpis5/chapters/chapter_05/core/ReactiveFileReaderTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_05.core; 2 | 3 | import java.time.Duration; 4 | 5 | import org.junit.Test; 6 | import reactor.test.StepVerifier; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | public class ReactiveFileReaderTest { 11 | 12 | final ReactiveFileReader reader = new ReactiveFileReader(); 13 | 14 | @Test 15 | public void readShakespeareWithBackpressureTest() { 16 | StepVerifier.create(reader.backpressuredShakespeare(), 1) 17 | .assertNext(db -> assertThat(db.capacity()).isEqualTo(1024)) 18 | .expectNoEvent(Duration.ofMillis(2000)) 19 | .thenRequest(1) 20 | .assertNext(db -> assertThat(db.capacity()).isEqualTo(1024)) 21 | .thenCancel() 22 | .verify(); 23 | } 24 | } -------------------------------------------------------------------------------- /chapter-06/functional-spring-boot/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.0.5.RELEASE' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url 'https://repo.spring.io/milestone' } 25 | maven { url 'https://repo.spring.io/snapshot' } 26 | } 27 | 28 | 29 | dependencies { 30 | compile('org.springframework.boot:spring-boot-autoconfigure') 31 | compile('org.springframework.boot:spring-boot-starter-webflux') 32 | compile('org.springframework.boot:spring-boot-starter-actuator') 33 | compile('org.springframework.security:spring-security-core') 34 | 35 | 36 | compileOnly('org.projectlombok:lombok:1.18.2') 37 | 38 | 39 | testCompile('io.projectreactor:reactor-test') 40 | testCompile('org.springframework.boot:spring-boot-starter-test') 41 | } 42 | -------------------------------------------------------------------------------- /chapter-06/functional-spring-boot/src/main/java/org/rpis5/chapters/chapter_06/functional/springboot/InMemoryOrderRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.functional.springboot; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | import reactor.core.publisher.Mono; 7 | 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class InMemoryOrderRepository implements OrderRepository { 12 | 13 | final Map ordersMap; 14 | 15 | public InMemoryOrderRepository() { 16 | ordersMap = new ConcurrentHashMap<>(); 17 | } 18 | 19 | @Override 20 | public Mono findById(String id) { 21 | return Mono.justOrEmpty(ordersMap.get(id)); 22 | } 23 | 24 | @Override 25 | public Mono save(Order order) { 26 | ordersMap.put(order.getId(), order); 27 | 28 | return Mono.just(order); 29 | } 30 | 31 | @Override 32 | public Mono deleteById(String id) { 33 | return null; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter-06/functional-spring-boot/src/main/java/org/rpis5/chapters/chapter_06/functional/springboot/Order.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.functional.springboot; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class Order { 11 | 12 | private String id; 13 | } 14 | -------------------------------------------------------------------------------- /chapter-06/functional-spring-boot/src/main/java/org/rpis5/chapters/chapter_06/functional/springboot/OrderHandler.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.functional.springboot; 2 | 3 | import java.net.URI; 4 | 5 | import reactor.core.publisher.Mono; 6 | 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.reactive.function.BodyInserters; 9 | import org.springframework.web.reactive.function.server.ServerRequest; 10 | import org.springframework.web.reactive.function.server.ServerResponse; 11 | 12 | @Service 13 | public class OrderHandler { 14 | 15 | final OrderRepository orderRepository; 16 | 17 | public OrderHandler(OrderRepository repository) { 18 | orderRepository = repository; 19 | } 20 | 21 | public Mono create(ServerRequest request) { 22 | return request 23 | .bodyToMono(Order.class) 24 | .flatMap(orderRepository::save) 25 | .flatMap(o -> 26 | ServerResponse.created(URI.create("/orders/" + o.getId())) 27 | .build() 28 | ); 29 | } 30 | 31 | public Mono get(ServerRequest request) { 32 | return orderRepository 33 | .findById(request.pathVariable("id")) 34 | .flatMap(order -> 35 | ServerResponse 36 | .ok() 37 | .syncBody(order) 38 | ) 39 | .switchIfEmpty(ServerResponse.notFound().build()); 40 | } 41 | 42 | public Mono list(ServerRequest request) { 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter-06/functional-spring-boot/src/main/java/org/rpis5/chapters/chapter_06/functional/springboot/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.functional.springboot; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | 6 | public interface OrderRepository { 7 | 8 | Mono findById(String id); 9 | 10 | Mono save(Order order); 11 | 12 | Mono deleteById(String id); 13 | } 14 | -------------------------------------------------------------------------------- /chapter-06/functional-spring-boot/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/chapter-06/functional-spring-boot/src/main/resources/application.properties -------------------------------------------------------------------------------- /chapter-06/samples/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.0.M4' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url 'https://repo.spring.io/milestone' } 25 | maven { url 'https://repo.spring.io/snapshot' } 26 | } 27 | 28 | ext { 29 | reactorExtraVersion = '3.2.0.RELEASE' 30 | } 31 | 32 | dependencies { 33 | compile('org.springframework.boot:spring-boot-autoconfigure') 34 | compile('org.springframework.boot:spring-boot-starter-webflux') 35 | compile('org.springframework.boot:spring-boot-starter-actuator') 36 | compile('org.springframework.security:spring-security-core') 37 | 38 | compile("io.projectreactor.addons:reactor-extra:${reactorExtraVersion}") 39 | 40 | compileOnly('org.projectlombok:lombok') 41 | 42 | 43 | testCompile('io.projectreactor:reactor-test') 44 | testCompile('org.springframework.boot:spring-boot-starter-test') 45 | } 46 | -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/functional/password/verification/client/PasswordDTO.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.functional.password.verification.client; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | public class PasswordDTO { 7 | private String raw; 8 | private String secured; 9 | 10 | 11 | @JsonCreator 12 | public PasswordDTO(@JsonProperty("raw") String raw, 13 | @JsonProperty("secured") String secured) { 14 | this.raw = raw; 15 | this.secured = secured; 16 | } 17 | 18 | public String getRaw() { 19 | return raw; 20 | } 21 | 22 | public String getSecured() { 23 | return secured; 24 | } 25 | 26 | public void setRaw(String raw) { 27 | this.raw = raw; 28 | } 29 | 30 | public void setSecured(String secured) { 31 | this.secured = secured; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/functional/password/verification/client/PasswordVerificationService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.functional.password.verification.client; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public interface PasswordVerificationService { 6 | 7 | Mono check(String raw, String encoded); 8 | } 9 | -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/json/Message.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.json; 2 | 3 | public class Message { 4 | 5 | private String message; 6 | 7 | public String getMessage() { 8 | return message; 9 | } 10 | 11 | public void setMessage(String message) { 12 | this.message = message; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return String.format("Message{%s}[message=%s]", hashCode(), message); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/json/ReactiveJsonParser.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.json; 2 | 3 | import java.nio.channels.AsynchronousFileChannel; 4 | import java.nio.file.Paths; 5 | import java.nio.file.StandardOpenOption; 6 | import java.time.Duration; 7 | 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | 10 | import org.springframework.core.ResolvableType; 11 | import org.springframework.core.io.buffer.DataBufferUtils; 12 | import org.springframework.core.io.buffer.DefaultDataBufferFactory; 13 | import org.springframework.http.codec.json.Jackson2JsonDecoder; 14 | 15 | public class ReactiveJsonParser { 16 | 17 | 18 | public static void main(String[] args) { 19 | Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(); 20 | 21 | decoder 22 | .decode(DataBufferUtils.readAsynchronousFileChannel( 23 | () -> AsynchronousFileChannel.open(Paths.get(ClassLoader.getSystemResource("LargeJsonFile.json").getPath()), StandardOpenOption.READ), 24 | new DefaultDataBufferFactory(), 25 | 10 26 | ).delayElements(Duration.ofMillis(100)), 27 | ResolvableType.forClass(Message.class), 28 | null, null 29 | ) 30 | .blockLast(); 31 | } 32 | } -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/sse/ServerSentController.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.sse; 2 | 3 | import java.util.Map; 4 | 5 | import reactor.core.publisher.Flux; 6 | 7 | import org.springframework.http.codec.ServerSentEvent; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | public class ServerSentController { 13 | 14 | private Map stringStocksServiceMap; 15 | 16 | @GetMapping("/sse/stocks") 17 | public Flux> streamStocks() { 18 | return Flux 19 | .fromIterable(stringStocksServiceMap.values()) 20 | .flatMap(StocksService::stream) 21 | .>map(item -> 22 | ServerSentEvent 23 | .builder(item) 24 | .event("StockItem") 25 | .id(item.getId()) 26 | .build() 27 | ) 28 | .startWith( 29 | ServerSentEvent 30 | .builder() 31 | .event("Stocks") 32 | .data(stringStocksServiceMap.keySet()) 33 | .build() 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/sse/StockItem.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.sse; 2 | 3 | public class StockItem { 4 | 5 | private String id; 6 | private String type; 7 | 8 | public String getId() { 9 | return id; 10 | } 11 | 12 | public String getType() { 13 | return type; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/sse/StocksService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.sse; 2 | 3 | import reactor.core.publisher.Flux; 4 | 5 | public interface StocksService { 6 | 7 | Flux stream(); 8 | } -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/webclient/TestWebClient.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.webclient; 2 | 3 | import org.springframework.security.core.userdetails.User; 4 | import org.springframework.web.reactive.function.client.WebClient; 5 | 6 | public class TestWebClient { 7 | 8 | public static void main(String[] args) throws InterruptedException { 9 | WebClient.create("http://localhost:8080/api") // (1) 10 | .get() // (2) 11 | .uri("/users/{id}", 10) 12 | .retrieve() 13 | .bodyToMono(User.class) 14 | .map(User::getUsername); 15 | 16 | Thread.sleep(1000); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/websocket/EchoWebSocketHandler.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.websocket; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | import org.springframework.web.reactive.socket.WebSocketHandler; 6 | import org.springframework.web.reactive.socket.WebSocketMessage; 7 | import org.springframework.web.reactive.socket.WebSocketSession; 8 | 9 | public class EchoWebSocketHandler implements WebSocketHandler { // (1) 10 | 11 | @Override // (2) 12 | public Mono handle(WebSocketSession session) { // 13 | return session // (3) 14 | .receive() // (4) 15 | .map(WebSocketMessage::getPayloadAsText) // (5) 16 | .map(tm -> "Echo: " + tm) // (6) 17 | .map(session::textMessage) // (7) 18 | .as(session::send); // (8) 19 | } // 20 | } 21 | -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/websocket/GreetingController.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.websocket; 2 | 3 | import org.springframework.stereotype.Controller; 4 | 5 | @Controller 6 | public class GreetingController { 7 | 8 | // @MessageMapping("/hello") 9 | // @SendTo("/topic/greetings") 10 | // public Greeting greeting(HelloMessage message) { 11 | // return new Greeting("Hello, " + message.getName() + "!"); 12 | // } 13 | 14 | } 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/websocket/WebSocketApplication.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.websocket; 2 | 3 | import java.net.URI; 4 | import java.time.Duration; 5 | 6 | import reactor.core.publisher.Flux; 7 | 8 | import org.springframework.boot.CommandLineRunner; 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.boot.autoconfigure.SpringBootApplication; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; 13 | 14 | @SpringBootApplication 15 | public class WebSocketApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(WebSocketApplication.class, args); 19 | } 20 | 21 | @Bean 22 | public CommandLineRunner commandLineRunner() { 23 | return (args) -> { 24 | ReactorNettyWebSocketClient client = new ReactorNettyWebSocketClient(); 25 | 26 | client.execute( 27 | URI.create("http://localhost:8080/ws/echo"), 28 | session -> Flux 29 | .interval(Duration.ofMillis(100)) 30 | .map(String::valueOf) 31 | .map(session::textMessage) 32 | .as(session::send) 33 | ) 34 | .subscribe(); 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter-06/samples/src/main/java/org/rpis5/chapters/chapter_06/websocket/WebSocketConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.websocket; 2 | 3 | import java.util.Collections; 4 | 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.reactive.HandlerAdapter; 8 | import org.springframework.web.reactive.HandlerMapping; 9 | import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; 10 | import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter; 11 | 12 | @Configuration 13 | public class WebSocketConfiguration { 14 | 15 | @Bean 16 | public HandlerMapping handlerMapping() { 17 | SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); 18 | mapping.setUrlMap(Collections.singletonMap("/ws/echo", new EchoWebSocketHandler())); 19 | mapping.setOrder(-1); 20 | return mapping; 21 | } 22 | 23 | @Bean 24 | public HandlerAdapter handlerAdapter() { 25 | return new WebSocketHandlerAdapter(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chapter-06/samples/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/chapter-06/samples/src/main/resources/application.properties -------------------------------------------------------------------------------- /chapter-06/samples/src/test/java/org/rpis5/chapters/chapter_06/functional/password/verification/client/PasswordVerificationServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.functional.password.verification.client; 2 | 3 | import java.time.Duration; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.rpis5.chapters.chapter_06.functional.password.verification.server.StandaloneApplication; 9 | import reactor.test.StepVerifier; 10 | 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | import org.springframework.web.reactive.function.client.WebClient; 15 | 16 | import static org.junit.Assert.*; 17 | 18 | public class PasswordVerificationServiceTest { 19 | 20 | @Before 21 | public void setUp() throws InterruptedException { 22 | new Thread(StandaloneApplication::main).start(); 23 | Thread.sleep(1000); 24 | } 25 | 26 | @Test 27 | public void checkApplicationRunning() { 28 | BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(18); 29 | DefaultPasswordVerificationService service = 30 | new DefaultPasswordVerificationService(WebClient.builder()); 31 | 32 | StepVerifier.create(service.check("test", encoder.encode("test"))) 33 | .expectSubscription() 34 | .expectComplete() 35 | .verify(Duration.ofSeconds(30)); 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /chapter-06/security-samples/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.0.M4' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url 'https://repo.spring.io/milestone' } 25 | maven { url 'https://repo.spring.io/snapshot' } 26 | } 27 | 28 | ext { 29 | reactorExtraVersion = '3.2.0.RELEASE' 30 | } 31 | 32 | dependencies { 33 | compile('org.springframework.boot:spring-boot-autoconfigure') 34 | compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive') 35 | compile('org.springframework.boot:spring-boot-starter-webflux') 36 | compile('org.springframework.boot:spring-boot-starter-actuator') 37 | compile('org.springframework.boot:spring-boot-starter-security') 38 | 39 | compile("io.projectreactor.addons:reactor-extra:${reactorExtraVersion}") 40 | 41 | compileOnly('org.projectlombok:lombok') 42 | 43 | testCompile('org.springframework.boot:spring-boot-starter-test') 44 | } 45 | -------------------------------------------------------------------------------- /chapter-06/security-samples/src/main/java/org/rpis5/chapters/chapter_06/security/DefaultProfileService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.security; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public class DefaultProfileService implements ProfileService { 6 | 7 | @Override 8 | public Mono getByUser(String name) { 9 | return Mono.empty(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /chapter-06/security-samples/src/main/java/org/rpis5/chapters/chapter_06/security/Profile.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.security; 2 | 3 | public class Profile { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /chapter-06/security-samples/src/main/java/org/rpis5/chapters/chapter_06/security/ProfileService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.security; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public interface ProfileService { 6 | 7 | Mono getByUser(String name); 8 | } 9 | -------------------------------------------------------------------------------- /chapter-06/security-samples/src/main/java/org/rpis5/chapters/chapter_06/security/SecuredProfileController.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.security; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | import org.springframework.security.core.context.ReactiveSecurityContextHolder; 6 | import org.springframework.security.core.context.SecurityContext; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | @RequestMapping("/api/v1") 13 | public class SecuredProfileController { 14 | 15 | private final ProfileService profileService; 16 | 17 | public SecuredProfileController(ProfileService service) { 18 | profileService = service; 19 | } 20 | 21 | @GetMapping("/profiles") 22 | public Mono getProfile() { 23 | return ReactiveSecurityContextHolder 24 | .getContext() 25 | .map(SecurityContext::getAuthentication) 26 | .flatMap(auth -> profileService.getByUser(auth.getName())); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chapter-06/security-samples/src/main/java/org/rpis5/chapters/chapter_06/security/SecuritySampleApplication.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_06.security; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SecuritySampleApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SecuritySampleApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-06/security-samples/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/chapter-06/security-samples/src/main/resources/application.properties -------------------------------------------------------------------------------- /chapter-07/section-01-jdbc/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.0.M4' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url 'https://repo.spring.io/milestone' } 25 | maven { url 'https://repo.spring.io/snapshot' } 26 | } 27 | 28 | ext { 29 | springDataJdbcVersion = '1.0.0.RELEASE' 30 | jdbiVersion = '3.3.0' 31 | } 32 | 33 | 34 | dependencies { 35 | compile('org.springframework.boot:spring-boot-starter-webflux') 36 | compile('org.springframework.boot:spring-boot-starter-jdbc') 37 | compile("org.springframework.data:spring-data-jdbc:${springDataJdbcVersion}") 38 | 39 | compile("org.jdbi:jdbi3-core:${jdbiVersion}") 40 | compile("org.jdbi:jdbi3-sqlobject:${jdbiVersion}") 41 | 42 | compileOnly('org.projectlombok:lombok:1.18.2') 43 | 44 | // In-memory DB for demonstration purposes 45 | runtime('com.h2database:h2') 46 | 47 | testCompile('org.springframework.boot:spring-boot-starter-test') 48 | } 49 | -------------------------------------------------------------------------------- /chapter-07/section-01-jdbc/src/main/java/org/rpis5/chapters/chapter_07/jdbc/Book.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.jdbc; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.Id; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class Book { 12 | @Id 13 | private int id; 14 | private String title; 15 | } 16 | -------------------------------------------------------------------------------- /chapter-07/section-01-jdbc/src/main/java/org/rpis5/chapters/chapter_07/jdbc/BookJdbiDao.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.jdbc; 2 | 3 | import org.jdbi.v3.sqlobject.config.RegisterBeanMapper; 4 | import org.jdbi.v3.sqlobject.customizer.BindBean; 5 | import org.jdbi.v3.sqlobject.statement.SqlQuery; 6 | import org.jdbi.v3.sqlobject.statement.SqlUpdate; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * DAO implemented with the Jdbi library 12 | */ 13 | public interface BookJdbiDao { 14 | 15 | @SqlQuery("SELECT * FROM book ORDER BY id DESC") 16 | @RegisterBeanMapper(Book.class) 17 | List listBooks(); 18 | 19 | @SqlUpdate("INSERT INTO book(id, title) VALUES (:id, :title)") 20 | void insertBook(@BindBean Book book); 21 | } 22 | -------------------------------------------------------------------------------- /chapter-07/section-01-jdbc/src/main/java/org/rpis5/chapters/chapter_07/jdbc/BookSpringDataJdbcRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.jdbc; 2 | 3 | import org.springframework.data.jdbc.repository.query.Query; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.data.repository.query.Param; 6 | import org.springframework.scheduling.annotation.Async; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.stream.Stream; 12 | 13 | @Repository 14 | public interface BookSpringDataJdbcRepository 15 | extends CrudRepository { 16 | 17 | @Query("SELECT * FROM book WHERE LENGTH(title) = " + 18 | "(SELECT MAX(LENGTH(title)) FROM book)") 19 | List findByLongestTitle(); 20 | 21 | @Query("SELECT * FROM book WHERE LENGTH(title) = " + 22 | "(SELECT MIN(LENGTH(title)) FROM book)") 23 | Stream findByShortestTitle(); 24 | 25 | @Async 26 | @Query("SELECT * FROM book b " + 27 | "WHERE b.title = :title") 28 | CompletableFuture findBookByTitleAsync( 29 | @Param("title") String title); 30 | 31 | @Async 32 | @Query("SELECT * FROM book b " + 33 | "WHERE b.id > :fromId AND b.id < :toId") 34 | CompletableFuture> findBooksByIdBetweenAsync( 35 | @Param("fromId") Integer from, 36 | @Param("toId") Integer to); 37 | } 38 | -------------------------------------------------------------------------------- /chapter-07/section-01-jdbc/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.datasource.driverClassName: "org.h2.Driver" 2 | spring.datasource.url: "jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;TRACE_LEVEL_FILE=4" 3 | spring.datasource.username: "sa" 4 | spring.datasource.password: "sa" 5 | 6 | spring.h2.console.enabled: true 7 | spring.datasource.initialization-mode: always 8 | 9 | logging: 10 | pattern: 11 | console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} : %msg%n" 12 | level: 13 | org.springframework.jdbc.datasource: DEBUG 14 | org.rpis5.chapters.chapter_07.jdbc: DEBUG -------------------------------------------------------------------------------- /chapter-07/section-01-jdbc/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | insert into book values(10,'The Martian'); 2 | insert into book values(11,'Blue Mars'); 3 | insert into book values(12,'The Case for Mars'); 4 | insert into book values(13,'The War of Worlds'); 5 | insert into book values(14,'Edison''s Conquest of Mars'); -------------------------------------------------------------------------------- /chapter-07/section-01-jdbc/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | create table book 2 | ( 3 | id integer not null, 4 | title varchar(255) not null, 5 | primary key(id) 6 | ); -------------------------------------------------------------------------------- /chapter-07/section-02-jpa/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.0.M4' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url 'https://repo.spring.io/milestone' } 25 | maven { url 'https://repo.spring.io/snapshot' } 26 | } 27 | 28 | 29 | dependencies { 30 | compile('org.springframework.boot:spring-boot-starter-data-jpa') 31 | compile('org.springframework.boot:spring-boot-starter-web') 32 | 33 | compileOnly('org.projectlombok:lombok:1.18.2') 34 | 35 | runtime('com.h2database:h2') 36 | 37 | testCompile('org.springframework.boot:spring-boot-starter-test') 38 | } 39 | -------------------------------------------------------------------------------- /chapter-07/section-02-jpa/src/main/java/org/rpis5/chapters/chapter_07/jpa/Book.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.jpa; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Entity; 8 | import javax.persistence.Id; 9 | import javax.persistence.Table; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Entity 15 | @Table(name = "book") 16 | public class Book { 17 | @Id 18 | private int id; 19 | private String title; 20 | } 21 | -------------------------------------------------------------------------------- /chapter-07/section-02-jpa/src/main/java/org/rpis5/chapters/chapter_07/jpa/BookSpringDataJpaRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.jpa; 2 | 3 | import org.springframework.data.jpa.repository.Query; 4 | import org.springframework.data.repository.PagingAndSortingRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface BookSpringDataJpaRepository 9 | extends PagingAndSortingRepository { 10 | 11 | Iterable findByIdBetween(int lower, int upper); 12 | 13 | @Query("SELECT b FROM Book b WHERE " + 14 | "LENGTH(b.title) = (SELECT MIN(LENGTH(b2.title)) FROM Book b2)") 15 | Iterable findShortestTitle(); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /chapter-07/section-02-jpa/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.datasource.driverClassName: "org.h2.Driver" 2 | spring.datasource.url: "jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;TRACE_LEVEL_FILE=4" 3 | spring.datasource.username: "sa" 4 | spring.datasource.password: "sa" 5 | 6 | spring.h2.console.enabled: true 7 | spring.datasource.initialization-mode: always 8 | spring.jpa.hibernate.ddl-auto: none 9 | 10 | logging.level: 11 | org.springframework.jdbc.datasource: DEBUG -------------------------------------------------------------------------------- /chapter-07/section-02-jpa/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | insert into book values(10,'The Martian'); 2 | insert into book values(11,'Blue Mars'); 3 | insert into book values(12,'The Case for Mars'); 4 | insert into book values(13,'The War of Worlds'); 5 | insert into book values(14,'Edison''s Conquest of Mars'); 6 | insert into book values(15,'Moving Mars'); 7 | insert into book values(17,'Man Plus'); 8 | insert into book values(20,'A Martian Odyssey'); 9 | insert into book values(22,'The Martian Race'); -------------------------------------------------------------------------------- /chapter-07/section-02-jpa/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | create table book 2 | ( 3 | id integer not null, 4 | title varchar(255) not null, 5 | primary key(id) 6 | ); -------------------------------------------------------------------------------- /chapter-07/section-03-mongo/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.0.M4' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url "https://repo.spring.io/snapshot" } 25 | maven { url "https://repo.spring.io/milestone" } 26 | } 27 | 28 | 29 | dependencies { 30 | compile('org.springframework.boot:spring-boot-autoconfigure') 31 | compile('org.springframework.boot:spring-boot-starter-data-mongodb') 32 | compile('org.springframework.boot:spring-boot-starter-web') 33 | compile('de.flapdoodle.embed:de.flapdoodle.embed.mongo') 34 | 35 | compileOnly('org.projectlombok:lombok:1.18.2') 36 | 37 | runtime('com.h2database:h2') 38 | 39 | testCompile('org.springframework.boot:spring-boot-starter-test') 40 | } 41 | -------------------------------------------------------------------------------- /chapter-07/section-03-mongo/src/main/java/org/rpis5/chapters/chapter_07/mongo_repo/Book.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.mongo_repo; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import org.bson.types.ObjectId; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.mongodb.core.index.Indexed; 8 | import org.springframework.data.mongodb.core.mapping.Document; 9 | import org.springframework.data.mongodb.core.mapping.Field; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | @Document(collection = "book") 15 | @Data 16 | @NoArgsConstructor 17 | public class Book { 18 | @Id 19 | private ObjectId id; 20 | 21 | @Indexed 22 | private String title; 23 | 24 | @Field("pubYear") 25 | private int publishingYear; 26 | 27 | @Indexed 28 | private List authors; 29 | 30 | public Book(String title, int publishingYear, String... authors) { 31 | this.title = title; 32 | this.publishingYear = publishingYear; 33 | this.authors = Arrays.asList(authors); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter-07/section-03-mongo/src/main/java/org/rpis5/chapters/chapter_07/mongo_repo/BookSpringDataMongoRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.mongo_repo; 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository; 4 | import org.springframework.data.mongodb.repository.Query; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface BookSpringDataMongoRepository 9 | extends MongoRepository { 10 | 11 | Iterable findByAuthorsOrderByPublishingYearDesc(String... authors); 12 | 13 | @Query("{ 'authors.1': { $exists: true } }") 14 | Iterable booksWithFewAuthors(); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /chapter-07/section-04-rx-mongo/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.0.M4' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url "https://repo.spring.io/snapshot" } 25 | maven { url "https://repo.spring.io/milestone" } 26 | } 27 | 28 | ext { 29 | reactorExtraVersion = '3.2.0.RELEASE' 30 | junitPlatformVersion = '1.2.0' 31 | } 32 | 33 | dependencies { 34 | compile('org.springframework.boot:spring-boot-autoconfigure') 35 | compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive') 36 | compile('org.springframework.boot:spring-boot-starter-webflux') 37 | compile('org.springframework.boot:spring-boot-starter-actuator') 38 | compile('de.flapdoodle.embed:de.flapdoodle.embed.mongo') 39 | 40 | compile("io.projectreactor.addons:reactor-extra:${reactorExtraVersion}") 41 | 42 | compileOnly('org.projectlombok:lombok') 43 | 44 | testCompile('org.springframework.boot:spring-boot-starter-test') 45 | } 46 | -------------------------------------------------------------------------------- /chapter-07/section-04-rx-mongo/src/main/java/org/rpis5/chapters/chapter_07/mongo_rx_tx/Book.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.mongo_rx_tx; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.Wither; 7 | import org.bson.types.ObjectId; 8 | import org.springframework.data.annotation.Id; 9 | import org.springframework.data.mongodb.core.index.Indexed; 10 | import org.springframework.data.mongodb.core.mapping.Document; 11 | import org.springframework.data.mongodb.core.mapping.Field; 12 | 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | @Document(collection = "book") 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | @Data 20 | @Wither 21 | public class Book { 22 | @Id 23 | private ObjectId id; 24 | 25 | @Indexed 26 | private String title; 27 | 28 | @Field("pubYear") 29 | private int publishingYear; 30 | 31 | @Indexed 32 | private List authors; 33 | 34 | public Book(String title, int publishingYear, String... authors) { 35 | this.title = title; 36 | this.publishingYear = publishingYear; 37 | this.authors = Arrays.asList(authors); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chapter-07/section-04-rx-mongo/src/main/java/org/rpis5/chapters/chapter_07/mongo_rx_tx/BookSpringDataMongoRxRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.mongo_rx_tx; 2 | 3 | import org.bson.types.ObjectId; 4 | import org.reactivestreams.Publisher; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.mongodb.repository.Meta; 7 | import org.springframework.data.mongodb.repository.Query; 8 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 9 | import org.springframework.stereotype.Repository; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | 13 | @Repository 14 | public interface BookSpringDataMongoRxRepository 15 | extends ReactiveMongoRepository { 16 | 17 | Mono findOneByTitle(Mono title); 18 | 19 | Flux findManyByTitleRegex(String regexp); 20 | 21 | @Meta(maxScanDocuments = 3) 22 | Flux findByAuthorsOrderByPublishingYearDesc(Publisher authors); 23 | 24 | @Query("{ 'authors.1': { $exists: true } }") 25 | Flux booksWithFewAuthors(); 26 | 27 | Flux findByPublishingYearBetweenOrderByPublishingYear( 28 | Integer from, 29 | Integer to, 30 | Pageable pageable 31 | ); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /chapter-07/section-04-rx-mongo/src/main/java/org/rpis5/chapters/chapter_07/mongo_rx_tx/RxMongoDriverQueryService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.mongo_rx_tx; 2 | 3 | import com.mongodb.client.model.Filters; 4 | import com.mongodb.reactivestreams.client.MongoClient; 5 | import org.bson.conversions.Bson; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Service; 8 | import reactor.core.publisher.Flux; 9 | 10 | import java.util.Collections; 11 | 12 | @Service 13 | public class RxMongoDriverQueryService { 14 | private static final String BOOK_COLLECTION = "book"; 15 | 16 | private final MongoClient mongoClient; 17 | private final String dbName; 18 | 19 | public RxMongoDriverQueryService( 20 | MongoClient mongoClient, 21 | @Value("${spring.data.mongodb.database}") String dbName 22 | ) { 23 | this.mongoClient = mongoClient; 24 | this.dbName = dbName; 25 | } 26 | 27 | public Flux findBooksByTitle(String title, boolean negate) { 28 | return Flux.defer(() -> { 29 | Bson query = Filters 30 | .regex("title", ".*" + title + ".*"); 31 | 32 | if (negate) { 33 | query = Filters.not(query); 34 | } 35 | return mongoClient 36 | .getDatabase(dbName) 37 | .getCollection(BOOK_COLLECTION) 38 | .find(query); 39 | }) 40 | .map(doc -> new Book( 41 | doc.getObjectId("id"), 42 | doc.getString("title"), 43 | doc.getInteger("pubYear"), 44 | Collections.emptyList() // Omit authors deserialization for the sake of simplicity 45 | )); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /chapter-07/section-04-rx-mongo/src/main/java/org/rpis5/chapters/chapter_07/mongo_rx_tx/RxMongoTemplateQueryService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.mongo_rx_tx; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.mongodb.core.ReactiveMongoOperations; 5 | import org.springframework.data.mongodb.core.query.Criteria; 6 | import org.springframework.data.mongodb.core.query.Query; 7 | import org.springframework.stereotype.Service; 8 | import reactor.core.publisher.Flux; 9 | 10 | @Service 11 | @RequiredArgsConstructor 12 | public class RxMongoTemplateQueryService { 13 | private static final String BOOK_COLLECTION = "book"; 14 | 15 | private final ReactiveMongoOperations mongoOperations; 16 | 17 | public Flux findBooksByTitle(String title) { 18 | Query query = Query.query(new Criteria("title") 19 | .regex(".*" + title + ".*")) 20 | .limit(100); 21 | return mongoOperations.find(query, Book.class, BOOK_COLLECTION); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter-07/section-04-rx-mongo/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | spring.data.mongodb.database: test-db 3 | 4 | #logging.level: 5 | #org.springframework.data.mongodb.core.ReactiveMongoTemplate: DEBUG -------------------------------------------------------------------------------- /chapter-07/section-05-rx-mongo-tx/src/main/java/org/rpis5/chapters/chapter_07/mongo_rx_tx/Chapter7RxMongoTransactions.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.mongo_rx_tx; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 9 | 10 | @EnableMongoRepositories 11 | @SpringBootApplication 12 | @RequiredArgsConstructor 13 | @Slf4j 14 | public class Chapter7RxMongoTransactions implements CommandLineRunner { 15 | 16 | public static void main(String... args) { 17 | SpringApplication.run(Chapter7RxMongoTransactions.class, args); 18 | } 19 | 20 | @Override 21 | public void run(String... args) { 22 | log.info("This application is verifiable through unit tests only!"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chapter-07/section-05-rx-mongo-tx/src/main/java/org/rpis5/chapters/chapter_07/mongo_rx_tx/wallet/BaseWalletService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.mongo_rx_tx.wallet; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | 8 | import java.util.Comparator; 9 | 10 | import static java.lang.String.format; 11 | 12 | @Slf4j 13 | @RequiredArgsConstructor 14 | public abstract class BaseWalletService implements WalletService { 15 | protected final WalletRepository walletRepository; 16 | 17 | public Flux generateClients(Integer number, Integer defaultBalance) { 18 | return walletRepository.saveAll( 19 | Flux.range(1, number) 20 | .map(id -> format("client-%05d", id)) 21 | .map(owner -> Wallet.wallet(owner, defaultBalance)) 22 | ).map(Wallet::getOwner); 23 | } 24 | 25 | @Override 26 | public Mono reportAllWallets() { 27 | return walletRepository 28 | .findAll() 29 | .sort(Comparator.comparing(Wallet::getOwner)) 30 | .doOnNext(w -> 31 | log.info(format("%10s: %7d$ (d: %5s | w: %5s)", 32 | w.getOwner(), w.getBalance(), w.getDepositOperations(), w.getWithdrawOperations()))) 33 | .reduce(new Statistics(), Statistics::withWallet); 34 | } 35 | 36 | @Override 37 | public Mono removeAllClients() { 38 | return walletRepository.deleteAll(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chapter-07/section-05-rx-mongo-tx/src/main/java/org/rpis5/chapters/chapter_07/mongo_rx_tx/wallet/Wallet.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.mongo_rx_tx.wallet; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.Wither; 7 | import org.bson.types.ObjectId; 8 | import org.springframework.data.annotation.Id; 9 | import org.springframework.data.mongodb.core.mapping.Document; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Wither 14 | @Data 15 | @Document(collection = "wallet") 16 | public class Wallet { 17 | @Id private ObjectId id; 18 | private String owner; 19 | private int balance; 20 | 21 | // Some statistics 22 | private int depositOperations; 23 | private int withdrawOperations; 24 | 25 | public boolean hasEnoughFunds(int amount) { 26 | return balance >= amount; 27 | } 28 | 29 | public void withdraw(int amount) { 30 | if (!hasEnoughFunds(amount)) { 31 | throw new RuntimeException("Not enough funds!"); 32 | } 33 | this.balance = this.balance - amount; 34 | this.withdrawOperations += 1; 35 | } 36 | 37 | public void deposit(int amount) { 38 | this.balance = this.balance + amount; 39 | this.depositOperations += 1; 40 | } 41 | 42 | public static Wallet wallet(String owner, int balance) { 43 | return new Wallet(new ObjectId(), owner, balance, 0, 0); 44 | } 45 | } 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /chapter-07/section-05-rx-mongo-tx/src/main/java/org/rpis5/chapters/chapter_07/mongo_rx_tx/wallet/WalletRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.mongo_rx_tx.wallet; 2 | 3 | import org.bson.types.ObjectId; 4 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 5 | import org.springframework.stereotype.Repository; 6 | import reactor.core.publisher.Mono; 7 | 8 | @Repository 9 | public interface WalletRepository 10 | extends ReactiveMongoRepository { 11 | 12 | Mono findByOwner(Mono owner); 13 | } 14 | -------------------------------------------------------------------------------- /chapter-07/section-05-rx-mongo-tx/src/main/java/org/rpis5/chapters/chapter_07/mongo_rx_tx/wallet/WalletService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.mongo_rx_tx.wallet; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | 9 | public interface WalletService { 10 | 11 | Flux generateClients(Integer number, Integer defaultBalance); 12 | 13 | Mono transferMoney( 14 | Mono fromOwner, 15 | Mono toOwner, 16 | Mono amount); 17 | 18 | Mono reportAllWallets(); 19 | 20 | Mono removeAllClients(); 21 | 22 | enum TxResult { 23 | SUCCESS, 24 | NOT_ENOUGH_FUNDS, 25 | TX_CONFLICT 26 | } 27 | 28 | @NoArgsConstructor 29 | @AllArgsConstructor 30 | @Data 31 | class Statistics { 32 | private long totalAccounts; 33 | private long totalBalance; 34 | private long totalDeposits; 35 | private long totalWithdraws; 36 | 37 | public Statistics withWallet(Wallet w) { 38 | return new Statistics( 39 | this.totalAccounts + 1, 40 | this.totalBalance + w.getBalance(), 41 | this.totalDeposits + w.getDepositOperations(), 42 | this.totalWithdraws + w.getWithdrawOperations()); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /chapter-07/section-05-rx-mongo-tx/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | spring: 3 | data: 4 | mongodb: 5 | # uri: "mongodb://192.168.2.106:27017/wallet,192.168.2.106:27018/wallet,192.168.2.106:27019/wallet" 6 | 7 | host: "localhost" 8 | port: 27017 9 | database: "wallet" 10 | # username: "admin" 11 | # password: "admin" 12 | # authentication-database: "admin" 13 | 14 | logging.level: 15 | reactor.core.publisher.FluxUsingWhen: ERROR 16 | #org.springframework.data.mongodb.core.ReactiveMongoTemplate: DEBUG -------------------------------------------------------------------------------- /chapter-07/section-05-rx-mongo-tx/src/main/resources/replica-set-config.js: -------------------------------------------------------------------------------- 1 | rs.initiate({ 2 | _id: 'reactive', 3 | members: [ 4 | {_id : 1, host : 'host.docker.internal:27017'}, 5 | {_id : 2, host : 'host.docker.internal:27018'}, 6 | {_id : 3, host : 'host.docker.internal:27019'} 7 | ] 8 | }); -------------------------------------------------------------------------------- /chapter-07/section-05-rx-mongo-tx/src/test/resources/README.md: -------------------------------------------------------------------------------- 1 | # Comparison of naive reactive implementation and transactional reactive implementation. 2 | 3 | Set up was tested with MacOS. 4 | 5 | Requirements: 6 | 7 | * Docker 8 | 9 | ## Naive implementation 10 | 11 | Uses no transactional consistency and consequently produces incorrect final state. 12 | 13 | ## Transactional reactive implementation 14 | 15 | Uses transactional consistency and produces correct final state. 16 | Uses retry strategy when transaction fails due to conflicting transactions. -------------------------------------------------------------------------------- /chapter-07/section-05-rx-mongo-tx/src/test/resources/docker-compose.yml: -------------------------------------------------------------------------------- 1 | mongo-node1: 2 | image: mongo:4.0.1 3 | hostname: "mongo-node1" 4 | ports: 5 | - "27017:27017" 6 | command: mongod --storageEngine wiredTiger --replSet reactive 7 | mongo-node2: 8 | image: mongo:4.0.1 9 | hostname: "mongo-node2" 10 | ports: 11 | - "27018:27017" 12 | command: mongod --storageEngine wiredTiger --replSet reactive 13 | mongo-node3: 14 | hostname: "mongo-node3" 15 | image: mongo:4.0.1 16 | ports: 17 | - "27019:27017" 18 | command: mongod --storageEngine wiredTiger --replSet reactive -------------------------------------------------------------------------------- /chapter-07/section-06-r2dbc/src/main/java/org/rpis5/chapters/chapter_07/r2dbs/Book.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.r2dbs; 2 | 3 | import lombok.Data; 4 | import lombok.Value; 5 | import org.springframework.data.annotation.Id; 6 | import org.springframework.data.relational.core.mapping.Table; 7 | 8 | @Table("book") 9 | @Data 10 | public class Book { 11 | @Id Integer id; 12 | String title; 13 | Integer publishingYear; 14 | 15 | public Book(Integer id, String title, Integer publishingYear) { 16 | this.id = id; 17 | this.title = title; 18 | this.publishingYear = publishingYear; 19 | } 20 | 21 | public Book(String title, Integer publishingYear) { 22 | this(null, title, publishingYear); 23 | } 24 | 25 | public Book() { 26 | this(null, null, null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chapter-07/section-06-r2dbc/src/main/java/org/rpis5/chapters/chapter_07/r2dbs/BookRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.r2dbs; 2 | 3 | import org.springframework.data.jdbc.repository.query.Query; 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 BookRepository 10 | extends ReactiveCrudRepository { 11 | 12 | @Query("SELECT * FROM book WHERE publishing_year = " + 13 | "(SELECT MAX(publishing_year) FROM book)") 14 | Flux findTheLatestBooks(); 15 | } 16 | -------------------------------------------------------------------------------- /chapter-07/section-06-r2dbc/src/main/java/org/rpis5/chapters/chapter_07/r2dbs/DatabaseLocation.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.r2dbs; 2 | 3 | import lombok.Value; 4 | 5 | @Value 6 | public class DatabaseLocation { 7 | private final String host; 8 | private final Integer port; 9 | private final String database; 10 | private final String user; 11 | private final String password; 12 | } 13 | -------------------------------------------------------------------------------- /chapter-07/section-06-r2dbc/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | org.springframework.data.r2dbc.function.DefaultDatabaseClient: DEBUG 4 | org.springframework.data.r2dbc.function.DefaultTransactionalDatabaseClient: DEBUG -------------------------------------------------------------------------------- /chapter-07/section-07-rx-webflux/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.0.M4' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url 'https://repo.spring.io/milestone' } 25 | maven { url 'https://repo.spring.io/snapshot' } 26 | } 27 | 28 | ext { 29 | reactorExtraVersion = '3.2.0.RELEASE' 30 | } 31 | 32 | dependencies { 33 | compile('org.springframework.boot:spring-boot-autoconfigure') 34 | compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive') 35 | compile('org.springframework.boot:spring-boot-starter-webflux') 36 | compile('org.springframework.boot:spring-boot-starter-actuator') 37 | compile('de.flapdoodle.embed:de.flapdoodle.embed.mongo') 38 | 39 | compile("io.projectreactor.addons:reactor-extra:${reactorExtraVersion}") 40 | 41 | compileOnly('org.projectlombok:lombok') 42 | 43 | testCompile('org.springframework.boot:spring-boot-starter-test') 44 | } 45 | -------------------------------------------------------------------------------- /chapter-07/section-07-rx-webflux/src/main/java/org/rpis5/chapters/chapter_07/webflux/ChatHandler.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.webflux; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | import reactor.core.publisher.Flux; 6 | 7 | @Service 8 | @RequiredArgsConstructor 9 | public class ChatHandler { 10 | private final MessageRepository messageRepository; 11 | 12 | public Flux messageStream() { 13 | return messageRepository.findBy(); 14 | } 15 | 16 | public Flux messageStreamForUser(String user) { 17 | return messageRepository.findByUser(user); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter-07/section-07-rx-webflux/src/main/java/org/rpis5/chapters/chapter_07/webflux/Message.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.webflux; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.bson.types.ObjectId; 8 | import org.springframework.data.annotation.Id; 9 | import org.springframework.data.mongodb.core.mapping.Document; 10 | 11 | import java.time.LocalDateTime; 12 | 13 | @Document(collection = "chat-messages") 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Data 17 | public class Message { 18 | @JsonIgnore 19 | @Id private ObjectId id; 20 | private LocalDateTime time; 21 | private String user; 22 | private String messageBody; 23 | } 24 | -------------------------------------------------------------------------------- /chapter-07/section-07-rx-webflux/src/main/java/org/rpis5/chapters/chapter_07/webflux/MessageRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.webflux; 2 | 3 | import org.bson.types.ObjectId; 4 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 5 | import org.springframework.data.mongodb.repository.Tailable; 6 | import reactor.core.publisher.Flux; 7 | 8 | public interface MessageRepository 9 | extends ReactiveMongoRepository { 10 | 11 | @Tailable 12 | Flux findBy(); 13 | 14 | @Tailable 15 | Flux findByUser(String user); 16 | } 17 | -------------------------------------------------------------------------------- /chapter-07/section-07-rx-webflux/src/main/resources/application.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/chapter-07/section-07-rx-webflux/src/main/resources/application.yml -------------------------------------------------------------------------------- /chapter-07/section-08-rx-dbs/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.0.M4' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url "https://repo.spring.io/snapshot" } 25 | maven { url "https://repo.spring.io/milestone" } 26 | } 27 | 28 | dependencies { 29 | compile('org.springframework.boot:spring-boot-starter-actuator') 30 | compile('org.springframework.boot:spring-boot-starter-webflux') 31 | 32 | compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive') 33 | compile('org.springframework.boot:spring-boot-starter-data-cassandra-reactive') 34 | compile('org.springframework.boot:spring-boot-starter-data-couchbase-reactive') 35 | compile('org.springframework.boot:spring-boot-starter-data-redis-reactive') 36 | 37 | compileOnly('org.projectlombok:lombok:1.18.2') 38 | 39 | testCompile('org.springframework.boot:spring-boot-starter-test') 40 | testCompile('de.flapdoodle.embed:de.flapdoodle.embed.mongo') 41 | testCompile('io.projectreactor:reactor-test') 42 | } 43 | -------------------------------------------------------------------------------- /chapter-07/section-08-rx-dbs/src/main/java/org/rpis5/chapters/chapter_07/rx_dbs/Chapter7ReactiveConnectorsApplication.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rx_dbs; 2 | 3 | public class Chapter7ReactiveConnectorsApplication { 4 | public static void main(String[] args) { 5 | // This application is not intended to be executable 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /chapter-07/section-08-rx-dbs/src/main/java/org/rpis5/chapters/chapter_07/rx_dbs/User.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rx_dbs; 2 | 3 | public class User { 4 | } 5 | -------------------------------------------------------------------------------- /chapter-07/section-08-rx-dbs/src/main/java/org/rpis5/chapters/chapter_07/rx_dbs/cassandra/UserRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rx_dbs.cassandra; 2 | 3 | import org.rpis5.chapters.chapter_07.rx_dbs.User; 4 | import org.springframework.data.cassandra.repository.ReactiveCassandraRepository; 5 | 6 | /** 7 | * Example of ReactiveCassandraRepository 8 | */ 9 | public interface UserRepository extends ReactiveCassandraRepository { 10 | } 11 | -------------------------------------------------------------------------------- /chapter-07/section-08-rx-dbs/src/main/java/org/rpis5/chapters/chapter_07/rx_dbs/couchbase/UserRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rx_dbs.couchbase; 2 | 3 | import org.rpis5.chapters.chapter_07.rx_dbs.User; 4 | import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository; 5 | 6 | /** 7 | * Example of ReactiveCouchbaseRepository 8 | */ 9 | public interface UserRepository extends ReactiveCouchbaseRepository { 10 | } 11 | -------------------------------------------------------------------------------- /chapter-07/section-08-rx-dbs/src/main/java/org/rpis5/chapters/chapter_07/rx_dbs/mongo/UserRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rx_dbs.mongo; 2 | 3 | import org.rpis5.chapters.chapter_07.rx_dbs.User; 4 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 5 | 6 | /** 7 | * Example of ReactiveMongoRepository 8 | */ 9 | public interface UserRepository extends ReactiveMongoRepository { 10 | } 11 | -------------------------------------------------------------------------------- /chapter-07/section-08-rx-dbs/src/main/java/org/rpis5/chapters/chapter_07/rx_dbs/redis/UserSession.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rx_dbs.redis; 2 | 3 | public class UserSession { 4 | } 5 | -------------------------------------------------------------------------------- /chapter-07/section-08-rx-dbs/src/main/java/org/rpis5/chapters/chapter_07/rx_dbs/redis/UserSessionCache.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rx_dbs.redis; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.redis.core.ReactiveRedisOperations; 5 | import reactor.core.publisher.Mono; 6 | 7 | /** 8 | * With Reactive Redis support, we have to work directly with ReactiveRedisOperations. 9 | */ 10 | @RequiredArgsConstructor 11 | public class UserSessionCache { 12 | private final ReactiveRedisOperations rxRedisOperations; 13 | 14 | public Mono getUSerSession(String userId) { 15 | return Mono.empty(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chapter-07/section-08-rx-dbs/src/main/resources/application.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/chapter-07/section-08-rx-dbs/src/main/resources/application.yaml -------------------------------------------------------------------------------- /chapter-07/section-09-rx-sync/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.0.M4' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url 'https://repo.spring.io/milestone' } 25 | maven { url 'https://repo.spring.io/snapshot' } 26 | } 27 | 28 | ext { 29 | reactorExtraVersion = '3.2.0.RELEASE' 30 | } 31 | 32 | dependencies { 33 | compile('org.springframework.boot:spring-boot-starter-data-jpa') 34 | compile('org.springframework.boot:spring-boot-starter-webflux') 35 | compile('org.springframework.boot:spring-boot-starter-actuator') 36 | 37 | compile("io.projectreactor.addons:reactor-extra:${reactorExtraVersion}") 38 | 39 | 40 | compileOnly('org.projectlombok:lombok') 41 | 42 | runtime('com.h2database:h2') 43 | 44 | testCompile('org.springframework.boot:spring-boot-starter-test') 45 | } 46 | -------------------------------------------------------------------------------- /chapter-07/section-09-rx-sync/src/main/java/org/rpis5/chapters/chapter_07/wrapped_sync/Book.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.wrapped_sync; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.GenerationType; 10 | import javax.persistence.Id; 11 | import javax.persistence.Table; 12 | 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Entity 17 | @Table(name = "book") 18 | public class Book { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Integer id; 22 | private String title; 23 | private Integer publishingYear; 24 | 25 | public Book(String title, int publishingYear) { 26 | this(null, title, publishingYear); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /chapter-07/section-09-rx-sync/src/main/java/org/rpis5/chapters/chapter_07/wrapped_sync/BookJpaRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.wrapped_sync; 2 | 3 | import org.springframework.data.jpa.repository.Query; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface BookJpaRepository 9 | extends CrudRepository { 10 | 11 | Iterable findByIdBetween(int lower, int upper); 12 | 13 | @Query("SELECT b FROM Book b WHERE " + 14 | "LENGTH(b.title) = (SELECT MIN(LENGTH(b2.title)) FROM Book b2)") 15 | Iterable findShortestTitle(); 16 | } 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter-07/section-09-rx-sync/src/main/java/org/rpis5/chapters/chapter_07/wrapped_sync/RxBookRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.wrapped_sync; 2 | 3 | import org.reactivestreams.Publisher; 4 | import org.springframework.stereotype.Component; 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | import reactor.core.scheduler.Scheduler; 8 | 9 | import static reactor.function.TupleUtils.function; 10 | 11 | @Component 12 | public class RxBookRepository extends 13 | ReactiveCrudRepositoryAdapter { 14 | 15 | public RxBookRepository( 16 | BookJpaRepository delegate, 17 | Scheduler scheduler 18 | ) { 19 | super(delegate, scheduler); 20 | } 21 | 22 | public Flux findByIdBetween( 23 | Publisher lowerPublisher, 24 | Publisher upperPublisher 25 | ) { 26 | return Mono.zip( 27 | Mono.from(lowerPublisher), 28 | Mono.from(upperPublisher) 29 | ).flatMapMany( 30 | function((lower, upper) -> 31 | Flux 32 | .fromIterable(delegate.findByIdBetween(lower, upper)) 33 | .subscribeOn(scheduler) 34 | )) 35 | .subscribeOn(scheduler); 36 | } 37 | 38 | public Flux findShortestTitle() { 39 | return Mono.fromCallable(delegate::findShortestTitle) 40 | .subscribeOn(scheduler) 41 | .flatMapMany(Flux::fromIterable); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter-07/section-09-rx-sync/src/main/java/org/rpis5/chapters/chapter_07/wrapped_sync/RxPersistenceConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.wrapped_sync; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import reactor.core.scheduler.Scheduler; 6 | import reactor.core.scheduler.Schedulers; 7 | 8 | @Configuration 9 | public class RxPersistenceConfiguration { 10 | @Bean 11 | public Scheduler jpaScheduler() { 12 | return Schedulers.newParallel("JPA", 10); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter-07/section-09-rx-sync/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.datasource.driverClassName: "org.h2.Driver" 2 | spring.datasource.url: "jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;TRACE_LEVEL_FILE=4" 3 | spring.datasource.username: "sa" 4 | spring.datasource.password: "sa" 5 | 6 | spring.h2.console.enabled: true 7 | spring.datasource.initialization-mode: always 8 | spring.jpa.hibernate.ddl-auto: none 9 | 10 | logging.level: 11 | org.springframework.jdbc.datasource: DEBUG -------------------------------------------------------------------------------- /chapter-07/section-09-rx-sync/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | insert into book values(12,'The Case for Mars', 1996); 2 | insert into book values(14,'Edison''s Conquest of Mars', 1947); 3 | insert into book values(15,'Moving Mars', 1993); 4 | insert into book values(17,'Man Plus', 1976); 5 | insert into book values(20,'A Martian Odyssey', 1934); 6 | insert into book values(22,'The Martian Race', 1999); -------------------------------------------------------------------------------- /chapter-07/section-09-rx-sync/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | create table book 2 | ( 3 | id integer auto_increment primary key, 4 | title varchar(255) not null, 5 | publishing_year integer not null, 6 | ); -------------------------------------------------------------------------------- /chapter-07/section-10-rxjava2-jdbc/src/main/java/org/rpis5/chapters/chapter_07/rxjava2jdbc/book/Book.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rxjava2jdbc.book; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.davidmoten.rx.jdbc.annotations.Column; 5 | import org.davidmoten.rx.jdbc.annotations.Query; 6 | 7 | import java.util.UUID; 8 | 9 | @Query("select id, title, publishing_year from book order by publishing_year") 10 | public interface Book { 11 | @Column String id(); 12 | @Column String title(); 13 | @Column Integer publishing_year(); 14 | 15 | static Book of(String title, Integer publishingYear) { 16 | return new Impl(UUID.randomUUID().toString(), title, publishingYear); 17 | } 18 | 19 | @RequiredArgsConstructor 20 | class Impl implements Book { 21 | private final String id; 22 | private final String title; 23 | private final Integer publishingYear; 24 | 25 | @Override 26 | public String id() { 27 | return id; 28 | } 29 | 30 | @Override 31 | public String title() { 32 | return title; 33 | } 34 | 35 | @Override 36 | public Integer publishing_year() { 37 | return publishingYear; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chapter-07/section-10-rxjava2-jdbc/src/main/java/org/rpis5/chapters/chapter_07/rxjava2jdbc/book/DatabaseConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rxjava2jdbc.book; 2 | 3 | import com.google.common.base.Charsets; 4 | import com.google.common.io.Resources; 5 | import org.davidmoten.rx.jdbc.Database; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import reactor.core.publisher.Mono; 10 | 11 | @Configuration 12 | public class DatabaseConfiguration { 13 | @Bean 14 | public Database database( 15 | @Value("${spring.datasource.url}") String uri, 16 | @Value("${rxjava2jdbc.pool.size}") Integer poolSize 17 | ) { 18 | Database db = Database 19 | .from(uri, poolSize); 20 | 21 | initializeDatabase(db) 22 | .block(); 23 | 24 | return db; 25 | } 26 | 27 | private Mono initializeDatabase(Database database) { 28 | return Mono.fromCallable(() -> { 29 | String schema = 30 | Resources.toString(Resources.getResource("schema.sql"), Charsets.UTF_8); 31 | 32 | String data = 33 | Resources.toString(Resources.getResource("data.sql"), Charsets.UTF_8); 34 | 35 | return database.update(schema) 36 | .counts() 37 | .ignoreElements() 38 | .andThen(database 39 | .update(data) 40 | .counts()) 41 | .blockingLast(); 42 | }).then(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter-07/section-10-rxjava2-jdbc/src/main/java/org/rpis5/chapters/chapter_07/rxjava2jdbc/wallet/Wallet.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rxjava2jdbc.wallet; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.Wither; 7 | 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Data 11 | public class Wallet { 12 | private Integer id; 13 | private String owner; 14 | private int balance; 15 | 16 | // Some statistics 17 | private int depositOperations; 18 | private int withdrawOperations; 19 | 20 | public boolean hasEnoughFunds(int amount) { 21 | return balance >= amount; 22 | } 23 | 24 | public void withdraw(int amount) { 25 | if (!hasEnoughFunds(amount)) { 26 | throw new RuntimeException("Not enough funds!"); 27 | } 28 | this.balance = this.balance - amount; 29 | this.withdrawOperations += 1; 30 | } 31 | 32 | public void deposit(int amount) { 33 | this.balance = this.balance + amount; 34 | this.depositOperations += 1; 35 | } 36 | 37 | public static Wallet wallet(Integer id, String owner, int balance) { 38 | return new Wallet(id, owner, balance, 0, 0); 39 | } 40 | } 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /chapter-07/section-10-rxjava2-jdbc/src/main/java/org/rpis5/chapters/chapter_07/rxjava2jdbc/wallet/WalletData.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rxjava2jdbc.wallet; 2 | 3 | import org.davidmoten.rx.jdbc.annotations.Column; 4 | import org.davidmoten.rx.jdbc.annotations.Query; 5 | 6 | @Query("select id, owner, balance, deposits, withdraws from wallet") 7 | public interface WalletData { 8 | @Column Integer id(); 9 | @Column String owner(); 10 | @Column Integer balance(); 11 | 12 | // Some statistics 13 | @Column Integer deposits(); 14 | @Column Integer withdraws(); 15 | } 16 | -------------------------------------------------------------------------------- /chapter-07/section-10-rxjava2-jdbc/src/main/java/org/rpis5/chapters/chapter_07/rxjava2jdbc/wallet/WalletService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_07.rxjava2jdbc.wallet; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | 9 | public interface WalletService { 10 | 11 | Mono initializeDatabase(); 12 | 13 | Flux generateClients(Integer number, Integer defaultBalance); 14 | 15 | Mono transferMoney(Mono fromOwner, Mono toOwner, Mono amount); 16 | 17 | Mono reportAllWallets(); 18 | 19 | Mono removeAllClients(); 20 | 21 | enum TxResult { 22 | SUCCESS, 23 | NOT_ENOUGH_FUNDS, 24 | TX_CONFLICT 25 | } 26 | 27 | @NoArgsConstructor 28 | @AllArgsConstructor 29 | @Data 30 | class Statistics { 31 | private long totalAccounts; 32 | private long totalBalance; 33 | private long totalDeposits; 34 | private long totalWithdraws; 35 | 36 | public Statistics withWallet(WalletData w) { 37 | return new Statistics( 38 | this.totalAccounts + 1, 39 | this.totalBalance + w.balance(), 40 | this.totalDeposits + w.deposits(), 41 | this.totalWithdraws + w.withdraws()); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter-07/section-10-rxjava2-jdbc/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.datasource: 2 | driverClassName: "org.h2.Driver" 3 | url: "jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;mode=MySQL" 4 | #url: "jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;TRACE_LEVEL_FILE=4;mode=MySQL" 5 | username: "sa" 6 | password: "sa" 7 | 8 | spring.h2.console.enabled: true 9 | spring.datasource.initialization-mode: always 10 | spring.jpa.hibernate.ddl-auto: none 11 | 12 | logging.level: 13 | org.springframework.jdbc.datasource: INFO 14 | 15 | rxjava2jdbc.pool.size: 20 -------------------------------------------------------------------------------- /chapter-07/section-10-rxjava2-jdbc/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | insert into book values('98cfd43e-1967-47a1-ace7-8399a29866a0','The Martian', 2011); 2 | insert into book values('99cfd43e-1111-3333-ace7-8399a29866a0','Blue Mars', 1996); 3 | insert into book values('11111111-1967-2343-7777-000000000000','The Case for Mars', 1996); 4 | insert into book values('99999999-1967-47a1-aaaa-8399a29866a0','The War of Worlds', 1897); 5 | insert into book values('99cfd43e-1967-3344-e34a-222222233333','Edison''s Conquest of Mars', 1947); -------------------------------------------------------------------------------- /chapter-07/section-10-rxjava2-jdbc/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | create table wallet 2 | ( 3 | id integer auto_increment primary key, 4 | owner varchar(255) not null, 5 | balance integer not null, 6 | deposits integer not null, 7 | withdraws integer not null 8 | ); 9 | 10 | create table book 11 | ( 12 | id varchar(64) primary key, 13 | title varchar(255) not null, 14 | publishing_year integer not null 15 | ); -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/controller/ChatController.java: -------------------------------------------------------------------------------- 1 | package com.example.controller; 2 | 3 | import java.util.Collections; 4 | 5 | import com.example.controller.vm.UserVM; 6 | import com.example.controller.vm.UsersStatisticVM; 7 | 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | 13 | @Controller 14 | @RequestMapping 15 | public class ChatController { 16 | 17 | @GetMapping 18 | public String index(Model model) { 19 | 20 | model.addAttribute("messages", Collections.emptyList()); 21 | UserVM emptyUser = new UserVM("", ""); 22 | model.addAttribute("statistic", new UsersStatisticVM(emptyUser, emptyUser)); 23 | 24 | return "chat"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/controller/vm/MessageVM.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.vm; 2 | 3 | import java.util.Date; 4 | import javax.validation.constraints.NotNull; 5 | 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.NonNull; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class MessageVM { 15 | @NotNull 16 | @NonNull 17 | private String id; 18 | @NotNull 19 | @NonNull 20 | private String text; 21 | @NotNull 22 | @NonNull 23 | private String html; 24 | @NonNull 25 | @NotNull 26 | private String username; 27 | @NonNull 28 | @NotNull 29 | private String userAvatarUrl; 30 | @NotNull 31 | @NonNull 32 | private Date sent; 33 | } 34 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/controller/vm/UserVM.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.vm; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.NonNull; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class UserVM { 14 | @NotNull 15 | @NonNull 16 | private String id; 17 | @NotNull 18 | @NonNull 19 | private String name; 20 | } 21 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/controller/vm/UsersStatisticVM.java: -------------------------------------------------------------------------------- 1 | package com.example.controller.vm; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class UsersStatisticVM { 11 | private UserVM mostActive; 12 | 13 | private UserVM mostMentioned; 14 | } 15 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/domain/Issue.java: -------------------------------------------------------------------------------- 1 | package com.example.domain; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.NonNull; 9 | 10 | import org.springframework.data.annotation.PersistenceConstructor; 11 | import org.springframework.data.mongodb.core.mapping.Document; 12 | 13 | @Data 14 | @Document(collection = "issues") 15 | @NoArgsConstructor 16 | @AllArgsConstructor(staticName = "of", onConstructor = @__(@PersistenceConstructor)) 17 | public class Issue implements Serializable { 18 | 19 | @NonNull 20 | private Long id; 21 | } 22 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/domain/Mention.java: -------------------------------------------------------------------------------- 1 | package com.example.domain; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.NonNull; 9 | 10 | import org.springframework.data.annotation.PersistenceConstructor; 11 | import org.springframework.data.mongodb.core.mapping.Document; 12 | 13 | @Data 14 | @Document(collection = "mentions") 15 | @NoArgsConstructor 16 | @AllArgsConstructor(staticName = "of", onConstructor = @__(@PersistenceConstructor)) 17 | public class Mention implements Serializable { 18 | 19 | @NonNull 20 | private String userId; 21 | 22 | @NonNull 23 | private String screenName; 24 | } 25 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/domain/Message.java: -------------------------------------------------------------------------------- 1 | package com.example.domain; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | import java.util.Set; 6 | 7 | import com.mongodb.annotations.Immutable; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Data; 11 | import lombok.NoArgsConstructor; 12 | import lombok.NonNull; 13 | 14 | import org.springframework.data.annotation.Id; 15 | import org.springframework.data.annotation.PersistenceConstructor; 16 | import org.springframework.data.mongodb.core.mapping.Document; 17 | 18 | @Data 19 | @Builder 20 | @Document 21 | @Immutable 22 | @NoArgsConstructor 23 | @AllArgsConstructor(staticName = "of", onConstructor = @__(@PersistenceConstructor)) 24 | public class Message implements Serializable { 25 | 26 | @Id 27 | @NonNull 28 | private String id; 29 | @NonNull 30 | private String text; 31 | @NonNull 32 | private String html; 33 | @NonNull 34 | private Date sent; 35 | @NonNull 36 | private User user; 37 | @NonNull 38 | private Boolean unread; 39 | @NonNull 40 | private Long readBy; 41 | @NonNull 42 | private String[] urls; 43 | @NonNull 44 | private Set mentions; 45 | @NonNull 46 | private Set issues; 47 | } 48 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.example.domain; 2 | 3 | import java.io.Serializable; 4 | 5 | import com.mongodb.annotations.Immutable; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.NonNull; 10 | 11 | import org.springframework.data.annotation.Id; 12 | import org.springframework.data.annotation.PersistenceConstructor; 13 | import org.springframework.data.mongodb.core.mapping.Document; 14 | 15 | @Data 16 | @Document(collection = "users") 17 | @Immutable 18 | @NoArgsConstructor 19 | @AllArgsConstructor(staticName = "of", onConstructor = @__(@PersistenceConstructor)) 20 | public class User implements Serializable { 21 | @Id 22 | @NonNull 23 | private String id; 24 | @NonNull 25 | private String name; 26 | @NonNull 27 | private String displayName; 28 | } 29 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/repository/MessageRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.repository; 2 | 3 | import com.example.domain.Message; 4 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface MessageRepository extends ReactiveMongoRepository { 9 | } 10 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.repository; 2 | 3 | import com.example.domain.User; 4 | 5 | import reactor.core.publisher.Mono; 6 | 7 | public interface UserRepository { 8 | 9 | Mono findMostActive(); 10 | 11 | Mono findMostPopular(); 12 | } 13 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/service/ChatService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import reactor.core.publisher.Flux; 4 | 5 | public interface ChatService { 6 | 7 | Flux getMessagesStream(); 8 | 9 | Flux getLatestMessages(); 10 | } 11 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/service/StatisticService.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import com.example.controller.vm.UsersStatisticVM; 4 | import com.example.service.gitter.dto.MessageResponse; 5 | import reactor.core.publisher.Flux; 6 | 7 | public interface StatisticService { 8 | 9 | Flux updateStatistic(Flux messages); 10 | } 11 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/service/gitter/GitterUriBuilder.java: -------------------------------------------------------------------------------- 1 | package com.example.service.gitter; 2 | 3 | 4 | import org.springframework.web.util.UriComponentsBuilder; 5 | 6 | public final class GitterUriBuilder { 7 | 8 | private GitterUriBuilder() { 9 | } 10 | 11 | public static UriComponentsBuilder from(GitterProperties.GenericProperties gitterProperties) { 12 | return UriComponentsBuilder.fromUri(gitterProperties.getEndpoint()) 13 | .pathSegment(gitterProperties.getVersion(), gitterProperties.getMessagesResource().toASCIIString()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/service/gitter/dto/Issue.java: -------------------------------------------------------------------------------- 1 | package com.example.service.gitter.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class Issue { 11 | private String number; 12 | } 13 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/service/gitter/dto/Mention.java: -------------------------------------------------------------------------------- 1 | package com.example.service.gitter.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.List; 8 | 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class Mention { 14 | private String screenName; 15 | private String userId; 16 | private List userIds; 17 | } 18 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/service/gitter/dto/MessageResponse.java: -------------------------------------------------------------------------------- 1 | package com.example.service.gitter.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.Date; 10 | import java.util.List; 11 | 12 | 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @EqualsAndHashCode(exclude = "meta") 17 | public class MessageResponse { 18 | private String id; 19 | private String text; 20 | private String html; 21 | private Date sent; 22 | private String editedAt; 23 | private UserResponse fromUser; 24 | 25 | @JsonProperty("unread") 26 | private Boolean unRead; 27 | private Long readBy; 28 | private List urls; 29 | private List mentions; 30 | private List issues; 31 | private List meta; 32 | 33 | @JsonProperty("v") 34 | private Integer version; 35 | } 36 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/service/gitter/dto/Meta.java: -------------------------------------------------------------------------------- 1 | package com.example.service.gitter.dto; 2 | 3 | public class Meta { 4 | } 5 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/service/gitter/dto/Role.java: -------------------------------------------------------------------------------- 1 | package com.example.service.gitter.dto; 2 | 3 | public enum Role { 4 | ADMIN, 5 | STANDARD 6 | } 7 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/service/gitter/dto/Url.java: -------------------------------------------------------------------------------- 1 | package com.example.service.gitter.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class Url { 12 | private String url; 13 | } 14 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/service/gitter/dto/UserResponse.java: -------------------------------------------------------------------------------- 1 | package com.example.service.gitter.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class UserResponse { 12 | private String id; 13 | private Integer v; 14 | private String username; 15 | private String displayName; 16 | private String avatarUrl; 17 | private String avatarUrlSmall; 18 | private String avatarUrlMedium; 19 | private Role role; 20 | private boolean staff; 21 | private String gv; 22 | private String url; 23 | } 24 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/java/com/example/service/impl/utils/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.example.service.impl.utils; 2 | 3 | import com.example.controller.vm.UserVM; 4 | import com.example.domain.User; 5 | 6 | public final class UserMapper { 7 | private UserMapper() { 8 | } 9 | 10 | public static UserVM toViewModelUnits(User domainUser) { 11 | if (domainUser == null) { 12 | return null; 13 | } 14 | 15 | return new UserVM(domainUser.getId(), domainUser.getName()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/resources/application-dev.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | thymeleaf: 3 | cache: false -------------------------------------------------------------------------------- /chapter-08/cloud-stream/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: gitter 3 | 4 | spring.cloud.stream: 5 | bindings: 6 | output: 7 | destination: Messages 8 | producer: 9 | requiredGroups: statistic, ui 10 | 11 | gitter: 12 | auth: 13 | token: ${GITTER_TOKEN} 14 | api: 15 | endpoint: https://api.gitter.im/ 16 | messages-resource: rooms/${gitter.auth.token}/chatMessages 17 | stream: 18 | endpoint: https://stream.gitter.im/ 19 | messages-resource: rooms/${gitter.auth.token}/chatMessages 20 | 21 | --- 22 | 23 | spring: 24 | profiles: statistic 25 | 26 | spring.cloud.stream: 27 | bindings: 28 | input: 29 | destination: Messages 30 | group: statistic 31 | output: 32 | producer: 33 | requiredGroups: ui 34 | destination: Statistic 35 | 36 | --- 37 | 38 | spring: 39 | profiles: ui 40 | 41 | spring.cloud.stream: 42 | bindings: 43 | messages: 44 | destination: Messages 45 | group: ui 46 | statistic: 47 | destination: Statistic 48 | group: ui -------------------------------------------------------------------------------- /chapter-08/dataflow/mongodb-processor/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.0.3.RELEASE' 4 | springBootThinLauncherVersion = '1.0.12.RELEASE' 5 | } 6 | repositories { 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 11 | // classpath("org.springframework.boot.experimental:spring-boot-thin-gradle-plugin:${springBootThinLauncherVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'io.spring.dependency-management' 18 | apply plugin: 'org.springframework.boot' 19 | //apply plugin: 'org.springframework.boot.experimental.thin-launcher' 20 | 21 | group = 'org.rpis5.chapters.chapter_08.dataflow' 22 | version = '0.0.1-SNAPSHOT' 23 | sourceCompatibility = 1.8 24 | 25 | repositories { 26 | mavenCentral() 27 | } 28 | 29 | 30 | ext { 31 | springCloudVersion = 'Finchley.RELEASE' 32 | } 33 | 34 | dependencies { 35 | compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive') 36 | compile('org.springframework.cloud:spring-cloud-stream-reactive') 37 | 38 | annotationProcessor('org.springframework.boot:spring-boot-configuration-processor') 39 | 40 | testCompile('org.springframework.integration:spring-integration-mongodb') 41 | testCompile('org.springframework.boot:spring-boot-starter-test') 42 | testCompile('de.flapdoodle.embed:de.flapdoodle.embed.mongo') 43 | testCompile('org.springframework.cloud:spring-cloud-stream-test-support') 44 | } 45 | 46 | dependencyManagement { 47 | imports { 48 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /chapter-08/dataflow/mongodb-processor/src/main/java/org/rpis5/chapters/chapter_08/dataflow/mongodb/processor/MongodbProcessorApplication.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_08.dataflow.mongodb.processor; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MongodbProcessorApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MongodbProcessorConfiguration.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-08/dataflow/mongodb-processor/src/main/resources/META-INF/spring-configuration-metadata-whitelist.properties: -------------------------------------------------------------------------------- 1 | configuration-properties.classes=org.rpis5.chapters.chapter_08.dataflow.mongodb.processor.MongodbProcessorProperties, \ 2 | org.springframework.boot.autoconfigure.mongo.MongoProperties 3 | 4 | -------------------------------------------------------------------------------- /chapter-08/dataflow/mongodb-processor/src/main/resources/META-INF/spring.provides: -------------------------------------------------------------------------------- 1 | provides: spring-cloud-starter-stream-processor-mongodb 2 | -------------------------------------------------------------------------------- /chapter-08/dataflow/mongodb-processor/src/main/resources/META-INF/thin.properties: -------------------------------------------------------------------------------- 1 | boms.spring-cloud-dependencies: org.springframework.cloud:spring-cloud-dependencies:Finchley.RELEASE 2 | dependencies.spring-integration-mongodb: org.springframework.integration:spring-integration-mongodb 3 | dependencies.spring-boot-starter-data-mongodb-reactive: org.springframework.boot:spring-boot-starter-data-mongodb-reactive 4 | dependencies.spring-cloud-stream-rabbit: org.springframework.cloud:spring-cloud-starter-stream-rabbit 5 | dependencies.spring-cloud-stream-reactive: org.springframework.cloud:spring-cloud-stream-reactive -------------------------------------------------------------------------------- /chapter-08/dataflow/mongodb-processor/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | application.name: ${vcap.application.name:mongodb-sink} 3 | autoconfigure.exclude: org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration 4 | 5 | info.app: 6 | name: mongodb-sink-rabbit 7 | description: Spring Cloud Stream Mongodb Processor Rabbit Binder Application 8 | version: 1.0.0.BUILD-SNAPSHOT 9 | 10 | management.endpoints.web.exposure.include: health,info,bindings 11 | mongodb: 12 | collection: test 13 | -------------------------------------------------------------------------------- /chapter-09/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.0.M4' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | mavenCentral() 24 | maven { url "https://repo.spring.io/snapshot" } 25 | maven { url "https://repo.spring.io/milestone" } 26 | } 27 | 28 | dependencies { 29 | compile('org.springframework.boot:spring-boot-autoconfigure') 30 | compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive') 31 | compile('org.springframework.boot:spring-boot-starter-security') 32 | compile('org.springframework.boot:spring-boot-starter-webflux') 33 | compile('de.flapdoodle.embed:de.flapdoodle.embed.mongo') 34 | 35 | compileOnly('org.projectlombok:lombok') 36 | 37 | testCompile('org.springframework.boot:spring-boot-starter-test') 38 | testCompile('io.projectreactor:reactor-test') 39 | testCompile('org.springframework.security:spring-security-test') 40 | } 41 | -------------------------------------------------------------------------------- /chapter-09/src/main/java/org/rpis5/chapters/chapter_09/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_09; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-09/src/main/java/org/rpis5/chapters/chapter_09/Payment.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_09; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | 9 | @Document 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class Payment { 14 | @Id String id; 15 | String user; 16 | 17 | public Payment withUser(String user) { 18 | return this.user.equals(user) ? this : new Payment(this.id, user); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter-09/src/main/java/org/rpis5/chapters/chapter_09/PaymentController.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_09; 2 | 3 | import reactor.core.publisher.Flux; 4 | import reactor.core.publisher.Mono; 5 | 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | @RequestMapping("/payments") 13 | public class PaymentController { 14 | 15 | private final PaymentService paymentService; 16 | 17 | public PaymentController(PaymentService service) { 18 | paymentService = service; 19 | } 20 | 21 | @GetMapping("") 22 | public Flux list() { 23 | return paymentService.list(); 24 | } 25 | 26 | @PostMapping("") 27 | public Mono send(Mono payment) { 28 | return paymentService.send(payment); 29 | } 30 | } -------------------------------------------------------------------------------- /chapter-09/src/main/java/org/rpis5/chapters/chapter_09/PaymentRepository.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_09; 2 | 3 | import reactor.core.publisher.Flux; 4 | 5 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 6 | 7 | public interface PaymentRepository extends ReactiveMongoRepository { 8 | 9 | Flux findAllByUser(String user); 10 | } 11 | -------------------------------------------------------------------------------- /chapter-09/src/main/java/org/rpis5/chapters/chapter_09/PaymentService.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_09; 2 | 3 | import reactor.core.publisher.Flux; 4 | import reactor.core.publisher.Mono; 5 | 6 | public interface PaymentService { 7 | 8 | Flux list(); 9 | 10 | Mono send(Mono payment); 11 | } 12 | -------------------------------------------------------------------------------- /chapter-09/src/main/java/org/rpis5/chapters/chapter_09/RootController.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) Zoomdata, Inc. 2012-2018. All rights reserved. 3 | */ 4 | package org.rpis5.chapters.chapter_09; 5 | 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("/") 13 | public class RootController { 14 | 15 | @GetMapping("") 16 | public Flux hello() { 17 | return Flux.just("Go to http://localhost:8080/payments"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter-09/src/test/java/org/rpis5/chapters/chapter_09/TestSecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_09; 2 | 3 | import org.springframework.boot.test.context.TestConfiguration; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Primary; 6 | import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; 7 | import org.springframework.security.config.web.server.ServerHttpSecurity; 8 | import org.springframework.security.web.server.SecurityWebFilterChain; 9 | 10 | @TestConfiguration 11 | @EnableReactiveMethodSecurity 12 | public class TestSecurityConfiguration { 13 | 14 | @Bean 15 | @Primary 16 | public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { 17 | return http 18 | .authorizeExchange() 19 | .anyExchange().permitAll() 20 | .and() 21 | .formLogin() 22 | .and() 23 | .csrf().disable() 24 | .build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-09/src/test/java/org/rpis5/chapters/chapter_09/TestWebClientBuilderConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_09; 2 | 3 | import org.springframework.boot.test.context.TestConfiguration; 4 | import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.web.reactive.function.client.ExchangeFunction; 7 | 8 | @TestConfiguration 9 | public class TestWebClientBuilderConfiguration { 10 | 11 | @Bean 12 | public WebClientCustomizer testWebClientCustomizer(ExchangeFunction exchangeFunction) { 13 | return builder -> builder.exchangeFunction(exchangeFunction); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter-09/src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | main: 3 | allow-bean-definition-overriding: true -------------------------------------------------------------------------------- /chapter-10/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 10: And Finally, Release it! 2 | 3 | This sample depicts a reactive application based on Spring Boot 2 and WebFlux with all required infrastructure for operational monitoring. 4 | 5 | Metrics are pulled by Prometheus, Grafana has a simple dashboard with app metrics, Zipkin gathers traces. 6 | 7 | ## Application Structure 8 | 9 | - Application itself: 10 | - Reactive Web App (with Spring Boot Admin) 11 | - Database: MongoDB (is not used) 12 | - Monitoring infrastructure: 13 | - Prometheus 14 | - Grafana 15 | - Zipkin 16 | 17 | ## Start or Stop Infrastructural Services 18 | 19 | To start services run the following command: 20 | 21 | ```bash 22 | docker-compose -f docker/docker-compose.yml up -d 23 | ``` 24 | 25 | To stop services run the following command: 26 | 27 | ```bash 28 | docker-compose -f docker/docker-compose.yml down 29 | ``` 30 | 31 | ## Start Spring Boot Application 32 | 33 | To start the application, run in your favorite IDE class 34 | `org.rpis5.chapters.chapter_10.Chapter10CloudReadyApplication`. 35 | 36 | ## Accessing Application Components 37 | 38 | - Reactive Web Application: 39 | - Spring Boot Admin 2.0: 40 | - Prometheus: 41 | - Grafana: (user: `admin`, password: `admin`) 42 | - Zipkin: 43 | 44 | -------------------------------------------------------------------------------- /chapter-10/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | prometheus: 5 | hostname: prometheus 6 | image: prom/prometheus:v2.3.0 7 | ports: 8 | - 9090:9090 9 | mem_limit: 128M 10 | volumes: 11 | - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml 12 | 13 | grafana: 14 | hostname: grafana 15 | image: grafana/grafana:5.1.3 16 | ports: 17 | - 3000:3000 18 | mem_limit: 256M 19 | volumes: 20 | - ./grafana/config.ini:/etc/grafana/config.ini 21 | - ./grafana/provisioning:/etc/grafana/provisioning 22 | - ./grafana/dashboards:/var/lib/grafana/dashboards 23 | depends_on: 24 | - prometheus 25 | 26 | zipkin: 27 | image: openzipkin/zipkin 28 | environment: 29 | - STORAGE_TYPE=mem 30 | ports: 31 | - 9411:9411 32 | mem_limit: 768M 33 | 34 | -------------------------------------------------------------------------------- /chapter-10/docker/grafana/config.ini: -------------------------------------------------------------------------------- 1 | [paths] 2 | provisioning = /etc/grafana/provisioning 3 | 4 | [server] 5 | enable_gzip = true -------------------------------------------------------------------------------- /chapter-10/docker/grafana/provisioning/dashboards/all.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'default' 5 | orgId: 1 6 | folder: '' 7 | type: file 8 | options: 9 | path: /var/lib/grafana/dashboards -------------------------------------------------------------------------------- /chapter-10/docker/grafana/provisioning/datasources/all.yml: -------------------------------------------------------------------------------- 1 | datasources: 2 | - name: 'DS_PROMETHEUS' 3 | type: 'prometheus' 4 | access: 'proxy' 5 | org_id: 1 6 | url: 'http://prometheus:9090' 7 | is_default: true 8 | version: 1 9 | editable: true -------------------------------------------------------------------------------- /chapter-10/docker/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 5s 3 | 4 | # Monitored services (Prometheus, Spring Boot App) 5 | scrape_configs: 6 | - job_name: 'prometheus' 7 | static_configs: 8 | - targets: ['localhost:9090'] 9 | - job_name: 'reactive_app' 10 | static_configs: 11 | # May not work on older Docker Engine or Linux, use external IP, resolvable from a container 12 | - targets: ['host.docker.internal:8090'] 13 | metrics_path: "/actuator/prometheus/" 14 | -------------------------------------------------------------------------------- /chapter-10/src/main/java/org/rpis5/chapters/chapter_10/Chapter10CloudReadyApplication.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_10; 2 | 3 | import de.codecentric.boot.admin.server.config.EnableAdminServer; 4 | import io.micrometer.core.instrument.MeterRegistry; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.rpis5.chapters.chapter_10.scheduler.MeteredSchedulersFactory; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import reactor.core.publisher.Hooks; 11 | import reactor.core.scheduler.Schedulers; 12 | 13 | import javax.annotation.PostConstruct; 14 | 15 | @Slf4j 16 | @RequiredArgsConstructor 17 | @EnableAdminServer 18 | @SpringBootApplication 19 | public class Chapter10CloudReadyApplication { 20 | private final MeterRegistry meterRegistry; 21 | 22 | public static void main(String[] args) { 23 | SpringApplication.run(Chapter10CloudReadyApplication.class, args); 24 | } 25 | 26 | @PostConstruct 27 | public void init() { 28 | Hooks.onNextDropped(c -> meterRegistry.counter("reactor.dropped.events").increment()); 29 | Schedulers.setFactory(new MeteredSchedulersFactory(meterRegistry)); 30 | log.info("Updated Scheduler factory with a custom instance"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chapter-10/src/main/java/org/rpis5/chapters/chapter_10/acturator/AppModeInfoProvider.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_10.acturator; 2 | 3 | import org.springframework.boot.actuate.info.Info; 4 | import org.springframework.boot.actuate.info.InfoContributor; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.Random; 8 | 9 | @Component 10 | public class AppModeInfoProvider implements InfoContributor { 11 | private final Random rnd = new Random(); 12 | 13 | @Override 14 | public void contribute(Info.Builder builder) { 15 | boolean appMode = rnd.nextBoolean(); 16 | builder 17 | .withDetail("application-mode", appMode ? "experimental" : "stable"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter-10/src/main/java/org/rpis5/chapters/chapter_10/acturator/SensorBatteryHealthIndicator.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_10.acturator; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.rpis5.chapters.chapter_10.service.TemperatureSensor; 5 | import org.springframework.boot.actuate.health.Health; 6 | import org.springframework.boot.actuate.health.ReactiveHealthIndicator; 7 | import org.springframework.boot.actuate.health.Status; 8 | import org.springframework.stereotype.Component; 9 | import reactor.core.publisher.Mono; 10 | 11 | @RequiredArgsConstructor 12 | @Component 13 | class SensorBatteryHealthIndicator implements ReactiveHealthIndicator { 14 | private final TemperatureSensor temperatureSensor; 15 | 16 | @Override 17 | public Mono health() { 18 | return temperatureSensor 19 | .batteryLevel() 20 | .map(level -> { 21 | if (level > 40) { 22 | return new Health.Builder() 23 | .up() 24 | .withDetail("level", level) 25 | .build(); 26 | } else { 27 | return new Health.Builder() 28 | .status(new Status("Low Battery")) 29 | .withDetail("level", level) 30 | .build(); 31 | } 32 | }).onErrorResume(err -> Mono. 33 | just(new Health.Builder() 34 | .outOfService() 35 | .withDetail("error", err.getMessage()) 36 | .build()) 37 | ); 38 | } 39 | } -------------------------------------------------------------------------------- /chapter-10/src/main/java/org/rpis5/chapters/chapter_10/controller/ProxyController.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_10.controller; 2 | 3 | import io.micrometer.core.instrument.MeterRegistry; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import org.springframework.web.reactive.function.client.WebClient; 11 | import reactor.core.publisher.Mono; 12 | 13 | @Slf4j 14 | @RestController 15 | @RequiredArgsConstructor 16 | public class ProxyController { 17 | private final MeterRegistry registry; 18 | private final WebClient.Builder webClientBuilder; 19 | 20 | @GetMapping(path = "/proxy") 21 | public Mono> proxyRequest(@RequestParam(name = "q") String uri) { 22 | String targetUri = "https://" + uri; 23 | 24 | return webClientBuilder 25 | .build() 26 | .get() 27 | .uri(targetUri) 28 | .exchange() 29 | .name("proxy.request") 30 | .metrics() 31 | .doOnNext(v -> log.info("Proxying for: {}", targetUri)) 32 | .flatMap(cr -> cr.toEntity(String.class)) 33 | .doOnTerminate(() -> registry 34 | .counter("proxy.request", "uri", targetUri) 35 | .increment()); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /chapter-10/src/main/java/org/rpis5/chapters/chapter_10/controller/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_10.controller; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.server.WebFilter; 6 | 7 | @Configuration 8 | public class WebConfiguration { 9 | 10 | @Bean 11 | public WebFilter indexHtmlFilter() { 12 | return (exchange, chain) -> { 13 | if (exchange.getRequest().getURI().getPath().equals("/")) { 14 | return chain.filter(exchange 15 | .mutate() 16 | .request(exchange 17 | .getRequest() 18 | .mutate() 19 | .path("/index.html") 20 | .build()) 21 | .build()); 22 | } 23 | return chain.filter(exchange); 24 | }; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /chapter-10/src/main/java/org/rpis5/chapters/chapter_10/scheduler/MeteredScheduledThreadPoolExecutorMinimal.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_10.scheduler; 2 | 3 | import io.micrometer.core.instrument.MeterRegistry; 4 | 5 | import java.util.concurrent.ScheduledThreadPoolExecutor; 6 | 7 | /** 8 | * Metrics friendly {@link ScheduledThreadPoolExecutor} extension. 9 | */ 10 | @SuppressWarnings("unused") 11 | public class MeteredScheduledThreadPoolExecutorMinimal extends ScheduledThreadPoolExecutor { 12 | 13 | public MeteredScheduledThreadPoolExecutorMinimal( 14 | int corePoolSize, 15 | MeterRegistry registry 16 | ) { 17 | super(corePoolSize); 18 | 19 | registry.gauge("pool.size", this.getCorePoolSize()); 20 | registry.gauge("pool.active.tasks", this.getActiveCount()); 21 | registry.gauge("pool.queue.size", getQueue().size()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter-10/src/main/java/org/rpis5/chapters/chapter_10/service/Temperature.java: -------------------------------------------------------------------------------- 1 | package org.rpis5.chapters.chapter_10.service; 2 | 3 | import lombok.Value; 4 | 5 | @Value 6 | public class Temperature { 7 | private final double value; 8 | } 9 | -------------------------------------------------------------------------------- /chapter-10/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | management: 5 | server: 6 | port: 8090 7 | endpoints: 8 | web: 9 | exposure.include: "*" 10 | endpoint: 11 | health: 12 | show-details: "always" 13 | cache: 14 | time-to-live: 10s 15 | 16 | logging: 17 | file: cloud-ready-reactive-app.log 18 | 19 | spring: 20 | application: 21 | name: "Cloud Ready Reactive Application" 22 | boot: 23 | admin: 24 | context-path: "/admin" 25 | client: 26 | url: "http://localhost:${management.server.port}/admin" 27 | 28 | # Information about the application 29 | info: 30 | name: "Reactive Spring App" 31 | mode: "testing" 32 | authors: "oleg & igor" 33 | service-version: "2.0" 34 | features: 35 | feature-a: "enabled" 36 | feature-b: "disabled" 37 | 38 | # To disable Info endpoint 39 | #management.endpoint.info.enabled: false 40 | 41 | # Distributed tracing 42 | spring.sleuth.sampler.probability: 1.0 43 | # spring.zipkin.baseUrl: "http://localhost:9411" 44 | 45 | 46 | -------------------------------------------------------------------------------- /doc/img/book-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/doc/img/book-title.png -------------------------------------------------------------------------------- /doc/img/ch10-grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/doc/img/ch10-grafana.png -------------------------------------------------------------------------------- /doc/img/ch10-spring-boot-admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/doc/img/ch10-spring-boot-admin.png -------------------------------------------------------------------------------- /doc/img/ch10-web-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/doc/img/ch10-web-page.png -------------------------------------------------------------------------------- /doc/img/ch10-zipkin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/doc/img/ch10-zipkin.png -------------------------------------------------------------------------------- /doc/img/idea-annotation-processing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/doc/img/idea-annotation-processing.png -------------------------------------------------------------------------------- /doc/img/idea-lombok-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/doc/img/idea-lombok-plugin.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Reactive-Programming-in-Spring-5/8ca99ca69358ba74e003675b6f17a752e3846584/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jun 06 00:21:40 EEST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'reactive-programming-in-spring-5.0' 2 | 3 | include "chapter-01" 4 | 5 | include "chapter-02" 6 | 7 | include "chapter-03/news-service" 8 | include "chapter-03/jdk9" 9 | include "chapter-03/vert.x" 10 | include "chapter-03/conversion_problem" 11 | include "chapter-03/push.vs.pull" 12 | include "chapter-03/rxjava-reactivestreams-ratpack" 13 | include "chapter-03/async.vs.reactive" 14 | 15 | include "chapter-04" 16 | 17 | include "chapter-05" 18 | 19 | include "chapter-06/samples" 20 | include "chapter-06/functional-spring-boot" 21 | include "chapter-06/security-samples" 22 | 23 | include "chapter-07/section-01-jdbc" 24 | include "chapter-07/section-02-jpa" 25 | include "chapter-07/section-03-mongo" 26 | include "chapter-07/section-04-rx-mongo" 27 | include "chapter-07/section-05-rx-mongo-tx" 28 | include "chapter-07/section-06-r2dbc" 29 | include "chapter-07/section-07-rx-webflux" 30 | include "chapter-07/section-08-rx-dbs" 31 | include "chapter-07/section-09-rx-sync" 32 | include "chapter-07/section-10-rxjava2-jdbc" 33 | 34 | include "chapter-08/dataflow/mongodb-processor" 35 | include "chapter-08/cloud-stream" 36 | include "chapter-08/cloud-function" 37 | 38 | include "chapter-09" 39 | 40 | include "chapter-10" 41 | --------------------------------------------------------------------------------