├── .gitignore ├── .travis.yml ├── LICENSE ├── PROJECT_LAYOUT.md ├── README.md ├── audit-service ├── README.md ├── pom.xml └── src │ ├── kubernetes │ └── database-secret.yaml │ └── main │ ├── fabric8 │ └── deployment.yml │ ├── java │ └── io │ │ └── vertx │ │ └── workshop │ │ └── audit │ │ └── impl │ │ └── AuditVerticle.java │ └── solution │ └── io │ └── vertx │ └── workshop │ └── audit │ └── impl │ └── AuditVerticle.java ├── compulsive-traders ├── README.md ├── pom.xml └── src │ └── main │ ├── fabric8 │ └── deployment.yml │ ├── java │ └── io │ │ └── vertx │ │ └── workshop │ │ └── trader │ │ └── impl │ │ ├── CallbackTraderVerticle.java │ │ ├── MainVerticle.java │ │ ├── RXCompulsiveTraderVerticle.java │ │ ├── RXEventBusService.java │ │ └── TraderUtils.java │ ├── kotlin │ └── io │ │ └── vertx │ │ └── workshop │ │ └── trader │ │ └── impl │ │ └── KotlinCompulsiveTraderVerticle.kt │ └── solution │ └── io │ └── vertx │ └── workshop │ └── trader │ └── impl │ ├── CallbackTraderVerticle.java │ ├── MainVerticle.java │ ├── RXCompulsiveTraderVerticle.java │ ├── RXEventBusService.java │ └── TraderUtils.java ├── currency-3rdparty-service ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── vertx │ └── workshop │ └── currency │ └── CurrencyService.java ├── currency-service ├── README.md ├── pom.xml └── src │ └── main │ ├── fabric8 │ └── deployment.yml │ ├── java │ └── io │ │ └── vertx │ │ └── workshop │ │ └── currency │ │ ├── CurrencyServiceProxy.java │ │ └── Helpers.java │ └── solution │ └── io │ └── vertx │ └── workshop │ └── currency │ ├── CurrencyServiceProxy.java │ └── Helpers.java ├── docs ├── README.md ├── assets │ ├── css │ │ ├── asciidoctor.css │ │ ├── bootstrap.css │ │ ├── font-awesome.min.css │ │ └── hol.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── js │ │ ├── bootstrap.js │ │ └── jquery-3.1.1.js ├── build.sh ├── chapters │ ├── 1-preface.adoc │ ├── 10-currency.adoc │ ├── 1_1-prerequisites.adoc │ ├── 2-vertx.adoc │ ├── 3-microservices.adoc │ ├── 4-kubernetes.adoc │ ├── 5-application.adoc │ ├── 6-quote-generator.adoc │ ├── 7-portfolio.adoc │ ├── 8-traders.adoc │ ├── 9-audit.adoc │ ├── conclusion.adoc │ └── references.adoc ├── docinfo.html ├── generate.sh ├── images │ ├── async-rpc-sequence.png │ ├── async-rpc-sequence.png.cache │ ├── blocked-event-loop.png │ ├── circuit-breaker-states.png │ ├── circuit-breaker.png │ ├── dashboard-initial-state.png │ ├── dashboard.png │ ├── database-sequence.png │ ├── diagram-sequence.png │ ├── diagram-sequence.png.cache │ ├── diagram-sequences.png │ ├── diagram-sequences.png.cache │ ├── event-loop.png │ ├── images.pptx │ ├── openshift-architecture.png │ ├── openshift-build-process.png │ ├── openshift-entities.png │ ├── openshift-first-deployment.png │ ├── openshift-service.png │ ├── portfolio-sequence.png │ ├── portfolio-sequence.png.cache │ ├── proxy-sequence-timeout.png │ ├── proxy-sequence.png │ ├── quote-openshift.png │ ├── rpc-sequence.png │ ├── rpc-sequence.png.cache │ ├── workshop-application.png │ ├── workshop-che-screenshot1.png │ ├── workshop-che-screenshot2.png │ ├── workshop-che-screenshot3.png │ ├── workshop-che-screenshot4.png │ ├── workshop-che-screenshot5.png │ ├── workshop-che-screenshot6.png │ └── workshop-che-screenshot7.png └── index.adoc ├── micro-trader-dashboard ├── README.md ├── pom.xml └── src │ └── main │ ├── fabric8 │ └── deployment.yml │ └── java │ └── io │ └── vertx │ └── workshop │ └── dashboard │ └── DashboardVerticle.java ├── microservices-exercises ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── vertx │ └── workshop │ └── exercise │ └── microservice │ ├── Exercise1Verticle.java │ ├── Exercise2Verticle.java │ ├── Exercise3Verticle.java │ └── Main.java ├── pom.xml ├── portfolio-service ├── README.md ├── pom.xml └── src │ ├── main │ ├── asciidoc │ │ └── dataobjects.adoc │ ├── fabric8 │ │ └── deployment.yml │ ├── generated │ │ ├── io │ │ │ └── vertx │ │ │ │ └── workshop │ │ │ │ └── portfolio │ │ │ │ ├── PortfolioConverter.java │ │ │ │ ├── PortfolioServiceVertxEBProxy.java │ │ │ │ ├── PortfolioServiceVertxProxyHandler.java │ │ │ │ └── reactivex │ │ │ │ └── PortfolioService.java │ │ └── vertx-workshop-portfolio-js │ │ │ ├── portfolio_service-proxy.d.ts │ │ │ ├── portfolio_service-proxy.js │ │ │ └── portfolio_service.js │ ├── java │ │ └── io │ │ │ └── vertx │ │ │ └── workshop │ │ │ └── portfolio │ │ │ ├── Portfolio.java │ │ │ ├── PortfolioService.java │ │ │ ├── impl │ │ │ ├── PortfolioServiceImpl.java │ │ │ └── PortfolioVerticle.java │ │ │ └── package-info.java │ ├── kotlin │ │ └── io │ │ │ └── vertx │ │ │ └── workshop │ │ │ └── portfolio │ │ │ └── kotlin │ │ │ └── Portfolio.kt │ └── solution │ │ └── io │ │ └── vertx │ │ └── workshop │ │ └── portfolio │ │ ├── Portfolio.java │ │ ├── PortfolioService.java │ │ ├── impl │ │ ├── PortfolioServiceImpl.java │ │ └── PortfolioVerticle.java │ │ └── package-info.java │ └── test │ └── java │ └── io │ └── vertx │ └── workshop │ └── portfolio │ └── impl │ ├── PortfolioServiceImplTest.java │ └── PortfolioVerticleTest.java ├── quote-generator ├── README.md ├── pom.xml └── src │ ├── kubernetes │ └── config.json │ ├── main │ ├── fabric8 │ │ └── deployment.yml │ ├── java │ │ └── io │ │ │ └── vertx │ │ │ └── workshop │ │ │ └── quote │ │ │ ├── GeneratorConfigVerticle.java │ │ │ ├── MarketDataVerticle.java │ │ │ └── RestQuoteAPIVerticle.java │ └── solution │ │ └── io │ │ └── vertx │ │ └── workshop │ │ └── quote │ │ ├── GeneratorConfigVerticle.java │ │ ├── MarketDataVerticle.java │ │ └── RestQuoteAPIVerticle.java │ └── test │ ├── java │ └── io │ │ └── vertx │ │ └── workshop │ │ └── quote │ │ ├── GeneratorConfigVerticleTest.java │ │ └── MarketDataVerticleTest.java │ └── resources │ └── config │ └── config.json ├── scripts ├── cleanup-project.sh ├── create-project.sh └── deploy-all.sh └── vertx-exercises ├── pom.xml └── src └── main └── java └── io └── vertx └── workshop └── exercise ├── Exercise1.java ├── Exercise2.java ├── Exercise2Verticle.java ├── Exercise4.java ├── Exercise4ReceiverVerticle.java ├── Exercise4SenderVerticle.java ├── Exercise5.java ├── Exercise5HttpVerticle.java ├── Exercise5ProcessorVerticle.java ├── Exercise6.java └── Exercise6HttpVerticle.java /.gitignore: -------------------------------------------------------------------------------- 1 | ### Asciidoctor output 2 | /docs/output 3 | /docs-zh-cn/output 4 | 5 | ### Maven template 6 | ======= 7 | target/ 8 | pom.xml.tag 9 | pom.xml.releaseBackup 10 | pom.xml.versionsBackup 11 | pom.xml.next 12 | release.properties 13 | dependency-reduced-pom.xml 14 | buildNumber.properties 15 | .mvn/timing.properties 16 | 17 | ### Intellij IDEA 18 | .idea 19 | *.iml 20 | 21 | ### Eclipse template 22 | .metadata 23 | bin/ 24 | tmp/ 25 | *.tmp 26 | *.bak 27 | *.swp 28 | *~.nib 29 | local.properties 30 | .settings/ 31 | .loadpath 32 | .recommenders 33 | 34 | # Eclipse Core 35 | .project 36 | 37 | # External tool builders 38 | .externalToolBuilders/ 39 | 40 | # Locally stored "Eclipse launch configurations" 41 | *.launch 42 | 43 | # PyDev specific (Python IDE for Eclipse) 44 | *.pydevproject 45 | 46 | # CDT-specific (C/C++ Development Tooling) 47 | .cproject 48 | 49 | # JDT-specific (Eclipse Java Development Tools) 50 | .classpath 51 | 52 | # Java annotation processor (APT) 53 | .factorypath 54 | 55 | # PDT-specific (PHP Development Tools) 56 | .buildpath 57 | 58 | # sbteclipse plugin 59 | .target 60 | 61 | # Tern plugin 62 | .tern-project 63 | 64 | # TeXlipse plugin 65 | .texlipse 66 | 67 | # STS (Spring Tool Suite) 68 | .springBeans 69 | 70 | # Code Recommenders 71 | .recommenders/ 72 | ### JetBrains template 73 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 74 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 75 | 76 | # User-specific stuff: 77 | .idea/workspace.xml 78 | .idea/tasks.xml 79 | .idea/dictionaries 80 | .idea/vcs.xml 81 | .idea/jsLibraryMappings.xml 82 | 83 | # Sensitive or high-churn files: 84 | .idea/dataSources.ids 85 | .idea/dataSources.xml 86 | .idea/dataSources.local.xml 87 | .idea/sqlDataSources.xml 88 | .idea/dynamic.xml 89 | .idea/uiDesigner.xml 90 | 91 | # Gradle: 92 | .idea/gradle.xml 93 | .idea/libraries 94 | 95 | # Mongo Explorer plugin: 96 | .idea/mongoSettings.xml 97 | 98 | ## File-based project format: 99 | *.iws 100 | 101 | ## Plugin-specific files: 102 | 103 | # IntelliJ 104 | /out/ 105 | 106 | # mpeltonen/sbt-idea plugin 107 | .idea_modules/ 108 | 109 | # JIRA plugin 110 | atlassian-ide-plugin.xml 111 | 112 | # Crashlytics plugin (for Android Studio and IntelliJ) 113 | com_crashlytics_export_strings.xml 114 | crashlytics.properties 115 | crashlytics-build.properties 116 | fabric.properties 117 | ### NetBeans template 118 | nbproject/private/ 119 | build/ 120 | nbbuild/ 121 | nbdist/ 122 | nbactions.xml 123 | .nb-gradle/ 124 | ### Java template 125 | *.class 126 | 127 | # Mobile Tools for Java (J2ME) 128 | .mtj.tmp/ 129 | 130 | # Package Files # 131 | *.jar 132 | *.war 133 | *.ear 134 | 135 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 136 | hs_err_pid* 137 | # Created by .ignore support plugin (hsz.mobi) 138 | 139 | #vertx 140 | .vertx 141 | 142 | # Asciidctor plugin 143 | .asciidoctor 144 | docs/chapters/async-rpc-sequence.png 145 | docs/chapters/portfolio-sequence.png 146 | docs/chapters/rpc-sequence.png 147 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | 5 | install: true 6 | 7 | script: 8 | - mvn clean install test -Ptravis 9 | - mvn clean install test -Ptravis,solution 10 | -------------------------------------------------------------------------------- /PROJECT_LAYOUT.md: -------------------------------------------------------------------------------- 1 | # Project Layout 2 | 3 | * User sources are in `src/main/java` 4 | * Solution sources are in `src/main/solution` 5 | * Resources are in `src/main/resources` 6 | * Fabric8 customization are in `src/main/fabric8` 7 | * Tests are in `src/test/java` 8 | 9 | The solutions are enabled using `-Psolution` in the command line. 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vert.x & Kubernetes - From zero to (micro-) hero. 2 | 3 | This repository is a lab about vert.x explaining how to build distributed _microservice_ reactive applications using 4 | Eclipse Vert.x and deploy them on Kubernetes 5 | 6 | Instructions are available on http://escoffier.me/vertx-kubernetes 7 | 8 | Complete code is available in the `solution` directory. 9 | 10 | [![Build Status](https://travis-ci.org/jbossdemocentral/vertx-kubernetes-workshop.svg?branch=master)](https://travis-ci.org/jbossdemocentral/vertx-kubernetes-workshop) 11 | 12 | ## Teasing 13 | 14 | When coming to microservices, event-driven, asynchronous and reactive are quickly mentioned to implement them right. It avoids building _distributed_ monolith. In addition, in order to keep everything on track, you need a way to package and manage them. OpenShift is a container platform, based on Kubernetes, able to build, deploy, manage and update your microservices. 15 | 16 | Eclipse Vert.x is a toolkit to create reactive distributed applications running on the top of the Java Virtual Machine. Vert.x exhibits very good performances, and a very simple and small API based on the asynchronous, non-blocking development model. With vert.x, you can developed microservices in Java, but also in JavaScript, Groovy, Ruby and Ceylon. Vert.x also lets you interact with Node.JS, .NET or C applications. Vert.x is a container-native runtime taking care of the efficient usage of your CPU and memory granted to your container. 17 | 18 | This lab is an introduction to microservice development using Vert.x and OpenShift. The application is a fake _trading_ application, and maybe you are going to become (virtually) rich! The applications is a federation of interacting microservices running on OpenShift. 19 | 20 | ## Want to improve this lab ? 21 | 22 | Forks and PRs are definitely welcome ! 23 | 24 | ## Building 25 | 26 | To build the code: 27 | 28 | mvn clean install 29 | 30 | To build the documentation: 31 | 32 | cd docs 33 | docker run -it -v `pwd`:/documents/ asciidoctor/docker-asciidoctor "./build.sh" "html" 34 | # or for fish 35 | docker run -it -v (pwd):/documents/ asciidoctor/docker-asciidoctor "./build.sh" "html" 36 | -------------------------------------------------------------------------------- /audit-service/README.md: -------------------------------------------------------------------------------- 1 | # Audit service 2 | 3 | The audit service receives operation (shares bought or sold) from the event bus and store them in a database. It also 4 | provides a REST endpoint to retrieve the last 10 operations. 5 | 6 | ## Build 7 | 8 | ``` 9 | mvn clean package 10 | ``` 11 | 12 | ## Deploy 13 | 14 | ``` 15 | mvn fabric8:deploy 16 | ``` -------------------------------------------------------------------------------- /audit-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.vertx.workshop 9 | vertx-kubernetes-workshop 10 | 1.0-SNAPSHOT 11 | 12 | 13 | audit-service 14 | 15 | 16 | io.vertx.workshop.audit.impl.AuditVerticle 17 | 18 | 19 | 20 | 21 | io.vertx.workshop 22 | portfolio-service 23 | ${project.version} 24 | client 25 | 26 | 27 | io.vertx 28 | vertx-infinispan 29 | 30 | 31 | org.infinispan 32 | infinispan-cloud 33 | 34 | 35 | io.vertx 36 | vertx-jdbc-client 37 | 38 | 39 | org.postgresql 40 | postgresql 41 | 9.4.1212 42 | 43 | 44 | 45 | 46 | 47 | 48 | io.reactiverse 49 | vertx-maven-plugin 50 | 51 | 52 | io.fabric8 53 | fabric8-maven-plugin 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /audit-service/src/kubernetes/database-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "v1" 2 | kind: "Secret" 3 | metadata: 4 | name: "audit-database-config" 5 | stringData: 6 | user: "admin" 7 | password: "secret" 8 | url: "jdbc:postgresql://audit-database:5432/audit" 9 | -------------------------------------------------------------------------------- /audit-service/src/main/fabric8/deployment.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | template: 3 | spec: 4 | containers: 5 | - name: vertx 6 | env: 7 | - name: KUBERNETES_NAMESPACE 8 | valueFrom: 9 | fieldRef: 10 | apiVersion: v1 11 | fieldPath: metadata.namespace 12 | - name: JAVA_OPTIONS 13 | value: '-Dvertx.cacheDirBase=/tmp -Dvertx.jgroups.config=default-configs/default-jgroups-kubernetes.xml -Djava.net.preferIPv4Stack=true' 14 | - name: JAVA_ARGS 15 | value: '-cluster' 16 | # - name: DB_USERNAME 17 | # valueFrom: 18 | # secretKeyRef: 19 | # name: audit-database-config 20 | # key: user 21 | # - name: DB_PASSWORD 22 | # valueFrom: 23 | # secretKeyRef: 24 | # name: audit-database-config 25 | # key: password 26 | # - name: DB_URL 27 | # valueFrom: 28 | # secretKeyRef: 29 | # name: audit-database-config 30 | # key: url 31 | -------------------------------------------------------------------------------- /compulsive-traders/README.md: -------------------------------------------------------------------------------- 1 | # Compulsive Traders 2 | 3 | The compulsive traders projects contains several implementations of (very dumb) _traders_ that sell and buy shares. They 4 | receive quotes from the event bus and use the portfolio service to buy and sell shared. 5 | 6 | ## Build 7 | 8 | ``` 9 | mvn clean package 10 | ``` 11 | 12 | ## Deploy 13 | 14 | ``` 15 | mvn fabric8:deploy 16 | ``` 17 | -------------------------------------------------------------------------------- /compulsive-traders/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.vertx.workshop 9 | vertx-kubernetes-workshop 10 | 1.0-SNAPSHOT 11 | 12 | 13 | compulsive-traders 14 | 15 | 16 | io.vertx.workshop.trader.impl.MainVerticle 17 | 18 | 19 | 20 | 21 | io.vertx.workshop 22 | portfolio-service 23 | ${project.version} 24 | client 25 | 26 | 27 | io.vertx 28 | vertx-infinispan 29 | 30 | 31 | org.infinispan 32 | infinispan-cloud 33 | 34 | 35 | 36 | 37 | 38 | 39 | io.reactiverse 40 | vertx-maven-plugin 41 | 42 | 43 | io.fabric8 44 | fabric8-maven-plugin 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /compulsive-traders/src/main/fabric8/deployment.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | template: 3 | spec: 4 | containers: 5 | - name: vertx 6 | env: 7 | - name: KUBERNETES_NAMESPACE 8 | valueFrom: 9 | fieldRef: 10 | apiVersion: v1 11 | fieldPath: metadata.namespace 12 | - name: JAVA_OPTIONS 13 | value: '-Dvertx.cacheDirBase=/tmp -Dvertx.jgroups.config=default-configs/default-jgroups-kubernetes.xml -Djava.net.preferIPv4Stack=true' 14 | - name: JAVA_ARGS 15 | value: '-cluster' 16 | -------------------------------------------------------------------------------- /compulsive-traders/src/main/java/io/vertx/workshop/trader/impl/CallbackTraderVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.trader.impl; 2 | 3 | import io.vertx.core.*; 4 | import io.vertx.core.eventbus.MessageConsumer; 5 | import io.vertx.core.json.JsonObject; 6 | import io.vertx.servicediscovery.ServiceDiscovery; 7 | import io.vertx.servicediscovery.types.EventBusService; 8 | import io.vertx.servicediscovery.types.MessageSource; 9 | import io.vertx.workshop.portfolio.PortfolioService; 10 | 11 | /** 12 | * A compulsive trader developed with callbacks and futures. 13 | */ 14 | public class CallbackTraderVerticle extends AbstractVerticle { 15 | 16 | 17 | @Override 18 | public void start(Future done) throws Exception { 19 | 20 | String company = TraderUtils.pickACompany(); 21 | int numberOfShares = TraderUtils.pickANumber(); 22 | System.out.println("Java-Callback compulsive trader configured for company " + company + " and shares: " + 23 | numberOfShares); 24 | 25 | // TODO Complete the code to apply the trading _logic_ on each message received from the "market-data" message 26 | // source 27 | // ---- 28 | 29 | // Retrieve service discovery 30 | Future retrieveServiceDiscovery = getServiceDiscovery(vertx); 31 | 32 | // When the service discovery is retrieved, retrieve the portfolio service and market data 33 | retrieveServiceDiscovery.setHandler(discovery -> { 34 | 35 | // TODO 1 - Get the Future objects for the portfolio and market services. Just use the method given below 36 | 37 | // TODO 2 - Use CompositeFuture.all to "wait" until both future are completed. 38 | 39 | // TODO 3 - Attach a handler on the composite future, and call "initialize" 40 | 41 | // Remove it 42 | done.complete(); 43 | }); 44 | // ---- 45 | 46 | } 47 | 48 | private Future getServiceDiscovery(Vertx vertx) { 49 | Future future = Future.future(); 50 | ServiceDiscovery.create(vertx, future::complete); 51 | return future; 52 | } 53 | 54 | private Future getPortfolioService(ServiceDiscovery discovery) { 55 | Future future = Future.future(); 56 | EventBusService.getServiceProxy(discovery, 57 | rec -> rec.getName().equalsIgnoreCase("portfolio"), 58 | PortfolioService.class, 59 | future); 60 | return future; 61 | } 62 | 63 | private Future> getMarketSource(ServiceDiscovery discovery) { 64 | Future> future = Future.future(); 65 | MessageSource.getConsumer(discovery, 66 | rec -> rec.getName().equalsIgnoreCase("market-data"), 67 | future); 68 | return future; 69 | } 70 | 71 | private void initialize(Future done, String company, int numberOfShares, 72 | Future retrieveThePortfolioService, Future> retrieveTheMarket, 73 | AsyncResult ar) { 74 | if (ar.failed()) { 75 | done.fail(ar.cause()); 76 | } else { 77 | PortfolioService portfolio = retrieveThePortfolioService.result(); 78 | MessageConsumer consumer = retrieveTheMarket.result(); 79 | consumer.handler(message -> TraderUtils.dumbTradingLogic(company, numberOfShares, portfolio, message.body())); 80 | done.complete(); 81 | } 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /compulsive-traders/src/main/java/io/vertx/workshop/trader/impl/MainVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.trader.impl; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.DeploymentOptions; 5 | import io.vertx.core.json.JsonObject; 6 | 7 | /** 8 | * The main verticle creating compulsive traders. 9 | */ 10 | public class MainVerticle extends AbstractVerticle { 11 | 12 | @Override 13 | public void start() throws Exception { 14 | vertx.deployVerticle(CallbackTraderVerticle.class.getName()); 15 | vertx.deployVerticle(RXCompulsiveTraderVerticle.class.getName()); 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /compulsive-traders/src/main/java/io/vertx/workshop/trader/impl/RXCompulsiveTraderVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.trader.impl; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.core.Future; 5 | import io.vertx.core.json.JsonObject; 6 | import io.vertx.reactivex.core.AbstractVerticle; 7 | import io.vertx.reactivex.core.eventbus.MessageConsumer; 8 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 9 | import io.vertx.reactivex.servicediscovery.types.MessageSource; 10 | import io.vertx.workshop.portfolio.reactivex.PortfolioService; 11 | import io.vertx.reactivex.CompletableHelper; 12 | 13 | 14 | /** 15 | * A compulsive trader developed with RX Java 2. 16 | */ 17 | public class RXCompulsiveTraderVerticle extends AbstractVerticle { 18 | 19 | @Override 20 | public void start(Future future) { 21 | String company = TraderUtils.pickACompany(); 22 | int numberOfShares = TraderUtils.pickANumber(); 23 | System.out.println("Java-RX compulsive trader configured for company " + company + " and shares: " + 24 | numberOfShares); 25 | 26 | 27 | ServiceDiscovery.create(vertx, discovery -> { 28 | Single retrieveThePortfolioService = RXEventBusService.rxGetProxy(discovery, PortfolioService.class, 29 | rec -> rec.getName().equalsIgnoreCase("portfolio")); 30 | 31 | Single> retrieveTheMarket = MessageSource.rxGetConsumer(discovery, 32 | rec -> rec.getName().equalsIgnoreCase("market-data")); 33 | 34 | //TODO 35 | //---- 36 | 37 | // TODO 1 - "wait" for both single to be completed (using Single.zip or Single.zipWith methods) 38 | 39 | // TODO 2 - When both single have completed, attach the handler to the message consumer to execute the 40 | // trading logic 41 | 42 | // TODO 3 - Use the TraderUtils.drumpTradingLogic method returning a Completable. Don't forget to 43 | // subscribe to it, or nothing will happen. Return "true" to comply with the "zip" operator 44 | // signature. 45 | 46 | 47 | // TODO 4 - Transform the output into a Completable (ignoreElement) and subscribe to it using: 48 | //.subscribe(CompletableHelper.toObserver(future)) - it reports the failure or success to the `done` 49 | // future. 50 | 51 | // To remove 52 | future.complete(); 53 | //---- 54 | 55 | }); 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /compulsive-traders/src/main/java/io/vertx/workshop/trader/impl/RXEventBusService.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.trader.impl; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.reactivex.impl.AsyncResultSingle; 5 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 6 | import io.vertx.reactivex.servicediscovery.types.EventBusService; 7 | import io.vertx.servicediscovery.Record; 8 | 9 | import java.util.function.Function; 10 | 11 | /** 12 | * @author Clement Escoffier 13 | */ 14 | public class RXEventBusService { 15 | 16 | 17 | public static Single rxGetProxy(ServiceDiscovery discovery, Class clientClass, Function 18 | filter) { 19 | return new AsyncResultSingle<>(handler -> 20 | EventBusService.getServiceProxy(discovery, filter, clientClass, handler) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /compulsive-traders/src/main/java/io/vertx/workshop/trader/impl/TraderUtils.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.trader.impl; 2 | 3 | import io.reactivex.Completable; 4 | import io.vertx.core.json.JsonObject; 5 | import io.vertx.workshop.portfolio.PortfolioService; 6 | 7 | import java.util.Map; 8 | import java.util.Random; 9 | 10 | /** 11 | * A small utility class to initialize the compulsive traders and implement the stupid trading logic. 12 | */ 13 | public class TraderUtils { 14 | 15 | private final static Random RANDOM = new Random(); 16 | 17 | public static String pickACompany() { 18 | int choice = RANDOM.nextInt(2); 19 | switch (choice) { 20 | case 0: 21 | return "Divinator"; 22 | case 1: 23 | return "MacroHard"; 24 | default: 25 | return "Black Coat"; 26 | } 27 | } 28 | 29 | public static boolean timeToSell() { 30 | return RANDOM.nextBoolean(); 31 | } 32 | 33 | public static int pickANumber() { 34 | return RANDOM.nextInt(6) + 1; 35 | } 36 | 37 | public static void dumbTradingLogic(String company, int numberOfShares, PortfolioService portfolio, JsonObject quote) { 38 | if (quote.getString("name").equals(company)) { 39 | if (TraderUtils.timeToSell()) { 40 | portfolio.sell(numberOfShares, quote, p -> { 41 | if (p.succeeded()) { 42 | System.out.println("Sold " + numberOfShares + " of " + company + "!"); 43 | } else { 44 | System.out.println("D'oh, failed to sell " + numberOfShares + " of " + company + " : " + p.cause()); 45 | } 46 | }); 47 | } else { 48 | portfolio.buy(numberOfShares, quote, p -> { 49 | if (p.succeeded()) { 50 | System.out.println("Bought " + numberOfShares + " of " + company + " !"); 51 | } else { 52 | System.out.println("D'oh, failed to buy " + numberOfShares + " of " + company + " : " + p.cause()); 53 | } 54 | }); 55 | } 56 | } 57 | } 58 | 59 | public static Completable dumbTradingLogic(String company, int numberOfShares, io.vertx.workshop.portfolio.reactivex.PortfolioService portfolio, JsonObject quote) { 60 | if (quote.getString("name").equals(company)) { 61 | if (TraderUtils.timeToSell()) { 62 | System.out.println("Trying to sell " + numberOfShares + " " + company); 63 | return portfolio.rxSell(numberOfShares, quote) 64 | .doOnSuccess(p -> System.out.println("Sold " + numberOfShares + " of " + company + "!")) 65 | .doOnError(e -> System.out.println("D'oh, failed to sell " + numberOfShares + " of " 66 | + company + ": " + e.getMessage())) 67 | .ignoreElement(); 68 | } else { 69 | System.out.println("Trying to buy " + numberOfShares + " " + company); 70 | return portfolio.rxBuy(numberOfShares, quote) 71 | .doOnSuccess(p -> System.out.println("Bought " + numberOfShares + " of " + company + " !")) 72 | .doOnError(e -> System.out.println("D'oh, failed to buy " + numberOfShares + " of " + company + " : " + e 73 | .getMessage())) 74 | .ignoreElement(); 75 | } 76 | } 77 | 78 | return Completable.complete(); 79 | } 80 | 81 | /** 82 | * Version called from Groovy where json are maps. 83 | * 84 | * @param company the company 85 | * @param numberOfShares the number of share to buy or sell 86 | * @param portfolio the portfolio service 87 | * @param quote the quote 88 | */ 89 | public static void dumbTradingLogic(String company, int numberOfShares, 90 | PortfolioService portfolio, Map quote) { 91 | JsonObject json = new JsonObject(quote); 92 | dumbTradingLogic(company, numberOfShares, portfolio, json); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /compulsive-traders/src/main/kotlin/io/vertx/workshop/trader/impl/KotlinCompulsiveTraderVerticle.kt: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.trader.impl 2 | 3 | import io.vertx.servicediscovery.Record 4 | import io.vertx.servicediscovery.ServiceDiscovery 5 | import io.vertx.servicediscovery.types.EventBusService 6 | import io.vertx.workshop.portfolio.PortfolioService 7 | 8 | 9 | class KotlinCompulsiveTraderVerticle : io.vertx.core.AbstractVerticle() { 10 | 11 | private var discovery: ServiceDiscovery? = null 12 | 13 | override fun start() { 14 | ServiceDiscovery.create(vertx, { discovery -> 15 | this.discovery = discovery 16 | 17 | EventBusService.getServiceProxy( 18 | discovery, 19 | fun(rec: Record) : Boolean { return rec.getName().equals("portfolio")}, 20 | PortfolioService::class.java, { 21 | ar -> println("done") 22 | } 23 | ) 24 | 25 | }); 26 | } 27 | } -------------------------------------------------------------------------------- /compulsive-traders/src/main/solution/io/vertx/workshop/trader/impl/CallbackTraderVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.trader.impl; 2 | 3 | import io.vertx.core.*; 4 | import io.vertx.core.eventbus.MessageConsumer; 5 | import io.vertx.core.json.JsonObject; 6 | import io.vertx.servicediscovery.ServiceDiscovery; 7 | import io.vertx.servicediscovery.types.EventBusService; 8 | import io.vertx.servicediscovery.types.MessageSource; 9 | import io.vertx.workshop.portfolio.PortfolioService; 10 | 11 | /** 12 | * A compulsive trader developed with callbacks and futures. 13 | */ 14 | public class CallbackTraderVerticle extends AbstractVerticle { 15 | 16 | 17 | @Override 18 | public void start(Future done) throws Exception { 19 | 20 | String company = TraderUtils.pickACompany(); 21 | int numberOfShares = TraderUtils.pickANumber(); 22 | System.out.println("Java-Callback compulsive trader configured for company " + company + " and shares: " + 23 | numberOfShares); 24 | 25 | // TODO Complete the code to apply the trading _logic_ on each message received from the "market-data" message 26 | // source 27 | // ---- 28 | 29 | // Retrieve service discovery 30 | Future retrieveServiceDiscovery = getServiceDiscovery(vertx); 31 | 32 | // When the service discovery is retrieved, retrieve the portfolio service and market data 33 | retrieveServiceDiscovery.setHandler(discovery -> { 34 | 35 | // TODO 1 - Get the Future objects for the portfolio and market services. Just use the method given below 36 | Future retrieveThePortfolioService = getPortfolioService(discovery.result()); 37 | Future> retrieveTheMarket = getMarketSource(discovery.result()); 38 | 39 | // TODO 2 - Use CompositeFuture.all to "wait" until both future are completed. 40 | 41 | // TODO 3 - Attach a handler on the composite future, and call "initialize" 42 | 43 | // When both are completed, register the message handler to execute the trading logic 44 | CompositeFuture.all(retrieveServiceDiscovery, retrieveTheMarket) 45 | .setHandler(x -> 46 | initialize(done, company, numberOfShares, retrieveThePortfolioService, retrieveTheMarket, x)); 47 | }); 48 | // ---- 49 | 50 | } 51 | 52 | private Future getServiceDiscovery(Vertx vertx) { 53 | Future future = Future.future(); 54 | ServiceDiscovery.create(vertx, future::complete); 55 | return future; 56 | } 57 | 58 | private Future getPortfolioService(ServiceDiscovery discovery) { 59 | Future future = Future.future(); 60 | EventBusService.getServiceProxy(discovery, 61 | rec -> rec.getName().equalsIgnoreCase("portfolio"), 62 | PortfolioService.class, 63 | future); 64 | return future; 65 | } 66 | 67 | private Future> getMarketSource(ServiceDiscovery discovery) { 68 | Future> future = Future.future(); 69 | MessageSource.getConsumer(discovery, 70 | rec -> rec.getName().equalsIgnoreCase("market-data"), 71 | future); 72 | return future; 73 | } 74 | 75 | private void initialize(Future done, String company, int numberOfShares, 76 | Future retrieveThePortfolioService, Future> retrieveTheMarket, 77 | AsyncResult ar) { 78 | if (ar.failed()) { 79 | done.fail(ar.cause()); 80 | } else { 81 | PortfolioService portfolio = retrieveThePortfolioService.result(); 82 | MessageConsumer consumer = retrieveTheMarket.result(); 83 | consumer.handler(message -> TraderUtils.dumbTradingLogic(company, numberOfShares, portfolio, message.body())); 84 | done.complete(); 85 | } 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /compulsive-traders/src/main/solution/io/vertx/workshop/trader/impl/MainVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.trader.impl; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.DeploymentOptions; 5 | import io.vertx.core.json.JsonObject; 6 | 7 | /** 8 | * The main verticle creating compulsive traders. 9 | */ 10 | public class MainVerticle extends AbstractVerticle { 11 | 12 | @Override 13 | public void start() throws Exception { 14 | 15 | vertx.deployVerticle(CallbackTraderVerticle.class.getName()); 16 | // vertx.deployVerticle(RXCompulsiveTraderVerticle.class.getName()); 17 | 18 | // TODO Kotlin 19 | // vertx.deployVerticle("GroovyCompulsiveTraderVerticle.groovy"); 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /compulsive-traders/src/main/solution/io/vertx/workshop/trader/impl/RXCompulsiveTraderVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.trader.impl; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.core.Future; 5 | import io.vertx.core.json.JsonObject; 6 | import io.vertx.reactivex.CompletableHelper; 7 | import io.vertx.reactivex.core.AbstractVerticle; 8 | import io.vertx.reactivex.core.eventbus.MessageConsumer; 9 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 10 | import io.vertx.reactivex.servicediscovery.types.MessageSource; 11 | import io.vertx.workshop.portfolio.reactivex.PortfolioService; 12 | 13 | /** 14 | * A compulsive trader developed with RX Java 2. 15 | */ 16 | public class RXCompulsiveTraderVerticle extends AbstractVerticle { 17 | 18 | @Override 19 | public void start(Future future) { 20 | String company = TraderUtils.pickACompany(); 21 | int numberOfShares = TraderUtils.pickANumber(); 22 | System.out.println("Java-RX compulsive trader configured for company " + company + " and shares: " + 23 | numberOfShares); 24 | 25 | 26 | ServiceDiscovery.create(vertx, discovery -> { 27 | Single retrieveThePortfolioService = RXEventBusService.rxGetProxy(discovery, PortfolioService.class, 28 | rec -> rec.getName().equalsIgnoreCase("portfolio")); 29 | 30 | Single> retrieveTheMarket = MessageSource.rxGetConsumer(discovery, 31 | rec -> rec.getName().equalsIgnoreCase("market-data")); 32 | 33 | //TODO 34 | //---- 35 | 36 | // TODO 1 - "wait" for both single to be completed (using Single.zip or Single.zipWith methods) 37 | retrieveThePortfolioService.zipWith(retrieveTheMarket, (ps, consumer) -> { 38 | 39 | // TODO 2 - When both single have completed, attach the handler to the message consumer to execute the 40 | // trading logic 41 | 42 | consumer.handler(message -> 43 | // TODO 3 - Use the TraderUtils.drumpTradingLogic method returning a Completable. Don't forget to 44 | // subscribe to it, or nothing will happen. Return "true" to comply with the "zip" operator 45 | // signature. 46 | 47 | TraderUtils.dumbTradingLogic(company, numberOfShares, ps, message.body()).subscribe()); 48 | // We need to return something as requested by the "zip" signature. 49 | return true; 50 | }) 51 | 52 | // TODO 4 - Transform the output into a Completable (ignoreElement) and subscribe to it using: 53 | //.subscribe(CompletableHelper.toObserver(future)) - it reports the failure or success to the `done` 54 | // future. 55 | .ignoreElement() 56 | .subscribe(CompletableHelper.toObserver(future)); 57 | //---- 58 | 59 | }); 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /compulsive-traders/src/main/solution/io/vertx/workshop/trader/impl/RXEventBusService.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.trader.impl; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.reactivex.impl.AsyncResultSingle; 5 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 6 | import io.vertx.reactivex.servicediscovery.types.EventBusService; 7 | import io.vertx.servicediscovery.Record; 8 | 9 | import java.util.function.Function; 10 | 11 | /** 12 | * @author Clement Escoffier 13 | */ 14 | public class RXEventBusService { 15 | 16 | 17 | public static Single rxGetProxy(ServiceDiscovery discovery, Class clientClass, Function 18 | filter) { 19 | return new AsyncResultSingle<>(handler -> 20 | EventBusService.getServiceProxy(discovery, filter, clientClass, handler) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /compulsive-traders/src/main/solution/io/vertx/workshop/trader/impl/TraderUtils.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.trader.impl; 2 | 3 | import io.reactivex.Completable; 4 | import io.vertx.core.json.JsonObject; 5 | import io.vertx.workshop.portfolio.PortfolioService; 6 | 7 | import java.util.Map; 8 | import java.util.Random; 9 | 10 | /** 11 | * A small utility class to initialize the compulsive traders and implement the stupid trading logic. 12 | */ 13 | public class TraderUtils { 14 | 15 | private final static Random RANDOM = new Random(); 16 | 17 | public static String pickACompany() { 18 | int choice = RANDOM.nextInt(2); 19 | switch (choice) { 20 | case 0: 21 | return "Divinator"; 22 | case 1: 23 | return "MacroHard"; 24 | default: 25 | return "Black Coat"; 26 | } 27 | } 28 | 29 | public static boolean timeToSell() { 30 | return RANDOM.nextBoolean(); 31 | } 32 | 33 | public static int pickANumber() { 34 | return RANDOM.nextInt(6) + 1; 35 | } 36 | 37 | public static void dumbTradingLogic(String company, int numberOfShares, PortfolioService portfolio, JsonObject quote) { 38 | if (quote.getString("name").equals(company)) { 39 | if (TraderUtils.timeToSell()) { 40 | portfolio.sell(numberOfShares, quote, p -> { 41 | if (p.succeeded()) { 42 | System.out.println("Sold " + numberOfShares + " of " + company + "!"); 43 | } else { 44 | System.out.println("D'oh, failed to sell " + numberOfShares + " of " + company + " : " + p.cause()); 45 | } 46 | }); 47 | } else { 48 | portfolio.buy(numberOfShares, quote, p -> { 49 | if (p.succeeded()) { 50 | System.out.println("Bought " + numberOfShares + " of " + company + " !"); 51 | } else { 52 | System.out.println("D'oh, failed to buy " + numberOfShares + " of " + company + " : " + p.cause()); 53 | } 54 | }); 55 | } 56 | } 57 | } 58 | 59 | public static Completable dumbTradingLogic(String company, int numberOfShares, io.vertx.workshop.portfolio.reactivex.PortfolioService portfolio, JsonObject quote) { 60 | if (quote.getString("name").equals(company)) { 61 | if (TraderUtils.timeToSell()) { 62 | System.out.println("Trying to sell " + numberOfShares + " " + company); 63 | return portfolio.rxSell(numberOfShares, quote) 64 | .doOnSuccess(p -> System.out.println("Sold " + numberOfShares + " of " + company + "!")) 65 | .doOnError(e -> System.out.println("D'oh, failed to sell " + numberOfShares + " of " 66 | + company + ": " + e.getMessage())) 67 | .ignoreElement(); 68 | } else { 69 | System.out.println("Trying to buy " + numberOfShares + " " + company); 70 | return portfolio.rxBuy(numberOfShares, quote) 71 | .doOnSuccess(p -> System.out.println("Bought " + numberOfShares + " of " + company + " !")) 72 | .doOnError(e -> System.out.println("D'oh, failed to buy " + numberOfShares + " of " + company + " : " + e 73 | .getMessage())) 74 | .ignoreElement(); 75 | } 76 | } 77 | 78 | return Completable.complete(); 79 | } 80 | 81 | /** 82 | * Version called from Groovy where json are maps. 83 | * 84 | * @param company the company 85 | * @param numberOfShares the number of share to buy or sell 86 | * @param portfolio the portfolio service 87 | * @param quote the quote 88 | */ 89 | public static void dumbTradingLogic(String company, int numberOfShares, 90 | PortfolioService portfolio, Map quote) { 91 | JsonObject json = new JsonObject(quote); 92 | dumbTradingLogic(company, numberOfShares, portfolio, json); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /currency-3rdparty-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.vertx.workshop 9 | vertx-kubernetes-workshop 10 | 1.0-SNAPSHOT 11 | 12 | 13 | currency-3rdparty-service 14 | 15 | 16 | io.vertx.workshop.currency.CurrencyService 17 | 18 | 19 | 20 | 21 | 22 | io.reactiverse 23 | vertx-maven-plugin 24 | 25 | 26 | io.fabric8 27 | fabric8-maven-plugin 28 | 29 | 30 | 31 | 32 | 33 | 34 | solution 35 | 36 | src/main/java 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /currency-3rdparty-service/src/main/java/io/vertx/workshop/currency/CurrencyService.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.currency; 2 | 3 | import io.vertx.codegen.annotations.Nullable; 4 | import io.vertx.core.AbstractVerticle; 5 | import io.vertx.core.Future; 6 | import io.vertx.core.json.JsonObject; 7 | import io.vertx.ext.web.Router; 8 | import io.vertx.ext.web.RoutingContext; 9 | import io.vertx.ext.web.handler.BodyHandler; 10 | 11 | import java.util.Random; 12 | 13 | /** 14 | * @author Someone you don't want to know... 15 | */ 16 | public class CurrencyService extends AbstractVerticle { 17 | 18 | private Random random = new Random(); 19 | 20 | @Override 21 | public void start(Future future) throws Exception { 22 | Router router = Router.router(vertx); 23 | router.get().handler(rc -> rc.response().end("OK")); 24 | router.post().handler(BodyHandler.create()); 25 | router.post().handler(this::handle); 26 | 27 | vertx.createHttpServer() 28 | .requestHandler(router) 29 | .listen(config().getInteger("port", 8080), 30 | ar -> future.handle(ar.mapEmpty())); 31 | } 32 | 33 | private void handle(RoutingContext rc) { 34 | @Nullable JsonObject json = rc.getBodyAsJson(); 35 | if (json == null || json.getDouble("amount") == null) { 36 | System.out.println("No content or no amount"); 37 | rc.fail(400); 38 | return; 39 | } 40 | 41 | double amount = json.getDouble("amount"); 42 | String target = json.getString("currency"); 43 | if (target == null) { 44 | target = "EUR"; 45 | } 46 | double rate = getRate(target); 47 | if (rate == -1) { 48 | System.out.println("Unknown currency: " + target); 49 | rc.fail(400); 50 | } 51 | 52 | int i = random.nextInt(10); 53 | if (i < 5) { 54 | 55 | rc.response().end(new JsonObject() 56 | .put("amount", convert(amount, rate)) 57 | .put("currency", target).encode() 58 | ); 59 | } else if (i < 8) { 60 | // Failure 61 | rc.fail(500); 62 | } 63 | // Timeout, we don't write the response. 64 | } 65 | 66 | private double getRate(String target) { 67 | if (target.equalsIgnoreCase("USD")) { 68 | return 1; 69 | } 70 | if (target.equalsIgnoreCase("EUR")) { 71 | return 0.84; 72 | } 73 | if (target.equalsIgnoreCase("GBP")) { 74 | return 0.77; 75 | } 76 | return -1; 77 | } 78 | 79 | private double convert(double amount, double rate) { 80 | return amount * rate; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /currency-service/README.md: -------------------------------------------------------------------------------- 1 | # Currency Service 2 | 3 | The currency service _proxy_ protects calls to a 3rd party service using a circuit breaker. 4 | 5 | 6 | ## Build 7 | 8 | ``` 9 | mvn clean package 10 | ``` 11 | 12 | ## Deploy 13 | 14 | ``` 15 | mvn fabric8:deploy 16 | ``` 17 | -------------------------------------------------------------------------------- /currency-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | 9 | io.vertx.workshop 10 | vertx-kubernetes-workshop 11 | 1.0-SNAPSHOT 12 | 13 | 14 | currency-service-proxy 15 | 16 | 17 | 18 | io.vertx.workshop.currency.CurrencyServiceProxy 19 | 20 | vertx-cluster 21 | 22 | 23 | 24 | 25 | io.vertx 26 | vertx-config 27 | 28 | 29 | io.vertx 30 | vertx-infinispan 31 | 32 | 33 | org.infinispan 34 | infinispan-cloud 35 | 36 | 37 | io.vertx.workshop 38 | portfolio-service 39 | 1.0-SNAPSHOT 40 | client 41 | 42 | 43 | 44 | 45 | 46 | 47 | io.reactiverse 48 | vertx-maven-plugin 49 | 50 | 51 | io.fabric8 52 | fabric8-maven-plugin 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /currency-service/src/main/fabric8/deployment.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | template: 3 | spec: 4 | containers: 5 | - name: vertx 6 | env: 7 | - name: KUBERNETES_NAMESPACE 8 | valueFrom: 9 | fieldRef: 10 | apiVersion: v1 11 | fieldPath: metadata.namespace 12 | - name: JAVA_OPTIONS 13 | value: '-Dvertx.cacheDirBase=/tmp -Dvertx.jgroups.config=default-configs/default-jgroups-kubernetes.xml -Djava.net.preferIPv4Stack=true' 14 | - name: JAVA_ARGS 15 | value: '-cluster' -------------------------------------------------------------------------------- /currency-service/src/main/java/io/vertx/workshop/currency/CurrencyServiceProxy.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.currency; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.circuitbreaker.CircuitBreakerOptions; 5 | import io.vertx.core.json.JsonObject; 6 | import io.vertx.reactivex.circuitbreaker.CircuitBreaker; 7 | import io.vertx.reactivex.core.AbstractVerticle; 8 | import io.vertx.reactivex.core.buffer.Buffer; 9 | import io.vertx.reactivex.ext.web.Router; 10 | import io.vertx.reactivex.ext.web.RoutingContext; 11 | import io.vertx.reactivex.ext.web.client.HttpResponse; 12 | import io.vertx.reactivex.ext.web.handler.BodyHandler; 13 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 14 | import io.vertx.reactivex.servicediscovery.types.EventBusService; 15 | import io.vertx.reactivex.servicediscovery.types.HttpEndpoint; 16 | import io.vertx.workshop.portfolio.reactivex.PortfolioService; 17 | 18 | import static io.vertx.workshop.currency.Helpers.toObserver; 19 | 20 | /** 21 | * Verticle acting as a proxy between our application and the flaky 3rd party currency service. 22 | */ 23 | public class CurrencyServiceProxy extends AbstractVerticle { 24 | 25 | private CircuitBreaker circuit; 26 | private ServiceDiscovery discovery; 27 | 28 | @Override 29 | public void start() throws Exception { 30 | Router router = Router.router(vertx); 31 | router.get().handler(this::convertPortfolioToEuro); 32 | router.post().handler(BodyHandler.create()); 33 | router.post().handler(this::delegateWithCircuitBreaker); 34 | 35 | circuit = CircuitBreaker.create("circuit-breaker", vertx, 36 | new CircuitBreakerOptions() 37 | .setFallbackOnFailure(true) 38 | .setMaxFailures(3) 39 | .setResetTimeout(5000) 40 | .setTimeout(1000) 41 | ); 42 | 43 | discovery = ServiceDiscovery.create(vertx); 44 | 45 | 46 | vertx.createHttpServer() 47 | .requestHandler(router) 48 | .listen(8080); 49 | } 50 | 51 | private void delegateWithCircuitBreaker(RoutingContext rc) { 52 | HttpEndpoint.rxGetWebClient(discovery, svc -> svc.getName().equals("currency-3rdparty-service")) 53 | .flatMap(client -> { 54 | 55 | // TODO 56 | // Use the circuit breaker (circuit) to call the service. Use the rxExecuteCommandWithFallback` method. 57 | // This methods takes 2 parameters: the first one if a function taking a `Future` as parameter and 58 | // needs to report the success or failure on this future. The second method is a function providing 59 | // the fallback result. You must provide a JSON object as response. For the fallback use: 60 | // new JsonObject() 61 | // .put("amount", rc.getBodyAsJson().getDouble("amount")) 62 | // .put("currency", "USD")) 63 | // In the first function, use the given client, emit a POST request on / containing the incoming 64 | // payload (rc.getBodyAsJson()). Extract the response payload as JSON (bodyAsJsonObject). Don't 65 | // forget to subscribe (you can use subscribe(toObserver(fut)). You can have a look to the `delegate` 66 | // method as example. 67 | // ----- 68 | return Single.just(new JsonObject().put("amount", 0.0).put("currency", "N/A")); 69 | }) 70 | // ---- 71 | .map(JsonObject::toBuffer) 72 | .map(Buffer::new) 73 | 74 | .subscribe(toObserver(rc)); 75 | } 76 | 77 | /** 78 | * Example of method not using a circuit breaker. 79 | * 80 | * @param rc the routing context 81 | */ 82 | private void delegate(RoutingContext rc) { 83 | HttpEndpoint.rxGetWebClient(discovery, svc -> svc.getName().equals("currency-3rdparty-service")) 84 | .flatMap(client -> client.post("/").rxSendJsonObject(rc.getBodyAsJson())) 85 | .map(HttpResponse::bodyAsBuffer) 86 | .subscribe(toObserver(rc)); 87 | } 88 | 89 | 90 | /** 91 | * Method to check the proxy requesting to convert the current portfolio to EUR. 92 | * 93 | * @param rc the routing context 94 | */ 95 | private void convertPortfolioToEuro(RoutingContext rc) { 96 | EventBusService.getServiceProxy(discovery, svc -> svc.getName().equals("portfolio"), PortfolioService.class, 97 | ar -> { 98 | if (ar.failed()) { 99 | rc.fail(ar.cause()); 100 | } else { 101 | ar.result().evaluate(res -> { 102 | if (res.failed()) { 103 | rc.fail(res.cause()); 104 | } else { 105 | JsonObject payload = new JsonObject().put("amount", res.result()).put("currency", "EUR"); 106 | rc.setBody(new Buffer(payload.toBuffer())); 107 | delegateWithCircuitBreaker(rc); 108 | } 109 | }); 110 | } 111 | }); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /currency-service/src/main/java/io/vertx/workshop/currency/Helpers.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.currency; 2 | 3 | import io.reactivex.Single; 4 | import io.reactivex.SingleObserver; 5 | import io.reactivex.annotations.NonNull; 6 | import io.reactivex.disposables.Disposable; 7 | import io.vertx.core.json.JsonObject; 8 | import io.vertx.reactivex.core.Future; 9 | import io.vertx.reactivex.core.buffer.Buffer; 10 | import io.vertx.reactivex.ext.web.RoutingContext; 11 | 12 | /** 13 | * @author Clement Escoffier 14 | */ 15 | public class Helpers { 16 | 17 | /** 18 | * Utility method to report the completion/failure from a Single to a Future. 19 | * 20 | * @param future the future 21 | * @return the single observer to pass to {@link Single#subscribe()} 22 | */ 23 | public static SingleObserver toObserver(Future future) { 24 | return new SingleObserver() { 25 | public void onSubscribe(@NonNull Disposable d) { 26 | } 27 | 28 | public void onSuccess(@NonNull JsonObject item) { 29 | future.tryComplete(item); 30 | } 31 | 32 | public void onError(Throwable error) { 33 | future.tryFail(error); 34 | } 35 | }; 36 | } 37 | 38 | /** 39 | * Utility method to report the completion/failure from a Single to a Routing Context. 40 | * 41 | * @param rc the routing context 42 | * @return the single observer to pass to {@link Single#subscribe()} 43 | */ 44 | public static SingleObserver toObserver(RoutingContext rc) { 45 | return new SingleObserver() { 46 | public void onSubscribe(@NonNull Disposable d) { 47 | } 48 | 49 | public void onSuccess(@NonNull Buffer payload) { 50 | rc.response().end(payload); 51 | } 52 | 53 | public void onError(Throwable error) { 54 | rc.fail(error); 55 | } 56 | }; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /currency-service/src/main/solution/io/vertx/workshop/currency/CurrencyServiceProxy.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.currency; 2 | 3 | import io.vertx.circuitbreaker.CircuitBreakerOptions; 4 | import io.vertx.core.json.JsonObject; 5 | import io.vertx.reactivex.circuitbreaker.CircuitBreaker; 6 | import io.vertx.reactivex.core.AbstractVerticle; 7 | import io.vertx.reactivex.core.buffer.Buffer; 8 | import io.vertx.reactivex.ext.web.Router; 9 | import io.vertx.reactivex.ext.web.RoutingContext; 10 | import io.vertx.reactivex.ext.web.client.HttpResponse; 11 | import io.vertx.reactivex.ext.web.handler.BodyHandler; 12 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 13 | import io.vertx.reactivex.servicediscovery.types.EventBusService; 14 | import io.vertx.reactivex.servicediscovery.types.HttpEndpoint; 15 | import io.vertx.workshop.portfolio.reactivex.PortfolioService; 16 | 17 | import static io.vertx.workshop.currency.Helpers.toObserver; 18 | 19 | /** 20 | * Verticle acting as a proxy between our application and the flaky 3rd party currency service. 21 | */ 22 | public class CurrencyServiceProxy extends AbstractVerticle { 23 | 24 | private CircuitBreaker circuit; 25 | private ServiceDiscovery discovery; 26 | 27 | @Override 28 | public void start() throws Exception { 29 | Router router = Router.router(vertx); 30 | router.get().handler(this::convertPortfolioToEuro); 31 | router.post().handler(BodyHandler.create()); 32 | router.post().handler(this::delegateWithCircuitBreaker); 33 | 34 | circuit = CircuitBreaker.create("circuit-breaker", vertx, 35 | new CircuitBreakerOptions() 36 | .setFallbackOnFailure(true) 37 | .setMaxFailures(3) 38 | .setResetTimeout(5000) 39 | .setTimeout(1000) 40 | ); 41 | 42 | discovery = ServiceDiscovery.create(vertx); 43 | 44 | 45 | vertx.createHttpServer() 46 | .requestHandler(router) 47 | .listen(8080); 48 | } 49 | 50 | private void delegateWithCircuitBreaker(RoutingContext rc) { 51 | HttpEndpoint.rxGetWebClient(discovery, svc -> svc.getName().equals("currency-3rdparty-service")) 52 | .flatMap(client -> 53 | 54 | // TODO 55 | // Use the circuit breaker (circuit) to call the service. Use the rxExecuteCommandWithFallback` method. 56 | // This methods takes 2 parameters: the first one if a function taking a `Future` as parameter and 57 | // needs to report the success or failure on this future. The second method is a function providing 58 | // the fallback result. You must provide a JSON object as response. For the fallback use: 59 | // new JsonObject() 60 | // .put("amount", rc.getBodyAsJson().getDouble("amount")) 61 | // .put("currency", "USD")) 62 | // In the first function, use the given client, emit a POST request on / containing the incoming 63 | // payload (rc.getBodyAsJson()). Extract the response payload as JSON (bodyAsJsonObject). Don't 64 | // forget to subscribe (you can use subscribe(toObserver(fut)). You can have a look to the `delegate` 65 | // method as example. 66 | // ----- 67 | circuit.rxExecuteCommandWithFallback( 68 | fut -> 69 | client.post("/").rxSendJsonObject(rc.getBodyAsJson()) 70 | .map(HttpResponse::bodyAsJsonObject) 71 | .subscribe(toObserver(fut)), 72 | err -> new JsonObject() 73 | .put("amount", rc.getBodyAsJson().getDouble("amount")) 74 | .put("currency", "USD"))) 75 | 76 | // ---- 77 | .map(JsonObject::toBuffer) 78 | .map(Buffer::new) 79 | 80 | .subscribe(toObserver(rc)); 81 | } 82 | 83 | /** 84 | * Example of method not using a circuit breaker. 85 | * 86 | * @param rc the routing context 87 | */ 88 | private void delegate(RoutingContext rc) { 89 | HttpEndpoint.rxGetWebClient(discovery, svc -> svc.getName().equals("currency-3rdparty-service")) 90 | .flatMap(client -> client.post("/").rxSendJsonObject(rc.getBodyAsJson())) 91 | .map(HttpResponse::bodyAsBuffer) 92 | .subscribe(toObserver(rc)); 93 | } 94 | 95 | 96 | /** 97 | * Method to check the proxy requesting to convert the current portfolio to EUR. 98 | * 99 | * @param rc the routing context 100 | */ 101 | private void convertPortfolioToEuro(RoutingContext rc) { 102 | EventBusService.getServiceProxy(discovery, svc -> svc.getName().equals("portfolio"), PortfolioService.class, 103 | ar -> { 104 | if (ar.failed()) { 105 | rc.fail(ar.cause()); 106 | } else { 107 | ar.result().evaluate(res -> { 108 | if (res.failed()) { 109 | rc.fail(res.cause()); 110 | } else { 111 | JsonObject payload = new JsonObject().put("amount", res.result()).put("currency", "EUR"); 112 | rc.setBody(new Buffer(payload.toBuffer())); 113 | delegateWithCircuitBreaker(rc); 114 | } 115 | }); 116 | } 117 | }); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /currency-service/src/main/solution/io/vertx/workshop/currency/Helpers.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.currency; 2 | 3 | import io.reactivex.Single; 4 | import io.reactivex.SingleObserver; 5 | import io.reactivex.annotations.NonNull; 6 | import io.reactivex.disposables.Disposable; 7 | import io.vertx.core.json.JsonObject; 8 | import io.vertx.reactivex.core.Future; 9 | import io.vertx.reactivex.core.buffer.Buffer; 10 | import io.vertx.reactivex.ext.web.RoutingContext; 11 | 12 | /** 13 | * @author Clement Escoffier 14 | */ 15 | public class Helpers { 16 | 17 | /** 18 | * Utility method to report the completion/failure from a Single to a Future. 19 | * 20 | * @param future the future 21 | * @return the single observer to pass to {@link Single#subscribe()} 22 | */ 23 | public static SingleObserver toObserver(Future future) { 24 | return new SingleObserver() { 25 | public void onSubscribe(@NonNull Disposable d) { 26 | } 27 | 28 | public void onSuccess(@NonNull JsonObject item) { 29 | future.tryComplete(item); 30 | } 31 | 32 | public void onError(Throwable error) { 33 | future.tryFail(error); 34 | } 35 | }; 36 | } 37 | 38 | /** 39 | * Utility method to report the completion/failure from a Single to a Routing Context. 40 | * 41 | * @param rc the routing context 42 | * @return the single observer to pass to {@link Single#subscribe()} 43 | */ 44 | public static SingleObserver toObserver(RoutingContext rc) { 45 | return new SingleObserver() { 46 | public void onSubscribe(@NonNull Disposable d) { 47 | } 48 | 49 | public void onSuccess(@NonNull Buffer payload) { 50 | rc.response().end(payload); 51 | } 52 | 53 | public void onError(Throwable error) { 54 | rc.fail(error); 55 | } 56 | }; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Build 2 | 3 | To build the documentation: 4 | 5 | * Using Fish: 6 | 7 | ``` 8 | docker run -it -v (pwd):/documents/ asciidoctor/docker-asciidoctor "./build.sh" "html" 9 | ``` 10 | 11 | * Using Bash 12 | 13 | ``` 14 | docker run -it -v $(pwd):/documents/ asciidoctor/docker-asciidoctor "./build.sh" "html" 15 | ``` 16 | 17 | 18 | 19 | 20 | 21 | * Using Workshopper local 22 | ``` 23 | cd workshopper-guides 24 | docker run -it --rm -p 8080:8080 -v $(pwd):/app-data \ 25 | -e LOG_TO_STDOUT=true \ 26 | -e CONTENT_URL_PREFIX="file:///app-data" \ 27 | -e WORKSHOPS_URLS="file:///app-data/_workshop-guides-che.yml" \ 28 | quay.io/osevg/workshopper:latest 29 | ``` -------------------------------------------------------------------------------- /docs/assets/css/hol.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 10px; 3 | } 4 | 5 | p, 6 | blockquote, 7 | dt, 8 | td.content, 9 | span.alt { 10 | font-size: inherit; 11 | } 12 | 13 | #header, 14 | #content, 15 | #footnotes, 16 | #footer { 17 | max-width: 90.5em; 18 | } 19 | 20 | .quoteblock blockquote, 21 | .quoteblock blockquote, 22 | td.content { 23 | font-size: 1.2em; 24 | } 25 | 26 | .assignment pre code { 27 | background-color: transparent; 28 | } 29 | 30 | .sidebarblock p, 31 | .sidebarblock dt, 32 | .sidebarblock td.content, 33 | p.tableblock { 34 | font-size: 1em; 35 | } 36 | 37 | a[data-toggle='collapse'] { 38 | margin-bottom: 10px; 39 | } 40 | 41 | h1 { 42 | font-size: 46px; 43 | font-weight: bold; 44 | } 45 | 46 | #toctitle, 47 | .h1, 48 | .h2, 49 | .h3, 50 | .h4, 51 | .h5, 52 | .h6, 53 | h1, 54 | h2, 55 | h3, 56 | h4, 57 | h5, 58 | h6 { 59 | font-family: inherit; 60 | line-height: 1.1; 61 | color: inherit; 62 | } 63 | 64 | h1 { 65 | margin: .67em 0; 66 | font-size: 1.5em; 67 | } 68 | 69 | li p { 70 | font-size: 1em; 71 | } 72 | 73 | code { 74 | color: #c7254e; 75 | } 76 | 77 | code, 78 | kbd, 79 | pre, 80 | samp { 81 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; 82 | } 83 | 84 | .quoteblock blockquote:before { 85 | vertical-align: baseline; 86 | } 87 | 88 | .quoteblock blockquote, .quoteblock blockquote, td.content { 89 | font-size: inherit; 90 | } -------------------------------------------------------------------------------- /docs/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /docs/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /docs/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run using: 4 | # 5 | # ./convert.sh 6 | # 7 | # or 8 | # 9 | # ./convert.sh html,pdf 10 | # 11 | # ...where the first argument is a comma-delimited list of formats 12 | 13 | # Program paths 14 | ASCIIDOCTOR=asciidoctor 15 | FOPUB=fopub 16 | ASCIIDOCTOR_PDF=asciidoctor-pdf 17 | 18 | # File names 19 | MASTER_ADOC=index.adoc 20 | MASTER_DOCBOOK=${MASTER_ADOC/.adoc/.xml} 21 | 22 | # Command options 23 | mkdir -p output 24 | SHARED_OPTIONS='-a toc=left -a stylesheet! -a numbered -a experimental -a source-highlighter=prettify -r asciidoctor-diagram -a imagesdir=images 25 | --destination-dir=output' 26 | 27 | cp -R images output 28 | cp -R assets output 29 | 30 | echo "Converting to HTML ..." 31 | $ASCIIDOCTOR -v $SHARED_OPTIONS $MASTER_ADOC 32 | 33 | cp -R images output 34 | rm -Rf **/.asciidoctor 35 | 36 | 37 | exit 0 -------------------------------------------------------------------------------- /docs/chapters/1-preface.adoc: -------------------------------------------------------------------------------- 1 | == Preface - what happens when you mix reactive, containers and Cloud altogether... 2 | 3 | Let's start from the beginning.... _Vert.x_. What's Vert.x? That's a pretty good question, and probably a good start. 4 | If you go on the http://vertx.io[Vert.x web site], Vert.x is defined as _"a toolkit for building reactive 5 | applications on the JVM"_. This description is rather unclear, right? What's a _toolkit_? What are _reactive_ 6 | applications? In this lab, we are going to explain these words, and build an application using Vert.x illustrating 7 | what Vert.x is. This application is going to be composed of _microservices_. Another buzzword that is currently trending, right ? Actually, 8 | Vert.x was already promoting _microservices_ before anyone else. 9 | 10 | The developed application is going to be: 11 | 12 | * based on Vert.x (that's why you are here, right?) 13 | * distributed 14 | * built as a reactive system 15 | * (a bit) fun 16 | 17 | This lab offers attendees an intro-level, hands-on session with Vert.x, from the first line of code, to making 18 | services, to consuming them and finally to assembling everything in a consistent reactive system. It illustrates what reactive systems are, what reactive programming is, and how to build applications based on reactive microservice**s** 19 | (and the **s** is important). 20 | 21 | Deploying and maintaining microservices is hard. Much harder than maintaining a monolith. The are many moving pieces 22 | that can change / evolve / crash at anytime. To help us deploying our microservices, we are going to use Kubernetes. 23 | Kubernetes (commonly referred to as "K8s") is an open-source system for automating deployment, scaling and management of 24 | containerized applications that was originally designed by Google and donated to the Cloud Native Computing Foundation. 25 | It aims to provide a "platform for automating deployment, scaling, and operations of application containers across 26 | clusters of hosts". In this lab, we are using a specific distribution of K8S named OpenShift that provide a few set 27 | of features very useful to maintain our microservices. It's **NOT** required to deploy Vert.x applications on top 28 | of Kubernetes. Bare metal is generally fine. We use Kubernetes in this lab because of the complexity involved when 29 | dealing with multiple microservices, their updates, downtimes and so on. 30 | 31 | This is a BYOL (Bring Your Own Laptop) session, so bring your Windows, OSX, or Linux laptop. You need JDK 8+ on your 32 | machine, and Apache Maven (3.5+). On Windows, you will need Cygwin. The lab contain a section to install _minishift_, 33 | a version of Kubernetes running your laptop. 34 | 35 | What you are going to learn: 36 | 37 | * What Vert.x is and how to use its asynchronous non-blocking development model 38 | * How to develop microservices with Vert.x with several types of services, and service discovery 39 | * How to deploy microservices on Kubernetes, and how Vert.x interacts with Kubernetes 40 | * What `verticles` are and how to use them 41 | * How to use the Vert.x event bus to send and receive messages 42 | * How to expose HTTP endpoints with Vert.x, and also how to consume them 43 | * How to compose asynchronous actions 44 | * How to use several languages in the same application 45 | * How to use databases with Vert.x 46 | * How to manage failures with async results, futures, exception handlers and circuit breakers 47 | * How to use Vert.x with RxJava 48 | * How to scale microservices, and use Kubernetes resilience 49 | 50 | And many more... 51 | 52 | This lab is an _à la carte_ lab. Once you have done the basic installation and introduction, you can pick one of the 53 | topics you are interested in. For instance, after the introduction, if you are interested in gRPC, you can jump to the 54 | gRPC section and try it out. We highly recommend to start at the beginning to understand the concepts behind Vert.x, 55 | reactive systems, reactive programming, and microservices. 56 | 57 | Don't expect to complete this lab in 3 hours, it will take you a lot more. But that's not a big deal. Pick the topic 58 | you want, learn what you want, and come back later to learn more. 59 | 60 | -------------------------------------------------------------------------------- /docs/chapters/10-currency.adoc: -------------------------------------------------------------------------------- 1 | == Resilience, Circuit Breaker and API Proxy - the currency service 2 | 3 | In this chapter we are going to see a very common microservice pattern: proxies. Sometimes you are relying on an 4 | _external_ service that is not _as good as you would like_. These flaky services can kill your system, as generally 5 | the error they produce cascade into the rest of your system. It's even worse with timeout as your are waiting for a 6 | response for a long time. 7 | 8 | In this chapter we are going to create a proxy in front of a 3rd party currency service (flaky) and improve the 9 | resilience of the system by protecting calls to this service with a _circuit breaker_. 10 | 11 | We are going to cover: 12 | * how to build a proxy 13 | * how to use a circuit breaker (and what it is) 14 | 15 | === Proxies 16 | 17 | A _proxy_ is just a microservices delegating calls to another one. It may "improve" or "decorate" it, or even 18 | orchestrate different services. In that later case, we are closer to API gateways than plain proxies. 19 | 20 | [plantuml, proxy-sequence, png] 21 | ---- 22 | @startuml 23 | autonumber 24 | actor User 25 | participant Proxy 26 | participant Service 27 | 28 | User ->> Proxy : The user emits a request to the proxy 29 | Proxy ->> Service : The proxy delegates the actual service 30 | Service -->> Proxy : The service respond to the proxy 31 | Proxy -->> User : The proxy forward the response to the user 32 | 33 | @enduml 34 | ---- 35 | 36 | But why doing a proxy? Well, let's take the example of a flaky service: 37 | 38 | [plantuml, proxy-sequence-timeout, png] 39 | ---- 40 | @startuml 41 | autonumber 42 | actor User 43 | participant Proxy 44 | participant Service 45 | 46 | User ->> Proxy : The user emits a request to the proxy 47 | Proxy ->> Service : The proxy delegates the actual service 48 | Service -->x Proxy : The service rejects the request, or does not reply 49 | 50 | @enduml 51 | ---- 52 | 53 | In this case, the user does not get the valid answer, may wait a long time to get an answer and so on. So, the proxy 54 | must contain some "resilience" logic to isolate errors and if possible provide a fallback answer. 55 | 56 | === Circuit Breakers 57 | 58 | A circuit breaker is an object monitoring an interaction, for instance the calls between our proxy and the flaky service. It starts in the _closed_ state. In this state, the circuit breaker forward the calls to the service and monitor the outcome (successes or failures). When the circuit breaker reaches an amount of failure, it switches to the _open_ state. In this state, the service is not called anymore, but a fallback response is used. After some specific time, the circuit breaker goes to the _half-open_ state and let the next request pass and hit the service. Depending on the result of this request, the circuit breaker goes back to the open state (on failure) or the closed state (on success). 59 | 60 | [plantuml, circuit-breaker-states, png] 61 | ---- 62 | @startuml 63 | [*] --> Closed 64 | Closed : In the closed state, requests\n are forward to the service. 65 | Open: The service is not called anymore,\n a fallback response is used 66 | state "Half-Open" as HO 67 | HO: The circuit breaker lets a\n request pass to check\n whether the service is back on track 68 | 69 | Closed -> Open : When the service has failed\n a couple of time 70 | 71 | Open -> HO : After some specific time,\n the circuit breaker\n switch to the half-open state 72 | 73 | 74 | HO -> Closed : If the request\n has been successful 75 | HO -> Open : If the request\n has failed 76 | 77 | @enduml 78 | ---- 79 | 80 | A circuit breaker is a great way to protect your system against flaky service and also gives them time to recover when they start failing (_open_ state). When the service is back on track, and thanks to the half-open state, your system is going to auto-recover and restart using the service. 81 | 82 | 83 | === Task - Using the vert.x circuit breaker 84 | 85 | Open the `io.vertx.workshop.currency.CurrencyServiceProxy` class located in the `currency-service` project. Jump to the `delegateWithCircuitBreaker` method and fill it using the inlined instructions. 86 | 87 | [.assignment] 88 | **** 89 | [source, java] 90 | ---- 91 | private void delegateWithCircuitBreaker(RoutingContext rc) { 92 | HttpEndpoint.rxGetWebClient(discovery, svc -> svc.getName().equals("currency-3rdparty-service")) 93 | .flatMap(client -> 94 | 95 | // TODO 96 | // Use the circuit breaker (circuit) to call the service. Use the rxExecuteCommandWithFallback` method. 97 | // This methods takes 2 parameters: the first one if a function taking a `Future` as parameter and 98 | // needs to report the success or failure on this future. The second method is a function providing 99 | // the fallback result. You must provide a JSON object as response. For the fallback use: 100 | // new JsonObject() 101 | // .put("amount", rc.getBodyAsJson().getDouble("amount")) 102 | // .put("currency", "USD")) 103 | // In the first function, use the given client, emit a POST request on / containing the incoming 104 | // payload (rc.getBodyAsJson()). Extract the response payload as JSON (bodyAsJsonObject). Don't 105 | // forget to subscribe (you can use subscribe(toObserver(fut)). You can have a look to the `delegate` 106 | // method as example. 107 | // ----- 108 | circuit.rxExecuteCommandWithFallback( 109 | fut -> 110 | client.post("/").rxSendJsonObject(rc.getBodyAsJson()) 111 | .map(HttpResponse::bodyAsJsonObject) 112 | .subscribe(toObserver(fut)), 113 | err -> new JsonObject() 114 | .put("amount", rc.getBodyAsJson().getDouble("amount")) 115 | .put("currency", "USD"))) 116 | 117 | // ---- 118 | .map(JsonObject::toBuffer) 119 | .map(Buffer::new) 120 | .subscribe(toObserver(rc)); 121 | } 122 | ---- 123 | **** 124 | 125 | === Show time ! 126 | 127 | Let's see how this works. 128 | 129 | [source] 130 | ---- 131 | cd currency-service 132 | mvn fabric8:deploy 133 | ---- 134 | 135 | Now, in your OpenShift dashboard, click on the new route that has been created (such as `http://currency-service-proxy-vertx-kubernetes-workshop.192.168.64.17.nip.io`). Click on it and you should see the 136 | evaluation of your portfolio in Euros. 137 | 138 | 139 | -------------------------------------------------------------------------------- /docs/chapters/1_1-prerequisites.adoc: -------------------------------------------------------------------------------- 1 | == Prerequisites 2 | 3 | We will get to the good stuff, coding all the way soon... But before we start, we need to install some software on our machines. 4 | 5 | === Hardware 6 | 7 | * Operating System: Mac OS X (10.8+), Windows 8.1+, Ubuntu 12+, CentOS 7+, Fedora 22+ 8 | * Memory: At least 8 GB+ 9 | 10 | === Java Development Kit 11 | 12 | We need a JDK 8+ installed on our machine. Latest JDK can downloaded from: 13 | 14 | * http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html[Oracle JDK 8] 15 | * http://openjdk.java.net/install/[OpenJDK] 16 | 17 | You can use either Oracle JDK or OpenJDK. 18 | 19 | === Apache Maven 20 | 21 | You need Apache Maven 3.5+. If you don't have it already: 22 | 23 | * Download Apache Maven from https://maven.apache.org/download.cgi. 24 | * Unzip to a directory of your choice and add it to the `PATH`. 25 | 26 | 27 | === Minishift and OpenShift Client 28 | 29 | Minishift provides a way to run Openshift (and so Kubernetes) on your laptop. 30 | 31 | 1. Download minishift from https://github.com/minishift/minishift/releases 32 | 2. Follow the instructions from https://docs.openshift.org/latest/minishift/getting-started/installing.html to install 33 | it. 34 | 35 | Be aware of the prerequisites in terms of hypervisors. 36 | 37 | At the end of the installation process, you should have the `minishift` executable in your `$PATH`. Then, follow the 38 | instructions from https://docs.openshift.org/latest/minishift/getting-started/quickstart.html to start minishift and 39 | access the OpenShift Client (command-line) and console (browser). 40 | 41 | === IDE 42 | 43 | We recommend you use an IDE. You can use Eclipse, IntelliJ, VS Code or Netbeans. 44 | 45 | ==== No IDE ? 46 | 47 | If you don't have an IDE, here are the steps to get started with Eclipse. 48 | 49 | 1. First download Eclipse from http://www.eclipse.org/downloads/packages/eclipse-ide-java-developers/oxygen1[the download page]. 50 | 2. In the _Download Links_ section, be sure to select the right version for your operating system. Once selected it brings you to a download page with a 51 | `Download` button. 52 | 3. Once downloaded, unzip it. 53 | 4. In the destination directory, you should find an `Eclipse` binary that you can execute. 54 | 5. Eclipse asks you to create a workspace. 55 | 6. Once launched, click on the _Workbench_ arrow (top right corner). 56 | 57 | === Getting the code 58 | 59 | [source] 60 | ---- 61 | git clone https://github.com/cescoffier/vertx-kubernetes-workshop 62 | ---- 63 | 64 | You can import the code in your IDE as a Maven project. You can refer to your IDE documentation to know how to import Maven projects. 65 | 66 | For Eclipse: 67 | 68 | 1. Click on `File - Import ... - Maven - Existing Maven Projects` 69 | 2. Select the location where you cloned the sources 70 | 3. Click _Finish_ and wait... 71 | 72 | You are going to see a couple of compilation errors. This is because Eclipse does not mark the `src/main/generated` 73 | directories as _source root_ by default. Right-click on `portfolio-service/src/main/generated` and select _Build Path 74 | -> Use as Source Folder_. 75 | 76 | 77 | -------------------------------------------------------------------------------- /docs/chapters/5-application.adoc: -------------------------------------------------------------------------------- 1 | == The Micro-Trader Application 2 | 3 | Ok, let's stop with playgrounds and get this party started. Now that we know more about Vert.x, microservices and Kubernetes / OpenShift, it's time to discuss the application we are going to develop in this lab. 4 | 5 | It's going to be a fake financial app where we will be making (virtual) money. The application is composed of a set of microservices: 6 | 7 | * The quote generator - this is an absolutely unrealistic simulator that generates the quotes for 3 fictional companies _MacroHard_, _Divinator_, and _Black Coat_. The market data is published on the Vert.x event bus. 8 | 9 | * The traders - these are a set of components that receive quotes from the quote generator and decides whether or not to buy or sell a particular share. To make this decision, they rely on another component called the _portfolio_ service. 10 | 11 | * The portfolio - this service manages the number of shares in our portfolio and their monetary value. It is exposed as a _service proxy_, i.e. an asynchronous RPC service on top of the Vert.x event bus. For every successful operation, it sends a message on the event bus describing the operation. It uses the quote generator to evaluate the current value of the portfolio. 12 | 13 | * The audit - that's the legal side, you know... We need to keep a list of all our operations (yes, that's the law). The audit component receives operations from the portfolio service via the event bus and an address. It then stores these in a database. It also provides a REST endpoint to retrieve the latest set of operations. 14 | 15 | * The currency service - it just converts the value of the portfolio in different currencies. This service relies on a 3rd party service and is ... picky (read _unreliable_). We will have to protect our application from the failures 16 | happening in this service. 17 | 18 | * The dashboard - a UI to let us know when we become rich and get details about services, circuit breakers, health-checks... 19 | 20 | Let's have a look at the architecture: 21 | 22 | image::workshop-application.png[Micro-Trader Architecture, 800] 23 | 24 | The application uses several types of services: 25 | 26 | * HTTP endpoint (_i.e._ REST API) - this service is located using an HTTP URL. We are going to use HTTP/1.1 and HTTP/2 27 | * gRPC - gRPC is a secure and fast RPC framework built on top of HTTP/2 28 | * Message sources - these are components publishing messages on the event bus, the service is located using an (event bus) address. 29 | 30 | All the components are going to be deployed in the same Kubernetes _namespace_ (_project_), and will form a cluster. 31 | 32 | The dashboard presents the available services, the value of each company's quotes, the latest set of operations made by our traders, and the current state of our portfolio. It also shows the state of the different circuit breakers. 33 | 34 | // TODO Redo screenshot 35 | image::dashboard.png[Dashboard, 800] 36 | 37 | We are going to implement critical parts of this application. However, the rest of the code is provided to illustrate some other Vert.x features. The code that needs to be written by us is indicated using **TODO** and wrapped as follows: 38 | 39 | [source,java] 40 | ---- 41 | //TODO 42 | // ---- 43 | // your code here 44 | // ---- 45 | ---- 46 | 47 | 48 | Just to give a brief overview of the next chapters. 49 | 50 | 51 | **Quote Generator chapter** 52 | 53 | * how projects are structured, built, and deployed 54 | * how to use the event bus to receive messages 55 | * how to implement HTTP actions 56 | * how to use RX Java 2 to avoid callbacks 57 | * using config maps to configure the application 58 | 59 | **Portfolio Service chapter** 60 | 61 | * how we can use the event bus to implement asynchronous RPC (RPC done right) 62 | * how to implement a _service proxy_ using callbacks 63 | * how to publish messages on the event bus 64 | * how to use the Vert.x Web Client 65 | 66 | **Impulsive Trader chapter** 67 | 68 | * how to consumer an event bus service 69 | * how to write a verticle using the plain Vert.x API (callbacks) 70 | * how to write a verticle using RX Java 2 71 | 72 | **Audit Service chapter** 73 | 74 | * how to manage advanced asynchronous orchestration 75 | * how to interact with a database 76 | * how to build a REST APU 77 | * how to manage _secrets_ with OpenShift 78 | 79 | **Currency Service chapter** 80 | 81 | * how to build a proxy 82 | * how to use a circuit breaker (and what it is) 83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/chapters/conclusion.adoc: -------------------------------------------------------------------------------- 1 | == Conclusion 2 | 3 | You made it ! Or you jumped to this section. Anyway, congratulations. We hope you enjoy this lab and learn some 4 | _stuff_. There is many other things Vert.x can do and that was not illustrated here. 5 | 6 | Don't forget that reactive systems and so Vert.x requires a mind-shift: 7 | 8 | * Vert.x is a toolkit to build reactive systems 9 | * Asynchronous, non-blocking development model can be hard to understand at the first glance, but it becomes very 10 | convenient very quickly. Don't also forget, computers are asynchronous, so using such development model is 11 | using it the right way to use it to its whole power. 12 | 13 | As soon as you jump into the microservice wagon, you will need a better "hosting" environment. Kubernetes and 14 | OpenShift are perfect weapon to build, deploy, bind, and manage your microservices. 15 | 16 | If you want, and I hope so, to go further here are some references: 17 | 18 | * http://vertx.io[The Vert.x web site] 19 | * http://vertx.io/blog/posts/introduction-to-vertx.html[A blog post series to start developing with Vert.x] 20 | * https://developers.redhat.com/promotions/building-reactive-microservices-in-java/[A free book about Eclipse Vert.x 21 | and microservices] 22 | 23 | -------------------------------------------------------------------------------- /docs/chapters/references.adoc: -------------------------------------------------------------------------------- 1 | == References 2 | 3 | Some recommended reading. Nothing especially about microservices or Vert.x because the concepts are broader than 4 | these two topics. 5 | 6 | [bibliography] 7 | - A. S. Tanenbaum, M Van Steam. Distributed Systems - Principles and Paradigms. 2003 8 | - L. Bass, I. Weber, L. Zhu. Devops, A software Architect's Perspective. 2015 9 | - P. Clements, F. Bachmann, L Bass, D. Garlan, J. Ivers, R. Little, P. Merson, R. Nord, J. 10 | Stafford. Documenting Software Architecture. 2010 11 | - S. Krakowiak. Middleware Architecture with Patterns and Frameworks. 2009 (unfinished), 12 | http://lig-membres.imag.fr/krakowia/Files/MW-Book/Chapters/Preface/preface.html 13 | - J. Lewis, M. Fowler. Microservices - a definition of this new architectural term, 2014, 14 | http://martinfowler.com/articles/microservices.html 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/docinfo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run -it -v $(pwd):/documents/ asciidoctor/docker-asciidoctor "./build.sh" "html" 3 | -------------------------------------------------------------------------------- /docs/images/async-rpc-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/async-rpc-sequence.png -------------------------------------------------------------------------------- /docs/images/async-rpc-sequence.png.cache: -------------------------------------------------------------------------------- 1 | {"checksum":"a3251c42ffc9db694e46458d2db9f888","width":962,"height":419} -------------------------------------------------------------------------------- /docs/images/blocked-event-loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/blocked-event-loop.png -------------------------------------------------------------------------------- /docs/images/circuit-breaker-states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/circuit-breaker-states.png -------------------------------------------------------------------------------- /docs/images/circuit-breaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/circuit-breaker.png -------------------------------------------------------------------------------- /docs/images/dashboard-initial-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/dashboard-initial-state.png -------------------------------------------------------------------------------- /docs/images/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/dashboard.png -------------------------------------------------------------------------------- /docs/images/database-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/database-sequence.png -------------------------------------------------------------------------------- /docs/images/diagram-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/diagram-sequence.png -------------------------------------------------------------------------------- /docs/images/diagram-sequence.png.cache: -------------------------------------------------------------------------------- 1 | {"checksum":"0e76bdfc35464648ab2ec5d9561ed892","width":276,"height":201} -------------------------------------------------------------------------------- /docs/images/diagram-sequences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/diagram-sequences.png -------------------------------------------------------------------------------- /docs/images/diagram-sequences.png.cache: -------------------------------------------------------------------------------- 1 | {"checksum":"0e76bdfc35464648ab2ec5d9561ed892","width":276,"height":201} -------------------------------------------------------------------------------- /docs/images/event-loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/event-loop.png -------------------------------------------------------------------------------- /docs/images/images.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/images.pptx -------------------------------------------------------------------------------- /docs/images/openshift-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/openshift-architecture.png -------------------------------------------------------------------------------- /docs/images/openshift-build-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/openshift-build-process.png -------------------------------------------------------------------------------- /docs/images/openshift-entities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/openshift-entities.png -------------------------------------------------------------------------------- /docs/images/openshift-first-deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/openshift-first-deployment.png -------------------------------------------------------------------------------- /docs/images/openshift-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/openshift-service.png -------------------------------------------------------------------------------- /docs/images/portfolio-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/portfolio-sequence.png -------------------------------------------------------------------------------- /docs/images/portfolio-sequence.png.cache: -------------------------------------------------------------------------------- 1 | {"checksum":"f5e1cf05efcda41c89527e2773ea01cc","width":1464,"height":428} -------------------------------------------------------------------------------- /docs/images/proxy-sequence-timeout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/proxy-sequence-timeout.png -------------------------------------------------------------------------------- /docs/images/proxy-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/proxy-sequence.png -------------------------------------------------------------------------------- /docs/images/quote-openshift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/quote-openshift.png -------------------------------------------------------------------------------- /docs/images/rpc-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/rpc-sequence.png -------------------------------------------------------------------------------- /docs/images/rpc-sequence.png.cache: -------------------------------------------------------------------------------- 1 | {"checksum":"d53e375dbe69a6320b28fbea76158677","width":744,"height":419} -------------------------------------------------------------------------------- /docs/images/workshop-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/workshop-application.png -------------------------------------------------------------------------------- /docs/images/workshop-che-screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/workshop-che-screenshot1.png -------------------------------------------------------------------------------- /docs/images/workshop-che-screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/workshop-che-screenshot2.png -------------------------------------------------------------------------------- /docs/images/workshop-che-screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/workshop-che-screenshot3.png -------------------------------------------------------------------------------- /docs/images/workshop-che-screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/workshop-che-screenshot4.png -------------------------------------------------------------------------------- /docs/images/workshop-che-screenshot5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/workshop-che-screenshot5.png -------------------------------------------------------------------------------- /docs/images/workshop-che-screenshot6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/workshop-che-screenshot6.png -------------------------------------------------------------------------------- /docs/images/workshop-che-screenshot7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedHat-Middleware-Workshops/vertx-workshop/5ff4228e3582cea4cfc73d69f943b72395ac1656/docs/images/workshop-che-screenshot7.png -------------------------------------------------------------------------------- /docs/index.adoc: -------------------------------------------------------------------------------- 1 | = Vert.x & Kubernetes - From zero to (micro)-hero 2 | Clement Escoffier, Rodney Russ, Deven Phillips 3 | v0.2, October, 11th, 2017 4 | :toc: left 5 | :toclevels: 4 6 | :imagesdir: images 7 | :docinfo1: 8 | :icons: font 9 | 10 | include::chapters/1-preface.adoc[Preface] 11 | include::chapters/1_1-prerequisites.adoc[Prerequisites] 12 | include::chapters/2-vertx.adoc[Vert.x] 13 | include::chapters/3-microservices.adoc[Demystifying microservices] 14 | include::chapters/4-kubernetes.adoc[First step with Kubernetes] 15 | include::chapters/5-application.adoc[The application] 16 | include::chapters/6-quote-generator.adoc[Your first Vert.x microservice - the quote generator] 17 | include::chapters/7-portfolio.adoc[Exposing Event bus services, and Web client - the portfolio service] 18 | include::chapters/8-traders.adoc[Async Programming Styles, Polyglot verticle and Replicas - the trader service] 19 | include::chapters/9-audit.adoc[Database, Secrets and Health checks - the audit service] 20 | include::chapters/10-currency.adoc[Resilience, Circuit Breaker and API Proxy - the currency service] 21 | include::chapters/conclusion.adoc[You made it!] 22 | include::chapters/references.adoc[References] 23 | 24 | -------------------------------------------------------------------------------- /micro-trader-dashboard/README.md: -------------------------------------------------------------------------------- 1 | # Micro-Trader-Dashboard 2 | 3 | The quote generator simulates the evolution of the values of 3 companies. Every quote is sent on the event bus. It 4 | also exposes a HTTP endpoint to retrieve the last quote of each company. 5 | 6 | 7 | ## Build 8 | 9 | ``` 10 | mvn clean package 11 | ``` 12 | 13 | ## Deploy 14 | 15 | ``` 16 | mvn fabric8:deploy 17 | ``` 18 | -------------------------------------------------------------------------------- /micro-trader-dashboard/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.vertx.workshop 9 | vertx-kubernetes-workshop 10 | 1.0-SNAPSHOT 11 | 12 | 13 | micro-trader-dashboard 14 | 15 | 16 | 17 | io.vertx.workshop.dashboard.DashboardVerticle 18 | 19 | vertx-cluster 20 | ca8b2b517f 21 | src/main/frontend 22 | 23 | /health 24 | 25 | 26 | 27 | 28 | jitpack.io 29 | https://jitpack.io 30 | 31 | 32 | 33 | 34 | 35 | com.github.yunyu.vertx-console 36 | vertx-console-base 37 | ${vertx.console.version} 38 | 39 | 40 | com.github.yunyu.vertx-console 41 | vertx-console-metrics 42 | ${vertx.console.version} 43 | 44 | 45 | 46 | com.github.yunyu.vertx-console 47 | vertx-console-services 48 | ${vertx.console.version} 49 | 50 | 51 | com.github.cescoffier 52 | vertx-kubernetes-workshop-trader-page 53 | 54 | 87996d087eacb3 55 | 56 | 57 | io.vertx 58 | vertx-dropwizard-metrics 59 | 60 | 61 | io.prometheus 62 | simpleclient_hotspot 63 | 0.0.23 64 | 65 | 66 | io.prometheus 67 | simpleclient_dropwizard 68 | 0.0.23 69 | 70 | 71 | com.github.yunyu 72 | prometheus-jvm-extras 73 | ee279b1 74 | 75 | 76 | io.vertx 77 | vertx-service-discovery-bridge-kubernetes 78 | 79 | 80 | org.slf4j 81 | slf4j-api 82 | 1.7.25 83 | 84 | 85 | com.github.yunyu.vertx-console 86 | vertx-console-circuit-breakers 87 | ${vertx.console.version} 88 | 89 | 90 | com.github.yunyu.vertx-console 91 | vertx-console-eventbus 92 | ${vertx.console.version} 93 | 94 | 95 | com.github.yunyu.vertx-console 96 | vertx-console-http-clients 97 | ${vertx.console.version} 98 | 99 | 100 | com.github.yunyu.vertx-console 101 | vertx-console-pools 102 | ${vertx.console.version} 103 | 104 | 105 | io.vertx 106 | vertx-infinispan 107 | 108 | 109 | org.infinispan 110 | infinispan-cloud 111 | 112 | 113 | 114 | 115 | 116 | 117 | io.reactiverse 118 | vertx-maven-plugin 119 | 120 | true 121 | -Dvertx.metrics.options.enabled=true -Dvertx.metrics.options.registryName=vertx-dw 122 | -cluster 123 | 124 | 125 | 126 | io.fabric8 127 | fabric8-maven-plugin 128 | 129 | 130 | 131 | 132 | 133 | 134 | solution 135 | 136 | src/main/java 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /micro-trader-dashboard/src/main/fabric8/deployment.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | template: 3 | spec: 4 | containers: 5 | - name: vertx 6 | env: 7 | - name: KUBERNETES_NAMESPACE 8 | valueFrom: 9 | fieldRef: 10 | apiVersion: v1 11 | fieldPath: metadata.namespace 12 | - name: JAVA_OPTIONS 13 | value: '-Dvertx.cacheDirBase=/tmp -Dvertx.jgroups.config=default-configs/default-jgroups-kubernetes.xml -Djava.net.preferIPv4Stack=true -Dvertx.metrics.options.enabled=true -Dvertx.metrics.options.registryName=vertx-dw' 14 | - name: JAVA_ARGS 15 | value: '-cluster' 16 | -------------------------------------------------------------------------------- /micro-trader-dashboard/src/main/java/io/vertx/workshop/dashboard/DashboardVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.dashboard; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import com.codahale.metrics.SharedMetricRegistries; 5 | import in.yunyul.vertx.console.base.WebConsoleRegistry; 6 | import in.yunyul.vertx.console.circuitbreakers.CircuitBreakersConsolePage; 7 | import in.yunyul.vertx.console.metrics.MetricsConsolePage; 8 | import in.yunyul.vertx.console.services.ServicesConsolePage; 9 | import io.vertx.core.AbstractVerticle; 10 | import io.vertx.core.Future; 11 | import io.vertx.core.json.JsonArray; 12 | import io.vertx.core.json.JsonObject; 13 | import io.vertx.ext.bridge.PermittedOptions; 14 | import io.vertx.ext.web.Router; 15 | import io.vertx.ext.web.RoutingContext; 16 | import io.vertx.ext.web.client.HttpResponse; 17 | import io.vertx.ext.web.client.WebClient; 18 | import io.vertx.ext.web.codec.BodyCodec; 19 | import io.vertx.ext.web.handler.sockjs.BridgeOptions; 20 | import io.vertx.ext.web.handler.sockjs.SockJSHandler; 21 | import io.vertx.servicediscovery.ServiceDiscovery; 22 | import io.vertx.servicediscovery.types.HttpEndpoint; 23 | 24 | /** 25 | * @author Clement Escoffier 26 | */ 27 | public class DashboardVerticle extends AbstractVerticle { 28 | 29 | private ServiceDiscovery discovery; 30 | 31 | @Override 32 | public void start() throws Exception { 33 | 34 | Router router = Router.router(vertx); 35 | 36 | SockJSHandler sockJSHandler = SockJSHandler.create(vertx); 37 | BridgeOptions options = new BridgeOptions(); 38 | options 39 | .addOutboundPermitted(new PermittedOptions().setAddress("market")) 40 | .addOutboundPermitted(new PermittedOptions().setAddress("portfolio")) 41 | .addOutboundPermitted(new PermittedOptions().setAddress("service.portfolio")) 42 | .addInboundPermitted(new PermittedOptions().setAddress("service.portfolio")) 43 | .addOutboundPermitted(new PermittedOptions().setAddress("vertx.circuit-breaker")); 44 | 45 | sockJSHandler.bridge(options); 46 | router.route("/eventbus/*").handler(sockJSHandler); 47 | 48 | // Last operations 49 | router.get("/operations").handler(this::callAuditService); 50 | router.get("/health").handler(rc -> rc.response().end("OK")); 51 | 52 | 53 | MetricRegistry dropwizardRegistry = SharedMetricRegistries.getOrCreate( 54 | System.getProperty("vertx.metrics.options.registryName") 55 | ); 56 | 57 | ServiceDiscovery.create(vertx, discovery -> { 58 | this.discovery = discovery; 59 | WebConsoleRegistry.create("/admin") 60 | // Add pages 61 | .addPage(MetricsConsolePage.create(dropwizardRegistry)) 62 | .addPage(new TraderPage()) 63 | .addPage(ServicesConsolePage.create(discovery)) 64 | .addPage(CircuitBreakersConsolePage.create()) 65 | .setCacheBusterEnabled(true) // Adds random query string to scripts 66 | // Mount to router 67 | .mount(vertx, router); 68 | 69 | retrieveAuditService(); 70 | vertx.createHttpServer() 71 | .requestHandler(router) 72 | .listen(8080); 73 | }); 74 | 75 | } 76 | 77 | 78 | private Future retrieveAuditService() { 79 | Future future = Future.future(); 80 | HttpEndpoint.getWebClient(discovery, new JsonObject().put("name", "audit-service"), future); 81 | return future; 82 | } 83 | 84 | 85 | private void callAuditService(RoutingContext context) { 86 | retrieveAuditService() 87 | .setHandler(ar -> { 88 | if (ar.failed() || ar.result() == null) { 89 | context.response() 90 | .putHeader("content-type", "application/json") 91 | .setStatusCode(200) 92 | .end(new JsonObject().put("message", "No audit service").encode()); 93 | } else { 94 | ar.result().get("/") 95 | .as(BodyCodec.jsonArray()) 96 | .timeout(5000) 97 | .send(res -> { 98 | if (res.succeeded()) { 99 | HttpResponse response = res.result(); 100 | JsonArray operations = new JsonArray(); 101 | for (Object entry : response.body()) { 102 | JsonObject json = (JsonObject) entry; 103 | operations.add( 104 | new JsonObject() 105 | .put("type", json.getString("action")) 106 | .put("company", json.getJsonObject("quote").getString("name")) 107 | .put("amount", json.getInteger("amount")) 108 | ); 109 | } 110 | context.response() 111 | .putHeader("content-type", "application/json") 112 | .setStatusCode(200) 113 | .end(operations.toBuffer()); 114 | } else { 115 | ar.cause().printStackTrace(); 116 | context.response() 117 | .putHeader("content-type", "application/json") 118 | .setStatusCode(200) 119 | .end(new JsonObject() 120 | .put("message", "No audit service (" + ar.cause().getMessage() + ")").encode()); 121 | } 122 | }); 123 | } 124 | }); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /microservices-exercises/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.vertx.workshop 9 | vertx-kubernetes-workshop 10 | 1.0-SNAPSHOT 11 | 12 | 13 | microservices-exercises 14 | 15 | 16 | 17 | solution 18 | 19 | src/main/java 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /microservices-exercises/src/main/java/io/vertx/workshop/exercise/microservice/Exercise1Verticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise.microservice; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.core.Future; 5 | import io.vertx.reactivex.CompletableHelper; 6 | import io.vertx.reactivex.core.http.HttpServer; 7 | import io.vertx.reactivex.ext.web.Router; 8 | import io.vertx.reactivex.core.AbstractVerticle; 9 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 10 | import io.vertx.reactivex.servicediscovery.types.HttpEndpoint; 11 | import io.vertx.servicediscovery.Record; 12 | 13 | import static io.vertx.reactivex.CompletableHelper.toObserver; 14 | 15 | /** 16 | * This verticle exposes a HTTP endpoint service. You need to publish it in the service discovery. 17 | */ 18 | public class Exercise1Verticle extends AbstractVerticle{ 19 | 20 | 21 | private Record record; 22 | private ServiceDiscovery discovery; 23 | 24 | @Override 25 | public void start(Future future) throws Exception { 26 | // Create a simple HTTP service (using Vert.x Web Router) and publish it in the service discovery. 27 | // As we want to complete the deployment when the service is exposed (asynchronous operation), we use a 28 | // `Future` argument to indicate when the deployment is completed. This allows deploying the other verticle 29 | // after the deployment completion of this one. 30 | 31 | 32 | // Create an instance of service discovery 33 | this.discovery = ServiceDiscovery.create(vertx); 34 | 35 | // Simple HTTP API using Vert.x Web Router. 36 | Router router = Router.router(vertx); 37 | router.get("/").handler(rc -> rc.response().end("OK")); 38 | router.get("/greetings").handler(rc -> rc.response().end("Hello world")); 39 | router.get("/greetings/:name").handler(rc -> rc.response().end("Hello " + rc.pathParam("name"))); 40 | 41 | 42 | vertx.createHttpServer() 43 | .requestHandler(router) 44 | .rxListen(8080) 45 | // When the server is ready, we publish the service 46 | .flatMap(this::publish) 47 | // Store the record, required to un-publish it 48 | .doOnSuccess(rec -> this.record = rec) 49 | .ignoreElement() 50 | .subscribe(toObserver(future)); 51 | } 52 | 53 | private Single publish(HttpServer server) { 54 | // 1 - Create a service record using `io.vertx.reactivex.servicediscovery.types.HttpEndpoint.createRecord`. 55 | // This record define the service name ("greetings"), the host ("localhost"), the server port and the root ("/") 56 | // TODO 57 | 58 | // 2 - Call the rxPublish method with the created record and return the resulting single 59 | // TODO 60 | return null; // To be removed 61 | } 62 | 63 | @Override 64 | public void stop(Future future) throws Exception { 65 | // Unregister the service when the verticle is stopped. 66 | // As it's an asynchronous operation, we use a `future` parameter to indicate when the operation has been 67 | // completed. 68 | discovery.rxUnpublish(record.getRegistration()).subscribe(toObserver(future)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /microservices-exercises/src/main/java/io/vertx/workshop/exercise/microservice/Exercise2Verticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise.microservice; 2 | 3 | import io.vertx.reactivex.core.AbstractVerticle; 4 | import io.vertx.reactivex.ext.web.client.HttpResponse; 5 | import io.vertx.reactivex.ext.web.client.WebClient; 6 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 7 | 8 | /** 9 | * A verticle retrieving the service exposed by the Exercise1Verticle. 10 | * It uses the low-level API. 11 | */ 12 | public class Exercise2Verticle extends AbstractVerticle { 13 | 14 | private ServiceDiscovery discovery; 15 | 16 | @Override 17 | public void start() throws Exception { 18 | discovery = ServiceDiscovery.create(vertx); 19 | 20 | // 1 - Get the service record using `rxGetRecord`. Pass the lambda `svc -> svc.getName().equals("greetings")` as 21 | // parameter to retrieve the service with the name "greetings" 22 | // 2 - With the record (`.map`), get the service reference using `discovery.getReference` 23 | // 3 - With the reference (`.map`), get a WebClient (Vert.x http client) using `ref.getAs(WebClient.class)` 24 | // 4 - With the client (`.flatMapSingle`), invoke the service using: `client.get("/greetings/vert.x-low-level-api").rxSend()` 25 | // 5 - With the response (`.map`), extract the body as string (`bodyAsString` method) 26 | // 6 - Finally subscribe and print the result on the console 27 | // TODO 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /microservices-exercises/src/main/java/io/vertx/workshop/exercise/microservice/Exercise3Verticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise.microservice; 2 | 3 | import io.vertx.reactivex.core.AbstractVerticle; 4 | import io.vertx.reactivex.ext.web.client.HttpResponse; 5 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 6 | import io.vertx.reactivex.servicediscovery.types.HttpEndpoint; 7 | 8 | /** 9 | * A verticle retrieving the service exposed by the Exercise1Verticle. 10 | * It uses the {@link HttpEndpoint} class to retrieve the {@link io.vertx.reactivex.ext.web.client.WebClient}. 11 | */ 12 | public class Exercise3Verticle extends AbstractVerticle { 13 | 14 | @Override 15 | public void start() throws Exception { 16 | ServiceDiscovery discovery = ServiceDiscovery.create(vertx); 17 | 18 | // 1 - Get the Web Client using the `HttpEndpoint.rxGetWebClient` method. Use the same lambda as in the 19 | // previous exercise. 20 | // 2 - Invoke the HTTP service as in the previous exercise 21 | // 3 - Extract the body as String 22 | // 4 - Subscribe and display the result on the console 23 | // TODO 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /microservices-exercises/src/main/java/io/vertx/workshop/exercise/microservice/Main.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise.microservice; 2 | 3 | 4 | import io.vertx.reactivex.core.Vertx; 5 | 6 | /** 7 | * A main class deploying the verticles. 8 | */ 9 | public class Main { 10 | 11 | public static void main(String[] args) { 12 | Vertx vertx = Vertx.vertx(); 13 | 14 | vertx.rxDeployVerticle(Exercise1Verticle.class.getName()) 15 | .flatMap(x -> vertx.rxDeployVerticle(Exercise2Verticle.class.getName())) 16 | .flatMap(x -> vertx.rxDeployVerticle(Exercise3Verticle.class.getName())) 17 | .subscribe(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /portfolio-service/README.md: -------------------------------------------------------------------------------- 1 | # Portfolio service 2 | 3 | The portfolio service manages your portfolio: the available cash and the owned shares. It is exposed as an async RPC 4 | service on the event bus. It consumes the _consolidation_ endpoint from the quote generator and on every successful 5 | operation, it sends a message on the event bus. 6 | 7 | 8 | ## Build 9 | 10 | ``` 11 | mvn clean package 12 | ``` 13 | 14 | ## Deploy 15 | 16 | ``` 17 | mvn fabric8:deploy 18 | ``` 19 | -------------------------------------------------------------------------------- /portfolio-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.vertx.workshop 9 | vertx-kubernetes-workshop 10 | 1.0-SNAPSHOT 11 | 12 | 13 | portfolio-service 14 | 15 | 16 | io.vertx.workshop.portfolio.impl.PortfolioVerticle 17 | vertx-cluster 18 | ${project.basedir}/src/main/generated 19 | 20 | 21 | 22 | 23 | io.vertx 24 | vertx-config 25 | 26 | 27 | io.vertx 28 | vertx-infinispan 29 | 30 | 31 | org.infinispan 32 | infinispan-cloud 33 | 34 | 35 | io.vertx 36 | vertx-service-proxy 37 | 38 | 39 | io.vertx 40 | vertx-service-proxy 41 | processor 42 | 43 | 44 | io.vertx 45 | vertx-sockjs-service-proxy 46 | 47 | 48 | 49 | 50 | 51 | 52 | kotlin-maven-plugin 53 | org.jetbrains.kotlin 54 | 1.1.51 55 | 56 | 57 | 58 | 59 | ${basedir}/src/main/kotlin 60 | 61 | 62 | 63 | compile 64 | 65 | 66 | 67 | 68 | 69 | org.bsc.maven 70 | maven-processor-plugin 71 | 3.2.0 72 | 73 | 74 | generate-sources 75 | 76 | process 77 | 78 | generate-sources 79 | 80 | 81 | %4$s: %3$s - %5$s %6$s%n 82 | 83 | 84 | io.vertx.codegen.CodeGenProcessor 85 | 86 | 87 | ${project.basedir}/src/main 88 | 89 | generated 90 | 91 | ${project.build.directory}/generated-sources/annotations 92 | 93 | 94 | 95 | 96 | 97 | org.codehaus.mojo 98 | build-helper-maven-plugin 99 | 1.12 100 | 101 | 102 | add-source 103 | generate-sources 104 | 105 | add-source 106 | 107 | 108 | 109 | ${generated.dir} 110 | 111 | 112 | 113 | 114 | 115 | 116 | io.reactiverse 117 | vertx-maven-plugin 118 | 119 | 120 | io.fabric8 121 | fabric8-maven-plugin 122 | 123 | 124 | org.apache.maven.plugins 125 | maven-jar-plugin 126 | 3.0.2 127 | 128 | 129 | package 130 | 131 | jar 132 | 133 | 134 | client 135 | 136 | io/vertx/workshop/portfolio/* 137 | io/vertx/workshop/portfolio/reactivex/* 138 | io/vertx/workshop/portfolio/kotlin/* 139 | vertx-workshop-portfolio-js/* 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /portfolio-service/src/main/asciidoc/dataobjects.adoc: -------------------------------------------------------------------------------- 1 | = Cheatsheets 2 | 3 | [[Portfolio]] 4 | == Portfolio 5 | 6 | ++++ 7 | Structure representing a portfolio. It stores the available cash and the owned shares. 8 | ++++ 9 | ''' 10 | 11 | [cols=">25%,25%,50%"] 12 | [frame="topbot"] 13 | |=== 14 | ^|Name | Type ^| Description 15 | |[[cash]]`@cash`|`Number (double)`|+++ 16 | Sets the available cash. Method used by the converter. 17 | +++ 18 | |[[shares]]`@shares`|`Number (Integer)`|+++ 19 | Sets the owned shares. Method used by the converter. 20 | +++ 21 | |=== 22 | 23 | -------------------------------------------------------------------------------- /portfolio-service/src/main/fabric8/deployment.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | template: 3 | spec: 4 | containers: 5 | - name: vertx 6 | env: 7 | - name: KUBERNETES_NAMESPACE 8 | valueFrom: 9 | fieldRef: 10 | apiVersion: v1 11 | fieldPath: metadata.namespace 12 | - name: JAVA_OPTIONS 13 | value: '-Dvertx.cacheDirBase=/tmp -Dvertx.jgroups.config=default-configs/default-jgroups-kubernetes.xml -Djava.net.preferIPv4Stack=true' 14 | - name: JAVA_ARGS 15 | value: '-cluster' 16 | -------------------------------------------------------------------------------- /portfolio-service/src/main/generated/io/vertx/workshop/portfolio/PortfolioConverter.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.portfolio; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | import io.vertx.core.json.JsonArray; 5 | import java.time.Instant; 6 | import java.time.format.DateTimeFormatter; 7 | 8 | /** 9 | * Converter for {@link io.vertx.workshop.portfolio.Portfolio}. 10 | * NOTE: This class has been automatically generated from the {@link io.vertx.workshop.portfolio.Portfolio} original class using Vert.x codegen. 11 | */ 12 | public class PortfolioConverter { 13 | 14 | public static void fromJson(Iterable> json, Portfolio obj) { 15 | for (java.util.Map.Entry member : json) { 16 | switch (member.getKey()) { 17 | case "cash": 18 | if (member.getValue() instanceof Number) { 19 | obj.setCash(((Number)member.getValue()).doubleValue()); 20 | } 21 | break; 22 | case "shares": 23 | if (member.getValue() instanceof JsonObject) { 24 | java.util.Map map = new java.util.LinkedHashMap<>(); 25 | ((Iterable>)member.getValue()).forEach(entry -> { 26 | if (entry.getValue() instanceof Number) 27 | map.put(entry.getKey(), ((Number)entry.getValue()).intValue()); 28 | }); 29 | obj.setShares(map); 30 | } 31 | break; 32 | } 33 | } 34 | } 35 | 36 | public static void toJson(Portfolio obj, JsonObject json) { 37 | toJson(obj, json.getMap()); 38 | } 39 | 40 | public static void toJson(Portfolio obj, java.util.Map json) { 41 | json.put("cash", obj.getCash()); 42 | if (obj.getShares() != null) { 43 | JsonObject map = new JsonObject(); 44 | obj.getShares().forEach((key, value) -> map.put(key, value)); 45 | json.put("shares", map); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /portfolio-service/src/main/generated/io/vertx/workshop/portfolio/reactivex/PortfolioService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.workshop.portfolio.reactivex; 18 | 19 | import java.util.Map; 20 | import io.reactivex.Observable; 21 | import io.reactivex.Flowable; 22 | import io.reactivex.Single; 23 | import io.reactivex.Completable; 24 | import io.reactivex.Maybe; 25 | import io.vertx.core.json.JsonObject; 26 | import io.vertx.core.AsyncResult; 27 | import io.vertx.workshop.portfolio.Portfolio; 28 | import io.vertx.core.Handler; 29 | 30 | /** 31 | * A service managing a portfolio. 32 | *

33 | * This service is an event bus service (a.k.a service proxies, or async RPC). The client and server are generated at 34 | * compile time. 35 | *

36 | * All method are asynchronous and so ends with a parameter. 37 | * 38 | *

39 | * NOTE: This class has been automatically generated from the {@link io.vertx.workshop.portfolio.PortfolioService original} non RX-ified interface using Vert.x codegen. 40 | */ 41 | 42 | @io.vertx.lang.reactivex.RxGen(io.vertx.workshop.portfolio.PortfolioService.class) 43 | public class PortfolioService { 44 | 45 | public static final io.vertx.lang.reactivex.TypeArg __TYPE_ARG = new io.vertx.lang.reactivex.TypeArg<>( 46 | obj -> new PortfolioService((io.vertx.workshop.portfolio.PortfolioService) obj), 47 | PortfolioService::getDelegate 48 | ); 49 | 50 | private final io.vertx.workshop.portfolio.PortfolioService delegate; 51 | 52 | public PortfolioService(io.vertx.workshop.portfolio.PortfolioService delegate) { 53 | this.delegate = delegate; 54 | } 55 | 56 | public io.vertx.workshop.portfolio.PortfolioService getDelegate() { 57 | return delegate; 58 | } 59 | 60 | /** 61 | * Gets the portfolio. 62 | * @param resultHandler the result handler called when the portfolio has been retrieved. The async result indicates whether the call was successful or not. 63 | */ 64 | public void getPortfolio(Handler> resultHandler) { 65 | delegate.getPortfolio(resultHandler); 66 | } 67 | 68 | /** 69 | * Gets the portfolio. 70 | * @return 71 | */ 72 | public Single rxGetPortfolio() { 73 | return new io.vertx.reactivex.core.impl.AsyncResultSingle(handler -> { 74 | getPortfolio(handler); 75 | }); 76 | } 77 | 78 | /** 79 | * Buy `amount` shares of the given shares (quote). 80 | * @param amount the amount 81 | * @param quote the last quote 82 | * @param resultHandler the result handler with the updated portfolio. If the action cannot be executed, the async result is market as a failure (not enough money, not enough shares available...) 83 | */ 84 | public void buy(int amount, JsonObject quote, Handler> resultHandler) { 85 | delegate.buy(amount, quote, resultHandler); 86 | } 87 | 88 | /** 89 | * Buy `amount` shares of the given shares (quote). 90 | * @param amount the amount 91 | * @param quote the last quote 92 | * @return 93 | */ 94 | public Single rxBuy(int amount, JsonObject quote) { 95 | return new io.vertx.reactivex.core.impl.AsyncResultSingle(handler -> { 96 | buy(amount, quote, handler); 97 | }); 98 | } 99 | 100 | /** 101 | * Sell `amount` shares of the given shares (quote). 102 | * @param amount the amount 103 | * @param quote the last quote 104 | * @param resultHandler the result handler with the updated portfolio. If the action cannot be executed, the async result is market as a failure (not enough share...) 105 | */ 106 | public void sell(int amount, JsonObject quote, Handler> resultHandler) { 107 | delegate.sell(amount, quote, resultHandler); 108 | } 109 | 110 | /** 111 | * Sell `amount` shares of the given shares (quote). 112 | * @param amount the amount 113 | * @param quote the last quote 114 | * @return 115 | */ 116 | public Single rxSell(int amount, JsonObject quote) { 117 | return new io.vertx.reactivex.core.impl.AsyncResultSingle(handler -> { 118 | sell(amount, quote, handler); 119 | }); 120 | } 121 | 122 | /** 123 | * Evaluates the current value of the portfolio. 124 | * @param resultHandler the result handler with the valuation 125 | */ 126 | public void evaluate(Handler> resultHandler) { 127 | delegate.evaluate(resultHandler); 128 | } 129 | 130 | /** 131 | * Evaluates the current value of the portfolio. 132 | * @return 133 | */ 134 | public Single rxEvaluate() { 135 | return new io.vertx.reactivex.core.impl.AsyncResultSingle(handler -> { 136 | evaluate(handler); 137 | }); 138 | } 139 | 140 | 141 | public static PortfolioService newInstance(io.vertx.workshop.portfolio.PortfolioService arg) { 142 | return arg != null ? new PortfolioService(arg) : null; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /portfolio-service/src/main/generated/vertx-workshop-portfolio-js/portfolio_service-proxy.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | 18 | /** 19 | A service managing a portfolio. 20 |

21 | This service is an event bus service (a.k.a service proxies, or async RPC). The client and server are generated at 22 | compile time. 23 |

24 | @class 25 | */ 26 | export default class PortfolioService { 27 | 28 | constructor (eb: any, address: string); 29 | 30 | getPortfolio(resultHandler: (err: any, result: any) => any) : void; 31 | 32 | buy(amount: number, quote: Object, resultHandler: (err: any, result: any) => any) : void; 33 | 34 | sell(amount: number, quote: Object, resultHandler: (err: any, result: any) => any) : void; 35 | 36 | evaluate(resultHandler: (err: any, result: any) => any) : void; 37 | } -------------------------------------------------------------------------------- /portfolio-service/src/main/generated/vertx-workshop-portfolio-js/portfolio_service-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /// 18 | 19 | /** @module vertx-workshop-portfolio-js/portfolio_service */ 20 | !function (factory) { 21 | if (typeof require === 'function' && typeof module !== 'undefined') { 22 | factory(); 23 | } else if (typeof define === 'function' && define.amd) { 24 | // AMD loader 25 | define('vertx-workshop-portfolio-js/portfolio_service-proxy', [], factory); 26 | } else { 27 | // plain old include 28 | PortfolioService = factory(); 29 | } 30 | }(function () { 31 | 32 | /** 33 | A service managing a portfolio. 34 |

35 | This service is an event bus service (a.k.a service proxies, or async RPC). The client and server are generated at 36 | compile time. 37 |

38 | @class 39 | */ 40 | var PortfolioService = function(eb, address) { 41 | var j_eb = eb; 42 | var j_address = address; 43 | var closed = false; 44 | var that = this; 45 | var convCharCollection = function(coll) { 46 | var ret = []; 47 | for (var i = 0;i < coll.length;i++) { 48 | ret.push(String.fromCharCode(coll[i])); 49 | } 50 | return ret; 51 | }; 52 | 53 | /** 54 | Gets the portfolio. 55 | 56 | @public 57 | @param resultHandler {function} the result handler called when the portfolio has been retrieved. The async result indicates whether the call was successful or not. 58 | */ 59 | this.getPortfolio = function(resultHandler) { 60 | var __args = arguments; 61 | if (__args.length === 1 && typeof __args[0] === 'function') { 62 | if (closed) { 63 | throw new Error('Proxy is closed'); 64 | } 65 | j_eb.send(j_address, {}, {"action":"getPortfolio"}, function(err, result) { __args[0](err, result && result.body); }); 66 | return; 67 | } else throw new TypeError('function invoked with invalid arguments'); 68 | }; 69 | 70 | /** 71 | Buy `amount` shares of the given shares (quote). 72 | 73 | @public 74 | @param amount {number} the amount 75 | @param quote {Object} the last quote 76 | @param resultHandler {function} the result handler with the updated portfolio. If the action cannot be executed, the async result is market as a failure (not enough money, not enough shares available...) 77 | */ 78 | this.buy = function(amount, quote, resultHandler) { 79 | var __args = arguments; 80 | if (__args.length === 3 && typeof __args[0] ==='number' && (typeof __args[1] === 'object' && __args[1] != null) && typeof __args[2] === 'function') { 81 | if (closed) { 82 | throw new Error('Proxy is closed'); 83 | } 84 | j_eb.send(j_address, {"amount":__args[0], "quote":__args[1]}, {"action":"buy"}, function(err, result) { __args[2](err, result && result.body); }); 85 | return; 86 | } else throw new TypeError('function invoked with invalid arguments'); 87 | }; 88 | 89 | /** 90 | Sell `amount` shares of the given shares (quote). 91 | 92 | @public 93 | @param amount {number} the amount 94 | @param quote {Object} the last quote 95 | @param resultHandler {function} the result handler with the updated portfolio. If the action cannot be executed, the async result is market as a failure (not enough share...) 96 | */ 97 | this.sell = function(amount, quote, resultHandler) { 98 | var __args = arguments; 99 | if (__args.length === 3 && typeof __args[0] ==='number' && (typeof __args[1] === 'object' && __args[1] != null) && typeof __args[2] === 'function') { 100 | if (closed) { 101 | throw new Error('Proxy is closed'); 102 | } 103 | j_eb.send(j_address, {"amount":__args[0], "quote":__args[1]}, {"action":"sell"}, function(err, result) { __args[2](err, result && result.body); }); 104 | return; 105 | } else throw new TypeError('function invoked with invalid arguments'); 106 | }; 107 | 108 | /** 109 | Evaluates the current value of the portfolio. 110 | 111 | @public 112 | @param resultHandler {function} the result handler with the valuation 113 | */ 114 | this.evaluate = function(resultHandler) { 115 | var __args = arguments; 116 | if (__args.length === 1 && typeof __args[0] === 'function') { 117 | if (closed) { 118 | throw new Error('Proxy is closed'); 119 | } 120 | j_eb.send(j_address, {}, {"action":"evaluate"}, function(err, result) { __args[0](err, result && result.body); }); 121 | return; 122 | } else throw new TypeError('function invoked with invalid arguments'); 123 | }; 124 | 125 | }; 126 | 127 | if (typeof exports !== 'undefined') { 128 | if (typeof module !== 'undefined' && module.exports) { 129 | exports = module.exports = PortfolioService; 130 | } else { 131 | exports.PortfolioService = PortfolioService; 132 | } 133 | } else { 134 | return PortfolioService; 135 | } 136 | }); -------------------------------------------------------------------------------- /portfolio-service/src/main/generated/vertx-workshop-portfolio-js/portfolio_service.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** @module vertx-workshop-portfolio-js/portfolio_service */ 18 | var utils = require('vertx-js/util/utils'); 19 | 20 | var io = Packages.io; 21 | var JsonObject = io.vertx.core.json.JsonObject; 22 | var JPortfolioService = Java.type('io.vertx.workshop.portfolio.PortfolioService'); 23 | var Portfolio = Java.type('io.vertx.workshop.portfolio.Portfolio'); 24 | 25 | /** 26 | A service managing a portfolio. 27 |

28 | This service is an event bus service (a.k.a service proxies, or async RPC). The client and server are generated at 29 | compile time. 30 |

31 | @class 32 | */ 33 | var PortfolioService = function(j_val) { 34 | 35 | var j_portfolioService = j_val; 36 | var that = this; 37 | 38 | /** 39 | Gets the portfolio. 40 | 41 | @public 42 | @param resultHandler {function} the result handler called when the portfolio has been retrieved. The async result indicates whether the call was successful or not. 43 | */ 44 | this.getPortfolio = function(resultHandler) { 45 | var __args = arguments; 46 | if (__args.length === 1 && typeof __args[0] === 'function') { 47 | j_portfolioService["getPortfolio(io.vertx.core.Handler)"](function(ar) { 48 | if (ar.succeeded()) { 49 | resultHandler(utils.convReturnDataObject(ar.result()), null); 50 | } else { 51 | resultHandler(null, ar.cause()); 52 | } 53 | }); 54 | } else throw new TypeError('function invoked with invalid arguments'); 55 | }; 56 | 57 | /** 58 | Buy `amount` shares of the given shares (quote). 59 | 60 | @public 61 | @param amount {number} the amount 62 | @param quote {Object} the last quote 63 | @param resultHandler {function} the result handler with the updated portfolio. If the action cannot be executed, the async result is market as a failure (not enough money, not enough shares available...) 64 | */ 65 | this.buy = function(amount, quote, resultHandler) { 66 | var __args = arguments; 67 | if (__args.length === 3 && typeof __args[0] ==='number' && (typeof __args[1] === 'object' && __args[1] != null) && typeof __args[2] === 'function') { 68 | j_portfolioService["buy(int,io.vertx.core.json.JsonObject,io.vertx.core.Handler)"](amount, utils.convParamJsonObject(quote), function(ar) { 69 | if (ar.succeeded()) { 70 | resultHandler(utils.convReturnDataObject(ar.result()), null); 71 | } else { 72 | resultHandler(null, ar.cause()); 73 | } 74 | }); 75 | } else throw new TypeError('function invoked with invalid arguments'); 76 | }; 77 | 78 | /** 79 | Sell `amount` shares of the given shares (quote). 80 | 81 | @public 82 | @param amount {number} the amount 83 | @param quote {Object} the last quote 84 | @param resultHandler {function} the result handler with the updated portfolio. If the action cannot be executed, the async result is market as a failure (not enough share...) 85 | */ 86 | this.sell = function(amount, quote, resultHandler) { 87 | var __args = arguments; 88 | if (__args.length === 3 && typeof __args[0] ==='number' && (typeof __args[1] === 'object' && __args[1] != null) && typeof __args[2] === 'function') { 89 | j_portfolioService["sell(int,io.vertx.core.json.JsonObject,io.vertx.core.Handler)"](amount, utils.convParamJsonObject(quote), function(ar) { 90 | if (ar.succeeded()) { 91 | resultHandler(utils.convReturnDataObject(ar.result()), null); 92 | } else { 93 | resultHandler(null, ar.cause()); 94 | } 95 | }); 96 | } else throw new TypeError('function invoked with invalid arguments'); 97 | }; 98 | 99 | /** 100 | Evaluates the current value of the portfolio. 101 | 102 | @public 103 | @param resultHandler {function} the result handler with the valuation 104 | */ 105 | this.evaluate = function(resultHandler) { 106 | var __args = arguments; 107 | if (__args.length === 1 && typeof __args[0] === 'function') { 108 | j_portfolioService["evaluate(io.vertx.core.Handler)"](function(ar) { 109 | if (ar.succeeded()) { 110 | resultHandler(ar.result(), null); 111 | } else { 112 | resultHandler(null, ar.cause()); 113 | } 114 | }); 115 | } else throw new TypeError('function invoked with invalid arguments'); 116 | }; 117 | 118 | // A reference to the underlying Java delegate 119 | // NOTE! This is an internal API and must not be used in user code. 120 | // If you rely on this property your code is likely to break if we change it / remove it without warning. 121 | this._jdel = j_portfolioService; 122 | }; 123 | 124 | PortfolioService._jclass = utils.getJavaClass("io.vertx.workshop.portfolio.PortfolioService"); 125 | PortfolioService._jtype = { 126 | accept: function(obj) { 127 | return PortfolioService._jclass.isInstance(obj._jdel); 128 | }, 129 | wrap: function(jdel) { 130 | var obj = Object.create(PortfolioService.prototype, {}); 131 | PortfolioService.apply(obj, arguments); 132 | return obj; 133 | }, 134 | unwrap: function(obj) { 135 | return obj._jdel; 136 | } 137 | }; 138 | PortfolioService._create = function(jdel) { 139 | var obj = Object.create(PortfolioService.prototype, {}); 140 | PortfolioService.apply(obj, arguments); 141 | return obj; 142 | } 143 | module.exports = PortfolioService; -------------------------------------------------------------------------------- /portfolio-service/src/main/java/io/vertx/workshop/portfolio/Portfolio.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.portfolio; 2 | 3 | import io.vertx.codegen.annotations.DataObject; 4 | import io.vertx.core.json.JsonObject; 5 | 6 | import java.util.Map; 7 | import java.util.TreeMap; 8 | 9 | /** 10 | * Structure representing a portfolio. It stores the available cash and the owned shares. 11 | */ 12 | @DataObject(generateConverter = true) 13 | public class Portfolio { 14 | 15 | private Map shares = new TreeMap<>(); 16 | 17 | private double cash; 18 | 19 | /** 20 | * Creates a new instance of {@link Portfolio}. 21 | */ 22 | public Portfolio() { 23 | // Empty constructor 24 | } 25 | 26 | /** 27 | * Creates a new instance of {@link Portfolio} by copying the other instance. 28 | * 29 | * @param other the instance to copy 30 | */ 31 | public Portfolio(Portfolio other) { 32 | this.shares = new TreeMap<>(other.shares); 33 | this.cash = other.cash; 34 | } 35 | 36 | /** 37 | * Creates a new instance of {@link Portfolio} from ths json object. 38 | * 39 | * @param json the json object 40 | */ 41 | public Portfolio(JsonObject json) { 42 | // A converter is generated to easy the conversion from and to JSON. 43 | PortfolioConverter.fromJson(json, this); 44 | } 45 | 46 | /** 47 | * @return a JSON representation of the portfolio computed using the converter. 48 | */ 49 | public JsonObject toJson() { 50 | JsonObject json = new JsonObject(); 51 | PortfolioConverter.toJson(this, json); 52 | return json; 53 | } 54 | 55 | /** 56 | * @return the owned shared (name -> number) 57 | */ 58 | public Map getShares() { 59 | return shares; 60 | } 61 | 62 | /** 63 | * Sets the owned shares. Method used by the converter. 64 | * 65 | * @param shares the shares 66 | * @return the current {@link Portfolio} 67 | */ 68 | public Portfolio setShares(Map shares) { 69 | this.shares = shares; 70 | return this; 71 | } 72 | 73 | /** 74 | * @return the available cash. 75 | */ 76 | public double getCash() { 77 | return cash; 78 | } 79 | 80 | /** 81 | * Sets the available cash. Method used by the converter. 82 | * 83 | * @param cash the cash 84 | * @return the current {@link Portfolio} 85 | */ 86 | public Portfolio setCash(double cash) { 87 | this.cash = cash; 88 | return this; 89 | } 90 | 91 | // -- Additional method 92 | 93 | /** 94 | * This method is just a convenient method to get the number of owned shares of the specify company (name of the 95 | * company). 96 | * 97 | * @param name the name of the company 98 | * @return the number of owned shares, {@literal 0} is none. 99 | */ 100 | public int getAmount(String name) { 101 | Integer current = shares.get(name); 102 | if (current == null) { 103 | return 0; 104 | } 105 | return current; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /portfolio-service/src/main/java/io/vertx/workshop/portfolio/PortfolioService.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.portfolio; 2 | 3 | import io.vertx.codegen.annotations.ProxyGen; 4 | import io.vertx.codegen.annotations.VertxGen; 5 | import io.vertx.core.AsyncResult; 6 | import io.vertx.core.Handler; 7 | import io.vertx.core.json.JsonObject; 8 | 9 | 10 | /** 11 | * A service managing a portfolio. 12 | *

13 | * This service is an event bus service (a.k.a service proxies, or async RPC). The client and server are generated at 14 | * compile time. 15 | *

16 | * All method are asynchronous and so ends with a {@link Handler} parameter. 17 | */ 18 | @VertxGen 19 | @ProxyGen 20 | public interface PortfolioService { 21 | 22 | /** 23 | * The address on which the service is published. 24 | */ 25 | String ADDRESS = "service.portfolio"; 26 | 27 | /** 28 | * The address on which the successful action are sent. 29 | */ 30 | String EVENT_ADDRESS = "portfolio"; 31 | 32 | /** 33 | * Gets the portfolio. 34 | * 35 | * @param resultHandler the result handler called when the portfolio has been retrieved. The async result indicates 36 | * whether the call was successful or not. 37 | */ 38 | void getPortfolio(Handler> resultHandler); 39 | 40 | /** 41 | * Buy `amount` shares of the given shares (quote). 42 | * 43 | * @param amount the amount 44 | * @param quote the last quote 45 | * @param resultHandler the result handler with the updated portfolio. If the action cannot be executed, the async 46 | * result is market as a failure (not enough money, not enough shares available...) 47 | */ 48 | void buy(int amount, JsonObject quote, Handler> resultHandler); 49 | 50 | /** 51 | * Sell `amount` shares of the given shares (quote). 52 | * 53 | * @param amount the amount 54 | * @param quote the last quote 55 | * @param resultHandler the result handler with the updated portfolio. If the action cannot be executed, the async 56 | * result is market as a failure (not enough share...) 57 | */ 58 | void sell(int amount, JsonObject quote, Handler> resultHandler); 59 | 60 | /** 61 | * Evaluates the current value of the portfolio. 62 | * 63 | * @param resultHandler the result handler with the valuation 64 | */ 65 | void evaluate(Handler> resultHandler); 66 | 67 | } 68 | -------------------------------------------------------------------------------- /portfolio-service/src/main/java/io/vertx/workshop/portfolio/impl/PortfolioServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.portfolio.impl; 2 | 3 | import io.reactivex.Flowable; 4 | import io.reactivex.Single; 5 | import io.vertx.core.AsyncResult; 6 | import io.vertx.core.Future; 7 | import io.vertx.core.Handler; 8 | import io.vertx.core.json.JsonObject; 9 | import io.vertx.reactivex.core.Vertx; 10 | import io.vertx.reactivex.ext.web.client.HttpResponse; 11 | import io.vertx.reactivex.ext.web.client.WebClient; 12 | import io.vertx.reactivex.ext.web.codec.BodyCodec; 13 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 14 | import io.vertx.reactivex.servicediscovery.types.HttpEndpoint; 15 | import io.vertx.workshop.portfolio.Portfolio; 16 | import io.vertx.workshop.portfolio.PortfolioService; 17 | 18 | import java.io.UnsupportedEncodingException; 19 | import java.net.URLEncoder; 20 | 21 | /** 22 | * The portfolio service implementation. 23 | */ 24 | public class PortfolioServiceImpl implements PortfolioService { 25 | 26 | private final Vertx vertx; 27 | private final Portfolio portfolio; 28 | private final ServiceDiscovery discovery; 29 | 30 | public PortfolioServiceImpl(Vertx vertx, ServiceDiscovery discovery, double initialCash) { 31 | this.vertx = vertx; 32 | this.portfolio = new Portfolio().setCash(initialCash); 33 | this.discovery = discovery; 34 | } 35 | 36 | @Override 37 | public void getPortfolio(Handler> resultHandler) { 38 | // TODO Call the given handler with a successful Async Result encapsulating the `portfolio` object 39 | // The async result instance is created using `Future.succeededFuture` 40 | // ---- 41 | 42 | // ---- 43 | } 44 | 45 | private void sendActionOnTheEventBus(String action, int amount, JsonObject quote, int newAmount) { 46 | // TODO Broadcast a JSON message to the `EVENT_ADDRESS` containing the following keys: "action", "quote", "date" 47 | // (use System.currentTimeMillis()), "amount" and "owned" (newAmount) 48 | // ---- 49 | 50 | // ---- 51 | } 52 | 53 | @Override 54 | public void evaluate(Handler> resultHandler) { 55 | // TODO 56 | // ---- 57 | 58 | // --- 59 | } 60 | 61 | private void computeEvaluation(WebClient webClient, Handler> resultHandler) { 62 | // We need to call the service for each company in which we own shares 63 | Flowable.fromIterable(portfolio.getShares().entrySet()) 64 | // For each, we retrieve the value 65 | .flatMapSingle(entry -> getValueForCompany(webClient, entry.getKey(), entry.getValue())) 66 | // We accumulate the results 67 | .toList() 68 | // And compute the sum 69 | .map(list -> list.stream().mapToDouble(x -> x).sum()) 70 | // We report the result or failure 71 | .subscribe((sum, err) -> { 72 | if (err != null) { 73 | resultHandler.handle(Future.failedFuture(err)); 74 | } else { 75 | resultHandler.handle(Future.succeededFuture(sum)); 76 | } 77 | }); 78 | } 79 | 80 | private Single getValueForCompany(WebClient client, String company, int numberOfShares) { 81 | //TODO 82 | //---- 83 | 84 | return Single.just(0.0); 85 | // --- 86 | 87 | 88 | } 89 | 90 | 91 | @Override 92 | public void buy(int amount, JsonObject quote, Handler> resultHandler) { 93 | if (amount <= 0) { 94 | resultHandler.handle(Future.failedFuture("Cannot buy " + quote.getString("name") + " - the amount must be " + 95 | "greater than 0")); 96 | return; 97 | } 98 | 99 | if (quote.getInteger("shares") < amount) { 100 | resultHandler.handle(Future.failedFuture("Cannot buy " + amount + " - not enough " + 101 | "stocks on the market (" + quote.getInteger("shares") + ")")); 102 | return; 103 | } 104 | 105 | double price = amount * quote.getDouble("ask"); 106 | String name = quote.getString("name"); 107 | // 1) do we have enough money 108 | if (portfolio.getCash() >= price) { 109 | // Yes, buy it 110 | portfolio.setCash(portfolio.getCash() - price); 111 | int current = portfolio.getAmount(name); 112 | int newAmount = current + amount; 113 | portfolio.getShares().put(name, newAmount); 114 | sendActionOnTheEventBus("BUY", amount, quote, newAmount); 115 | resultHandler.handle(Future.succeededFuture(portfolio)); 116 | } else { 117 | resultHandler.handle(Future.failedFuture("Cannot buy " + amount + " of " + name + " - " + "not enough money, " + 118 | "need " + price + ", has " + portfolio.getCash())); 119 | } 120 | } 121 | 122 | 123 | @Override 124 | public void sell(int amount, JsonObject quote, Handler> resultHandler) { 125 | if (amount <= 0) { 126 | resultHandler.handle(Future.failedFuture("Cannot sell " + quote.getString("name") + " - the amount must be " + 127 | "greater than 0")); 128 | return; 129 | } 130 | 131 | double price = amount * quote.getDouble("bid"); 132 | String name = quote.getString("name"); 133 | int current = portfolio.getAmount(name); 134 | // 1) do we have enough stocks 135 | if (current >= amount) { 136 | // Yes, sell it 137 | int newAmount = current - amount; 138 | if (newAmount == 0) { 139 | portfolio.getShares().remove(name); 140 | } else { 141 | portfolio.getShares().put(name, newAmount); 142 | } 143 | portfolio.setCash(portfolio.getCash() + price); 144 | sendActionOnTheEventBus("SELL", amount, quote, newAmount); 145 | resultHandler.handle(Future.succeededFuture(portfolio)); 146 | } else { 147 | resultHandler.handle(Future.failedFuture("Cannot sell " + amount + " of " + name + " - " + "not enough stocks " + 148 | "in portfolio")); 149 | } 150 | 151 | } 152 | 153 | private static String encode(String value) { 154 | try { 155 | return URLEncoder.encode(value, "UTF-8"); 156 | } catch (UnsupportedEncodingException e) { 157 | throw new RuntimeException("Unsupported encoding"); 158 | } 159 | } 160 | 161 | 162 | } 163 | -------------------------------------------------------------------------------- /portfolio-service/src/main/java/io/vertx/workshop/portfolio/impl/PortfolioVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.portfolio.impl; 2 | 3 | import io.vertx.reactivex.core.AbstractVerticle; 4 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 5 | import io.vertx.reactivex.servicediscovery.types.EventBusService; 6 | import io.vertx.servicediscovery.Record; 7 | import io.vertx.serviceproxy.ServiceBinder; 8 | import io.vertx.workshop.portfolio.PortfolioService; 9 | 10 | import static io.vertx.workshop.portfolio.PortfolioService.ADDRESS; 11 | 12 | /** 13 | * A verticle publishing the portfolio service. 14 | */ 15 | public class PortfolioVerticle extends AbstractVerticle { 16 | 17 | private Record record; 18 | private ServiceDiscovery discovery; 19 | 20 | @Override 21 | public void start() { 22 | 23 | ServiceDiscovery.create(vertx, discovery -> { 24 | this.discovery = discovery; 25 | // Create the service object 26 | PortfolioServiceImpl service = new PortfolioServiceImpl(vertx, discovery, config().getDouble("money", 10000.00)); 27 | 28 | // Register the service proxy on the event bus 29 | ServiceBinder binder = new ServiceBinder(vertx.getDelegate()).setAddress(ADDRESS); 30 | binder.register(PortfolioService.class, service); 31 | 32 | Record record = EventBusService.createRecord("portfolio", ADDRESS, PortfolioService.class.getName()); 33 | discovery.publish(record, ar -> { 34 | if (ar.succeeded()) { 35 | this.record = record; 36 | System.out.println("Portfolio service published"); 37 | 38 | // Used for health check 39 | vertx.createHttpServer().requestHandler(req -> req.response().end("OK")).listen(8080); 40 | } else { 41 | ar.cause().printStackTrace(); 42 | } 43 | }); 44 | 45 | }); 46 | } 47 | 48 | @Override 49 | public void stop() throws Exception { 50 | if (record != null) { 51 | discovery.unpublish(record.getRegistration(), v -> {}); 52 | } 53 | discovery.close(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /portfolio-service/src/main/java/io/vertx/workshop/portfolio/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Indicates that this module contains classes that need to be generated / processed. 3 | */ 4 | @ModuleGen(name = "vertx-workshop-portfolio", groupPackage = "io.vertx.workshop.portfolio") 5 | package io.vertx.workshop.portfolio; 6 | 7 | import io.vertx.codegen.annotations.ModuleGen; -------------------------------------------------------------------------------- /portfolio-service/src/main/kotlin/io/vertx/workshop/portfolio/kotlin/Portfolio.kt: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.portfolio.kotlin 2 | 3 | import io.vertx.workshop.portfolio.Portfolio 4 | 5 | /** 6 | * A function providing a DSL for building [io.vertx.workshop.portfolio.Portfolio] objects. 7 | * 8 | * Structure representing a portfolio. It stores the available cash and the owned shares. 9 | * 10 | * @param cash Sets the available cash. Method used by the converter. 11 | * @param shares Sets the owned shares. Method used by the converter. 12 | * 13 | *

14 | * NOTE: This function has been automatically generated from the [io.vertx.workshop.portfolio.Portfolio original] using Vert.x codegen. 15 | */ 16 | fun Portfolio( 17 | cash: Double? = null, 18 | shares: Map? = null): Portfolio = io.vertx.workshop.portfolio.Portfolio().apply { 19 | 20 | if (cash != null) { 21 | this.setCash(cash) 22 | } 23 | if (shares != null) { 24 | this.setShares(shares) 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /portfolio-service/src/main/solution/io/vertx/workshop/portfolio/Portfolio.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.portfolio; 2 | 3 | import io.vertx.codegen.annotations.DataObject; 4 | import io.vertx.core.json.JsonObject; 5 | 6 | import java.util.Map; 7 | import java.util.TreeMap; 8 | 9 | /** 10 | * Structure representing a portfolio. It stores the available cash and the owned shares. 11 | */ 12 | @DataObject(generateConverter = true) 13 | public class Portfolio { 14 | 15 | private Map shares = new TreeMap<>(); 16 | 17 | private double cash; 18 | 19 | /** 20 | * Creates a new instance of {@link Portfolio}. 21 | */ 22 | public Portfolio() { 23 | // Empty constructor 24 | } 25 | 26 | /** 27 | * Creates a new instance of {@link Portfolio} by copying the other instance. 28 | * 29 | * @param other the instance to copy 30 | */ 31 | public Portfolio(Portfolio other) { 32 | this.shares = new TreeMap<>(other.shares); 33 | this.cash = other.cash; 34 | } 35 | 36 | /** 37 | * Creates a new instance of {@link Portfolio} from ths json object. 38 | * 39 | * @param json the json object 40 | */ 41 | public Portfolio(JsonObject json) { 42 | // A converter is generated to easy the conversion from and to JSON. 43 | PortfolioConverter.fromJson(json, this); 44 | } 45 | 46 | /** 47 | * @return a JSON representation of the portfolio computed using the converter. 48 | */ 49 | public JsonObject toJson() { 50 | JsonObject json = new JsonObject(); 51 | PortfolioConverter.toJson(this, json); 52 | return json; 53 | } 54 | 55 | /** 56 | * @return the owned shared (name -> number) 57 | */ 58 | public Map getShares() { 59 | return shares; 60 | } 61 | 62 | /** 63 | * Sets the owned shares. Method used by the converter. 64 | * 65 | * @param shares the shares 66 | * @return the current {@link Portfolio} 67 | */ 68 | public Portfolio setShares(Map shares) { 69 | this.shares = shares; 70 | return this; 71 | } 72 | 73 | /** 74 | * @return the available cash. 75 | */ 76 | public double getCash() { 77 | return cash; 78 | } 79 | 80 | /** 81 | * Sets the available cash. Method used by the converter. 82 | * 83 | * @param cash the cash 84 | * @return the current {@link Portfolio} 85 | */ 86 | public Portfolio setCash(double cash) { 87 | this.cash = cash; 88 | return this; 89 | } 90 | 91 | // -- Additional method 92 | 93 | /** 94 | * This method is just a convenient method to get the number of owned shares of the specify company (name of the 95 | * company). 96 | * 97 | * @param name the name of the company 98 | * @return the number of owned shares, {@literal 0} is none. 99 | */ 100 | public int getAmount(String name) { 101 | Integer current = shares.get(name); 102 | if (current == null) { 103 | return 0; 104 | } 105 | return current; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /portfolio-service/src/main/solution/io/vertx/workshop/portfolio/PortfolioService.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.portfolio; 2 | 3 | import io.vertx.codegen.annotations.ProxyGen; 4 | import io.vertx.codegen.annotations.VertxGen; 5 | import io.vertx.core.AsyncResult; 6 | import io.vertx.core.Handler; 7 | import io.vertx.core.json.JsonObject; 8 | 9 | 10 | /** 11 | * A service managing a portfolio. 12 | *

13 | * This service is an event bus service (a.k.a service proxies, or async RPC). The client and server are generated at 14 | * compile time. 15 | *

16 | * All method are asynchronous and so ends with a {@link Handler} parameter. 17 | */ 18 | @VertxGen 19 | @ProxyGen 20 | public interface PortfolioService { 21 | 22 | /** 23 | * The address on which the service is published. 24 | */ 25 | String ADDRESS = "service.portfolio"; 26 | 27 | /** 28 | * The address on which the successful action are sent. 29 | */ 30 | String EVENT_ADDRESS = "portfolio"; 31 | 32 | /** 33 | * Gets the portfolio. 34 | * 35 | * @param resultHandler the result handler called when the portfolio has been retrieved. The async result indicates 36 | * whether the call was successful or not. 37 | */ 38 | void getPortfolio(Handler> resultHandler); 39 | 40 | /** 41 | * Buy `amount` shares of the given shares (quote). 42 | * 43 | * @param amount the amount 44 | * @param quote the last quote 45 | * @param resultHandler the result handler with the updated portfolio. If the action cannot be executed, the async 46 | * result is market as a failure (not enough money, not enough shares available...) 47 | */ 48 | void buy(int amount, JsonObject quote, Handler> resultHandler); 49 | 50 | /** 51 | * Sell `amount` shares of the given shares (quote). 52 | * 53 | * @param amount the amount 54 | * @param quote the last quote 55 | * @param resultHandler the result handler with the updated portfolio. If the action cannot be executed, the async 56 | * result is market as a failure (not enough share...) 57 | */ 58 | void sell(int amount, JsonObject quote, Handler> resultHandler); 59 | 60 | /** 61 | * Evaluates the current value of the portfolio. 62 | * 63 | * @param resultHandler the result handler with the valuation 64 | */ 65 | void evaluate(Handler> resultHandler); 66 | 67 | } 68 | -------------------------------------------------------------------------------- /portfolio-service/src/main/solution/io/vertx/workshop/portfolio/impl/PortfolioVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.portfolio.impl; 2 | 3 | import io.vertx.reactivex.core.AbstractVerticle; 4 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 5 | import io.vertx.reactivex.servicediscovery.types.EventBusService; 6 | import io.vertx.servicediscovery.Record; 7 | import io.vertx.serviceproxy.ServiceBinder; 8 | import io.vertx.workshop.portfolio.PortfolioService; 9 | 10 | import static io.vertx.workshop.portfolio.PortfolioService.ADDRESS; 11 | 12 | /** 13 | * A verticle publishing the portfolio service. 14 | */ 15 | public class PortfolioVerticle extends AbstractVerticle { 16 | 17 | private Record record; 18 | private ServiceDiscovery discovery; 19 | 20 | @Override 21 | public void start() { 22 | 23 | ServiceDiscovery.create(vertx, discovery -> { 24 | this.discovery = discovery; 25 | // Create the service object 26 | PortfolioServiceImpl service = new PortfolioServiceImpl(vertx, discovery, config().getDouble("money", 10000.00)); 27 | 28 | // Register the service proxy on the event bus 29 | ServiceBinder binder = new ServiceBinder(vertx.getDelegate()).setAddress(ADDRESS); 30 | binder.register(PortfolioService.class, service); 31 | 32 | Record record = EventBusService.createRecord("portfolio", ADDRESS, PortfolioService.class.getName()); 33 | discovery.publish(record, ar -> { 34 | if (ar.succeeded()) { 35 | this.record = record; 36 | System.out.println("Portfolio service published"); 37 | 38 | // Used for health check 39 | vertx.createHttpServer().requestHandler(req -> req.response().end("OK")).listen(8080); 40 | } else { 41 | ar.cause().printStackTrace(); 42 | } 43 | }); 44 | 45 | }); 46 | } 47 | 48 | @Override 49 | public void stop() throws Exception { 50 | if (record != null) { 51 | discovery.unpublish(record.getRegistration(), v -> {}); 52 | } 53 | discovery.close(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /portfolio-service/src/main/solution/io/vertx/workshop/portfolio/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Indicates that this module contains classes that need to be generated / processed. 3 | */ 4 | @ModuleGen(name = "vertx-workshop-portfolio", groupPackage = "io.vertx.workshop.portfolio") 5 | package io.vertx.workshop.portfolio; 6 | 7 | import io.vertx.codegen.annotations.ModuleGen; -------------------------------------------------------------------------------- /portfolio-service/src/test/java/io/vertx/workshop/portfolio/impl/PortfolioServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.portfolio.impl; 2 | 3 | import io.vertx.core.Vertx; 4 | import io.vertx.core.json.JsonObject; 5 | import io.vertx.ext.unit.Async; 6 | import io.vertx.ext.unit.TestContext; 7 | import io.vertx.ext.unit.junit.VertxUnitRunner; 8 | import io.vertx.serviceproxy.ServiceProxyBuilder; 9 | import io.vertx.workshop.portfolio.Portfolio; 10 | import io.vertx.workshop.portfolio.PortfolioService; 11 | import org.junit.After; 12 | import org.junit.Before; 13 | import org.junit.Ignore; 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | 17 | 18 | @RunWith(VertxUnitRunner.class) 19 | @Ignore("De-ignore me once you have implemented the methods") 20 | public class PortfolioServiceImplTest { 21 | 22 | private Vertx vertx; 23 | private PortfolioService service; 24 | private Portfolio original; 25 | 26 | @Before 27 | public void setUp(TestContext tc) { 28 | vertx = Vertx.vertx(); 29 | 30 | Async async = tc.async(); 31 | vertx.deployVerticle(io.vertx.workshop.portfolio.impl.PortfolioVerticle.class.getName(), id -> { 32 | service = new ServiceProxyBuilder(vertx).setAddress(PortfolioService.ADDRESS).build(PortfolioService.class); 33 | service.getPortfolio(ar -> { 34 | tc.assertTrue(ar.succeeded()); 35 | original = ar.result(); 36 | async.complete(); 37 | }); 38 | }); 39 | } 40 | 41 | @After 42 | public void tearDown() { 43 | vertx.close(); 44 | } 45 | 46 | @Test 47 | public void testBuyStocks(TestContext tc) { 48 | Async async = tc.async(); 49 | service.buy(10, quote("A", 10, 20, 100), ar -> { 50 | tc.assertTrue(ar.succeeded()); 51 | Portfolio portfolio = ar.result(); 52 | tc.assertEquals(portfolio.getAmount("A"), 10); 53 | tc.assertEquals(portfolio.getAmount("B"), 0); 54 | tc.assertEquals(portfolio.getCash(), original.getCash() - 10 * 10); 55 | async.complete(); 56 | }); 57 | } 58 | 59 | @Test 60 | public void testBuyAndSell(TestContext tc) { 61 | Async async = tc.async(); 62 | service.buy(10, quote("A", 10, 20, 100), ar -> { 63 | tc.assertTrue(ar.succeeded()); 64 | Portfolio portfolio = ar.result(); 65 | tc.assertEquals(portfolio.getAmount("A"), 10); 66 | tc.assertEquals(portfolio.getAmount("B"), 0); 67 | tc.assertEquals(portfolio.getCash(), original.getCash() - 10 * 10); 68 | 69 | // Sell the bought stocks immediately 70 | service.sell(5, quote("A", 10, 20, 100), ar2 -> { 71 | tc.assertTrue(ar2.succeeded()); 72 | Portfolio portfolio2 = ar2.result(); 73 | tc.assertEquals(portfolio2.getAmount("A"), 5); 74 | tc.assertEquals(portfolio2.getAmount("B"), 0); 75 | tc.assertEquals(portfolio2.getCash(), portfolio.getCash() + 5 * 20); 76 | async.complete(); 77 | }); 78 | }); 79 | } 80 | 81 | @Test 82 | public void testThatYouCannotBuyIfYouRunOutOfMoney(TestContext tc) { 83 | Async async = tc.async(); 84 | service.buy(10000, quote("A", 10, 20, 100000), ar -> { 85 | tc.assertTrue(ar.failed()); 86 | tc.assertTrue(ar.cause().getMessage().contains("not enough money")); 87 | async.complete(); 88 | }); 89 | } 90 | 91 | @Test 92 | public void testThatYouCannotBuyIfThereIsNotEnoughShare(TestContext tc) { 93 | Async async = tc.async(); 94 | service.buy(100, quote("A", 10, 20, 10), ar -> { 95 | tc.assertTrue(ar.failed()); 96 | tc.assertTrue(ar.cause().getMessage().contains("not enough stocks")); 97 | async.complete(); 98 | }); 99 | } 100 | 101 | @Test 102 | public void testThatYouCannotSellMoreThanWhatYouOwn(TestContext tc) { 103 | Async async = tc.async(); 104 | service.buy(100, quote("A", 10, 20, 100), ar -> { 105 | service.sell(100, quote("A", 10, 20, 0), ar2 -> { 106 | tc.assertTrue(ar2.succeeded()); 107 | service.sell(1, quote("A", 10, 20, 0), ar3 -> { 108 | tc.assertTrue(ar3.failed()); 109 | tc.assertTrue(ar3.cause().getMessage().contains("not enough stocks")); 110 | async.complete(); 111 | }); 112 | }); 113 | 114 | }); 115 | } 116 | 117 | @Test 118 | public void testYouCannotBuyANegativeAmount(TestContext tc) { 119 | Async async = tc.async(); 120 | service.buy(-1, quote("A", 10, 20, 100), ar -> { 121 | tc.assertTrue(ar.failed()); 122 | async.complete(); 123 | }); 124 | } 125 | 126 | @Test 127 | public void testYouCannotSellANegativeAmount(TestContext tc) { 128 | Async async = tc.async(); 129 | service.sell(-1, quote("A", 10, 20, 100), ar -> { 130 | tc.assertTrue(ar.failed()); 131 | async.complete(); 132 | }); 133 | } 134 | 135 | private JsonObject quote(String name, double ask, double bid, int available) { 136 | return new JsonObject() 137 | .put("name", name) 138 | .put("ask", ask) 139 | .put("bid", bid) 140 | .put("shares", available); 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /portfolio-service/src/test/java/io/vertx/workshop/portfolio/impl/PortfolioVerticleTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.portfolio.impl; 2 | 3 | import io.vertx.core.Vertx; 4 | import io.vertx.serviceproxy.ServiceProxyBuilder; 5 | import io.vertx.workshop.portfolio.Portfolio; 6 | import io.vertx.workshop.portfolio.PortfolioService; 7 | import org.junit.Ignore; 8 | import org.junit.Test; 9 | 10 | import java.util.concurrent.atomic.AtomicReference; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.awaitility.Awaitility.await; 14 | import static org.hamcrest.CoreMatchers.not; 15 | import static org.hamcrest.CoreMatchers.nullValue; 16 | 17 | @Ignore("De-ignore me once you have implemented the methods") 18 | public class PortfolioVerticleTest { 19 | 20 | @Test 21 | public void testServiceAccess() { 22 | Vertx vertx = Vertx.vertx(); 23 | vertx.deployVerticle(PortfolioVerticle.class.getName()); 24 | 25 | PortfolioService proxy = new ServiceProxyBuilder(vertx).setAddress(PortfolioService.ADDRESS).build(PortfolioService.class); 26 | 27 | assertThat(proxy).isNotNull(); 28 | AtomicReference reference = new AtomicReference<>(); 29 | proxy.getPortfolio(ar -> reference.set(ar.result())); 30 | 31 | await().untilAtomic(reference, not(nullValue())); 32 | 33 | vertx.close(); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /quote-generator/README.md: -------------------------------------------------------------------------------- 1 | # Quote generator 2 | 3 | The quote generator simulates the evolution of the values of 3 companies. Every quote is sent on the event bus. It 4 | also exposes a HTTP endpoint to retrieve the last quote of each company. 5 | 6 | 7 | ## Build 8 | 9 | ``` 10 | mvn clean package 11 | ``` 12 | 13 | ## Deploy 14 | 15 | ``` 16 | mvn fabric8:deploy 17 | ``` 18 | -------------------------------------------------------------------------------- /quote-generator/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | 9 | io.vertx.workshop 10 | vertx-kubernetes-workshop 11 | 1.0-SNAPSHOT 12 | 13 | 14 | quote-generator 15 | 16 | 17 | 18 | io.vertx.workshop.quote.GeneratorConfigVerticle 19 | 20 | vertx-cluster 21 | 22 | 23 | 24 | 25 | io.vertx 26 | vertx-config 27 | 28 | 29 | io.vertx 30 | vertx-infinispan 31 | 32 | 33 | org.infinispan 34 | infinispan-cloud 35 | 36 | 37 | 38 | 39 | 40 | 41 | io.reactiverse 42 | vertx-maven-plugin 43 | 44 | 45 | io.fabric8 46 | fabric8-maven-plugin 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /quote-generator/src/kubernetes/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "http.port": 35000, 3 | "companies": [ 4 | { 5 | "name": "MacroHard", 6 | "symbol": "MCH", 7 | "volume": 95000, 8 | "price": 600, 9 | "variation": 100 10 | }, 11 | { 12 | "name": "Divinator", 13 | "symbol": "DVN", 14 | "volume": 98000, 15 | "price": 650, 16 | "variation": 50 17 | }, 18 | { 19 | "name": "Black Coat", 20 | "symbol": "BCT", 21 | "volume": 90000, 22 | "price": 550, 23 | "variation": 150 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /quote-generator/src/main/fabric8/deployment.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | template: 3 | spec: 4 | # Declare a volume mounting the config map 5 | volumes: 6 | - configMap: 7 | # Name of the config map 8 | name: app-config 9 | # Define the items from the config map to mount 10 | items: 11 | - key: config.json 12 | path: config.json 13 | # Volume name (used as reference below) 14 | name: config 15 | containers: 16 | - name: vertx 17 | env: 18 | - name: KUBERNETES_NAMESPACE 19 | valueFrom: 20 | fieldRef: 21 | apiVersion: v1 22 | fieldPath: metadata.namespace 23 | - name: JAVA_OPTIONS 24 | value: '-Dvertx.cacheDirBase=/tmp -Dvertx.jgroups.config=default-configs/default-jgroups-kubernetes.xml -Djava.net.preferIPv4Stack=true' 25 | - name: JAVA_ARGS 26 | value: '-cluster' 27 | # Mount the volume define above in /deployments/config 28 | volumeMounts: 29 | - name: config 30 | mountPath: /deployments/config 31 | -------------------------------------------------------------------------------- /quote-generator/src/main/java/io/vertx/workshop/quote/GeneratorConfigVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.quote; 2 | 3 | import io.reactivex.Observable; 4 | import io.vertx.config.ConfigRetrieverOptions; 5 | import io.vertx.config.ConfigStoreOptions; 6 | import io.vertx.core.DeploymentOptions; 7 | import io.vertx.core.Future; 8 | import io.vertx.core.json.JsonObject; 9 | import io.vertx.reactivex.CompletableHelper; 10 | import io.vertx.reactivex.config.ConfigRetriever; 11 | import io.vertx.reactivex.core.AbstractVerticle; 12 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 13 | import io.vertx.reactivex.servicediscovery.types.MessageSource; 14 | import io.vertx.servicediscovery.Record; 15 | 16 | /** 17 | * a verticle generating "fake" quotes based on the configuration. 18 | */ 19 | public class GeneratorConfigVerticle extends AbstractVerticle { 20 | 21 | /** 22 | * The address on which the data are sent. 23 | */ 24 | static final String ADDRESS = "market"; 25 | 26 | private Record record; 27 | private ServiceDiscovery discovery; 28 | 29 | /** 30 | * This method is called when the verticle is deployed. 31 | */ 32 | @Override 33 | public void start(Future future) { 34 | discovery = ServiceDiscovery.create(vertx); 35 | ConfigRetriever retriever = ConfigRetriever.create(vertx, getConfigurationOptions()); 36 | 37 | retriever.rxGetConfig() 38 | // Read the configuration, and deploy a MarketDataVerticle for each company listed in the configuration. 39 | .flatMap(config -> 40 | Observable.fromIterable(config.getJsonArray("companies")) 41 | .cast(JsonObject.class) 42 | // Deploy the verticle with a configuration. 43 | .flatMapSingle(company -> vertx.rxDeployVerticle(MarketDataVerticle.class.getName(), 44 | new DeploymentOptions().setConfig(company))) 45 | .toList() 46 | ) 47 | // Deploy another verticle 48 | .flatMap(l -> vertx.rxDeployVerticle(RestQuoteAPIVerticle.class.getName())) 49 | // Expose the market-data message source 50 | .flatMap(x -> discovery.rxPublish(MessageSource.createRecord("market-data", ADDRESS))) 51 | .subscribe((rec, err) -> { 52 | if (rec != null) { 53 | this.record = rec; 54 | future.complete(); 55 | } else { 56 | future.fail(err); 57 | } 58 | }); 59 | } 60 | 61 | @Override 62 | public void stop(Future future) throws Exception { 63 | if (record != null) { 64 | discovery.rxUnpublish(record.getRegistration()).subscribe(CompletableHelper.toObserver(future)); 65 | } else { 66 | future.complete(); 67 | } 68 | } 69 | 70 | private ConfigRetrieverOptions getConfigurationOptions() { 71 | JsonObject path = new JsonObject().put("path", "config/config.json"); 72 | return new ConfigRetrieverOptions() 73 | .addStore(new ConfigStoreOptions().setType("file").setConfig(path)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /quote-generator/src/main/java/io/vertx/workshop/quote/MarketDataVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.quote; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.json.JsonObject; 5 | 6 | import java.util.Objects; 7 | import java.util.Random; 8 | 9 | /** 10 | * A verticle simulating the evaluation of a company evaluation in a very unrealistic and irrational way. 11 | * It emits the new data on the `market` address on the event bus. 12 | */ 13 | public class MarketDataVerticle extends AbstractVerticle { 14 | 15 | String name; 16 | int variation; 17 | long period; 18 | String symbol; 19 | int stocks; 20 | double price; 21 | 22 | double bid; 23 | double ask; 24 | 25 | int share; 26 | private double value; 27 | 28 | private final Random random = new Random(); 29 | 30 | /** 31 | * Method called when the verticle is deployed. 32 | */ 33 | @Override 34 | public void start() { 35 | // Retrieve the configuration, and initialize the verticle. 36 | JsonObject config = config(); 37 | init(config); 38 | 39 | // Every `period` ms, the given Handler is called. 40 | vertx.setPeriodic(period, l -> { 41 | compute(); 42 | send(); 43 | }); 44 | } 45 | 46 | /** 47 | * Read the configuration and set the initial values. 48 | * @param config the configuration 49 | */ 50 | void init(JsonObject config) { 51 | period = config.getLong("period", 3000L); 52 | variation = config.getInteger("variation", 100); 53 | name = config.getString("name"); 54 | Objects.requireNonNull(name); 55 | symbol = config.getString("symbol", name); 56 | stocks = config.getInteger("volume", 10000); 57 | price = config.getDouble("price", 100.0); 58 | 59 | value = price; 60 | ask = price + random.nextInt(variation / 2); 61 | bid = price + random.nextInt(variation / 2); 62 | 63 | share = stocks / 2; 64 | 65 | System.out.println("Initialized " + name); 66 | } 67 | 68 | /** 69 | * Sends the market data on the event bus. 70 | */ 71 | private void send() { 72 | vertx.eventBus().publish(GeneratorConfigVerticle.ADDRESS, toJson()); 73 | } 74 | 75 | /** 76 | * Compute the new evaluation... 77 | */ 78 | void compute() { 79 | 80 | if (random.nextBoolean()) { 81 | value = value + random.nextInt(variation); 82 | ask = value + random.nextInt(variation / 2); 83 | bid = value + random.nextInt(variation / 2); 84 | } else { 85 | value = value - random.nextInt(variation); 86 | ask = value - random.nextInt(variation / 2); 87 | bid = value - random.nextInt(variation / 2); 88 | } 89 | 90 | if (value <= 0) { 91 | value = 1.0; 92 | } 93 | if (ask <= 0) { 94 | ask = 1.0; 95 | } 96 | if (bid <= 0) { 97 | bid = 1.0; 98 | } 99 | 100 | if (random.nextBoolean()) { 101 | // Adjust share 102 | int shareVariation = random.nextInt(100); 103 | if (shareVariation > 0 && share + shareVariation < stocks) { 104 | share += shareVariation; 105 | } else if (shareVariation < 0 && share + shareVariation > 0) { 106 | share += shareVariation; 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * @return a json representation of the market data (quote). The structure is close to 113 | * https://en.wikipedia.org/wiki/Market_data. 114 | */ 115 | private JsonObject toJson() { 116 | return new JsonObject() 117 | .put("exchange", "Vert.x stock exchange") 118 | .put("symbol", symbol) 119 | .put("name", name) 120 | .put("bid", bid) 121 | .put("ask", ask) 122 | .put("volume", stocks) 123 | .put("open", price) 124 | .put("shares", share); 125 | 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /quote-generator/src/main/java/io/vertx/workshop/quote/RestQuoteAPIVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.quote; 2 | 3 | import io.reactivex.Completable; 4 | import io.vertx.core.json.Json; 5 | import io.vertx.core.json.JsonObject; 6 | import io.vertx.reactivex.core.AbstractVerticle; 7 | import io.vertx.reactivex.core.http.HttpServer; 8 | import io.vertx.reactivex.core.http.HttpServerResponse; 9 | import io.vertx.reactivex.core.eventbus.Message; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * This verticle exposes a HTTP endpoint to retrieve the current / last values of the maker data (quotes). 16 | */ 17 | public class RestQuoteAPIVerticle extends AbstractVerticle { 18 | 19 | private Map quotes = new HashMap<>(); 20 | 21 | @Override 22 | public Completable rxStart() { 23 | // Get the stream of messages sent on the "market" address 24 | vertx.eventBus().consumer(GeneratorConfigVerticle.ADDRESS).toFlowable() 25 | // TODO Extract the body of the message using `.map(msg -> {})` 26 | // ---- 27 | // 28 | // ---- 29 | // TODO For each message, populate the `quotes` map with the received quote. Use `.doOnNext(json -> {})` 30 | // Quotes are json objects you can retrieve from the message body 31 | // The map is structured as follows: name -> quote 32 | // ---- 33 | // 34 | // ---- 35 | .subscribe(); 36 | 37 | HttpServer server = vertx.createHttpServer(); 38 | server.requestStream().toFlowable() 39 | .doOnNext(request -> { 40 | HttpServerResponse response = request.response() 41 | .putHeader("content-type", "application/json"); 42 | 43 | // TODO Handle the HTTP request 44 | // The request handler returns a specific quote if the `name` parameter is set, or the whole map if none. 45 | // To write the response use: `response.end(content)` 46 | // If the name is set but not found, you should return 404 (use response.setStatusCode(404)). 47 | // To encode a Json object, use the `encorePrettily` method 48 | // ---- 49 | 50 | // Remove this line 51 | response.end(Json.encodePrettily(quotes)); 52 | 53 | // ---- 54 | }) 55 | .subscribe(); 56 | 57 | return server.rxListen(config().getInteger("http.port", 8080)) 58 | .ignoreElement(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /quote-generator/src/main/solution/io/vertx/workshop/quote/GeneratorConfigVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.quote; 2 | 3 | import io.reactivex.Observable; 4 | import io.vertx.config.ConfigRetrieverOptions; 5 | import io.vertx.config.ConfigStoreOptions; 6 | import io.vertx.core.DeploymentOptions; 7 | import io.vertx.core.Future; 8 | import io.vertx.core.json.JsonObject; 9 | import io.vertx.reactivex.CompletableHelper; 10 | import io.vertx.reactivex.config.ConfigRetriever; 11 | import io.vertx.reactivex.core.AbstractVerticle; 12 | import io.vertx.reactivex.servicediscovery.ServiceDiscovery; 13 | import io.vertx.reactivex.servicediscovery.types.MessageSource; 14 | import io.vertx.servicediscovery.Record; 15 | 16 | /** 17 | * a verticle generating "fake" quotes based on the configuration. 18 | */ 19 | public class GeneratorConfigVerticle extends AbstractVerticle { 20 | 21 | /** 22 | * The address on which the data are sent. 23 | */ 24 | static final String ADDRESS = "market"; 25 | 26 | private Record record; 27 | private ServiceDiscovery discovery; 28 | 29 | /** 30 | * This method is called when the verticle is deployed. 31 | */ 32 | @Override 33 | public void start(Future future) { 34 | discovery = ServiceDiscovery.create(vertx); 35 | ConfigRetriever retriever = ConfigRetriever.create(vertx, getConfigurationOptions()); 36 | 37 | retriever.rxGetConfig() 38 | // Read the configuration, and deploy a MarketDataVerticle for each company listed in the configuration. 39 | .flatMap(config -> 40 | Observable.fromIterable(config.getJsonArray("companies")) 41 | .cast(JsonObject.class) 42 | // Deploy the verticle with a configuration. 43 | .flatMapSingle(company -> vertx.rxDeployVerticle(MarketDataVerticle.class.getName(), 44 | new DeploymentOptions().setConfig(company))) 45 | .toList() 46 | ) 47 | // Deploy another verticle 48 | .flatMap(l -> vertx.rxDeployVerticle(RestQuoteAPIVerticle.class.getName())) 49 | // Expose the market-data message source 50 | .flatMap(x -> discovery.rxPublish(MessageSource.createRecord("market-data", ADDRESS))) 51 | .subscribe((rec, err) -> { 52 | if (rec != null) { 53 | this.record = rec; 54 | future.complete(); 55 | } else { 56 | future.fail(err); 57 | } 58 | }); 59 | } 60 | 61 | @Override 62 | public void stop(Future future) throws Exception { 63 | if (record != null) { 64 | discovery.rxUnpublish(record.getRegistration()).subscribe(CompletableHelper.toObserver(future)); 65 | } else { 66 | future.complete(); 67 | } 68 | } 69 | 70 | private ConfigRetrieverOptions getConfigurationOptions() { 71 | JsonObject path = new JsonObject().put("path", "config/config.json"); 72 | return new ConfigRetrieverOptions().addStore(new ConfigStoreOptions().setType("file").setConfig(path)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /quote-generator/src/main/solution/io/vertx/workshop/quote/MarketDataVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.quote; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.json.JsonObject; 5 | 6 | import java.util.Objects; 7 | import java.util.Random; 8 | 9 | /** 10 | * A verticle simulating the evaluation of a company evaluation in a very unrealistic and irrational way. 11 | * It emits the new data on the `market` address on the event bus. 12 | */ 13 | public class MarketDataVerticle extends AbstractVerticle { 14 | 15 | String name; 16 | int variation; 17 | long period; 18 | String symbol; 19 | int stocks; 20 | double price; 21 | 22 | double bid; 23 | double ask; 24 | 25 | int share; 26 | private double value; 27 | 28 | private final Random random = new Random(); 29 | 30 | /** 31 | * Method called when the verticle is deployed. 32 | */ 33 | @Override 34 | public void start() { 35 | // Retrieve the configuration, and initialize the verticle. 36 | JsonObject config = config(); 37 | init(config); 38 | 39 | // Every `period` ms, the given Handler is called. 40 | vertx.setPeriodic(period, l -> { 41 | compute(); 42 | send(); 43 | }); 44 | } 45 | 46 | /** 47 | * Read the configuration and set the initial values. 48 | * @param config the configuration 49 | */ 50 | void init(JsonObject config) { 51 | period = config.getLong("period", 3000L); 52 | variation = config.getInteger("variation", 100); 53 | name = config.getString("name"); 54 | Objects.requireNonNull(name); 55 | symbol = config.getString("symbol", name); 56 | stocks = config.getInteger("volume", 10000); 57 | price = config.getDouble("price", 100.0); 58 | 59 | value = price; 60 | ask = price + random.nextInt(variation / 2); 61 | bid = price + random.nextInt(variation / 2); 62 | 63 | share = stocks / 2; 64 | 65 | System.out.println("Initialized " + name); 66 | } 67 | 68 | /** 69 | * Sends the market data on the event bus. 70 | */ 71 | private void send() { 72 | vertx.eventBus().publish(GeneratorConfigVerticle.ADDRESS, toJson()); 73 | } 74 | 75 | /** 76 | * Compute the new evaluation... 77 | */ 78 | void compute() { 79 | 80 | if (random.nextBoolean()) { 81 | value = value + random.nextInt(variation); 82 | ask = value + random.nextInt(variation / 2); 83 | bid = value + random.nextInt(variation / 2); 84 | } else { 85 | value = value - random.nextInt(variation); 86 | ask = value - random.nextInt(variation / 2); 87 | bid = value - random.nextInt(variation / 2); 88 | } 89 | 90 | if (value <= 0) { 91 | value = 1.0; 92 | } 93 | if (ask <= 0) { 94 | ask = 1.0; 95 | } 96 | if (bid <= 0) { 97 | bid = 1.0; 98 | } 99 | 100 | if (random.nextBoolean()) { 101 | // Adjust share 102 | int shareVariation = random.nextInt(100); 103 | if (shareVariation > 0 && share + shareVariation < stocks) { 104 | share += shareVariation; 105 | } else if (shareVariation < 0 && share + shareVariation > 0) { 106 | share += shareVariation; 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * @return a json representation of the market data (quote). The structure is close to 113 | * https://en.wikipedia.org/wiki/Market_data. 114 | */ 115 | private JsonObject toJson() { 116 | return new JsonObject() 117 | .put("exchange", "Vert.x stock exchange") 118 | .put("symbol", symbol) 119 | .put("name", name) 120 | .put("bid", bid) 121 | .put("ask", ask) 122 | .put("volume", stocks) 123 | .put("open", price) 124 | .put("shares", share); 125 | 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /quote-generator/src/main/solution/io/vertx/workshop/quote/RestQuoteAPIVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.quote; 2 | 3 | import io.reactivex.Completable; 4 | import io.vertx.core.json.Json; 5 | import io.vertx.core.json.JsonObject; 6 | import io.vertx.reactivex.core.AbstractVerticle; 7 | import io.vertx.reactivex.core.eventbus.Message; 8 | import io.vertx.reactivex.core.http.HttpServer; 9 | import io.vertx.reactivex.core.http.HttpServerResponse; 10 | import io.vertx.reactivex.core.eventbus.Message; 11 | 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * This verticle exposes a HTTP endpoint to retrieve the current / last values of the maker data (quotes). 18 | */ 19 | public class RestQuoteAPIVerticle extends AbstractVerticle { 20 | 21 | private Map quotes = new HashMap<>(); 22 | 23 | @Override 24 | public Completable rxStart() { 25 | // Get the stream of messages sent on the "market" address 26 | vertx.eventBus().consumer(GeneratorConfigVerticle.ADDRESS).toFlowable() 27 | // TODO Extract the body of the message using `.map(msg -> {})` 28 | // ---- 29 | .map(Message::body) 30 | // ---- 31 | // TODO For each message, populate the `quotes` map with the received quote. Use `.doOnNext(json -> {})` 32 | // Quotes are json objects you can retrieve from the message body 33 | // The map is structured as follows: name -> quote 34 | // ---- 35 | .doOnNext(json -> { 36 | quotes.put(json.getString("name"), json); // 2 37 | }) 38 | // ---- 39 | .subscribe(); 40 | 41 | HttpServer server = vertx.createHttpServer(); 42 | server.requestStream().toFlowable() 43 | .doOnNext(request -> { 44 | HttpServerResponse response = request.response() 45 | .putHeader("content-type", "application/json"); 46 | 47 | // TODO Handle the HTTP request 48 | // The request handler returns a specific quote if the `name` parameter is set, or the whole map if none. 49 | // To write the response use: `response.end(content)` 50 | // If the name is set but not found, you should return 404 (use response.setStatusCode(404)). 51 | // To encode a Json object, use the `encorePrettily` method 52 | // ---- 53 | 54 | String company = request.getParam("name"); 55 | if (company == null) { 56 | String content = Json.encodePrettily(quotes); 57 | response.end(content); 58 | } else { 59 | JsonObject quote = quotes.get(company); 60 | if (quote == null) { 61 | response.setStatusCode(404).end(); 62 | } else { 63 | response.end(quote.encodePrettily()); 64 | } 65 | } 66 | // ---- 67 | }) 68 | .subscribe(); 69 | 70 | return server.rxListen(config().getInteger("http.port", 8080)) 71 | .ignoreElement(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /quote-generator/src/test/java/io/vertx/workshop/quote/GeneratorConfigVerticleTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.quote; 2 | 3 | import io.vertx.core.DeploymentOptions; 4 | import io.vertx.core.Vertx; 5 | import io.vertx.core.json.JsonObject; 6 | import org.junit.Test; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | import static org.awaitility.Awaitility.await; 16 | 17 | 18 | public class GeneratorConfigVerticleTest { 19 | 20 | @Test 21 | public void test() throws IOException { 22 | byte[] bytes = Files.readAllBytes(new File("src/test/resources/config/config.json").toPath()); 23 | JsonObject config = new JsonObject(new String(bytes, "UTF-8")); 24 | 25 | Vertx vertx = Vertx.vertx(); 26 | 27 | List mch = new ArrayList<>(); 28 | List dvn = new ArrayList<>(); 29 | List bct = new ArrayList<>(); 30 | 31 | vertx.eventBus().consumer(GeneratorConfigVerticle.ADDRESS, message -> { 32 | JsonObject quote = (JsonObject) message.body(); 33 | System.out.println(quote.encodePrettily()); 34 | assertThat(quote.getDouble("bid")).isGreaterThan(0); 35 | assertThat(quote.getDouble("ask")).isGreaterThan(0); 36 | assertThat(quote.getInteger("volume")).isGreaterThan(0); 37 | assertThat(quote.getInteger("shares")).isGreaterThan(0); 38 | switch (quote.getString("symbol")) { 39 | case "MCH": 40 | mch.add(quote); 41 | break; 42 | case "DVN": 43 | dvn.add(quote); 44 | break; 45 | case "BCT": 46 | bct.add(quote); 47 | break; 48 | } 49 | }); 50 | 51 | vertx.deployVerticle(GeneratorConfigVerticle.class.getName(), new DeploymentOptions().setConfig(config)); 52 | 53 | await().until(() -> mch.size() > 10); 54 | await().until(() -> dvn.size() > 10); 55 | await().until(() -> bct.size() > 10); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /quote-generator/src/test/java/io/vertx/workshop/quote/MarketDataVerticleTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.quote; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | import org.junit.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | 9 | public class MarketDataVerticleTest { 10 | 11 | 12 | @Test 13 | public void testComputation() { 14 | JsonObject json = new JsonObject() 15 | .put("name", "test") 16 | .put("symbol", "TT"); 17 | 18 | MarketDataVerticle verticle = new MarketDataVerticle(); 19 | verticle.init(json); 20 | 21 | int volume = verticle.stocks; 22 | 23 | assertThat(verticle.ask).isGreaterThan(0.0); 24 | assertThat(verticle.bid).isGreaterThan(0.0); 25 | assertThat(verticle.share).isGreaterThanOrEqualTo(0).isLessThanOrEqualTo(volume); 26 | 27 | for (int i = 0; i < 1000000; i++) { 28 | verticle.compute(); 29 | assertThat(verticle.ask).isGreaterThan(0.0); 30 | assertThat(verticle.bid).isGreaterThan(0.0); 31 | assertThat(verticle.share).isGreaterThanOrEqualTo(0).isLessThanOrEqualTo(volume); 32 | } 33 | 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /quote-generator/src/test/resources/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "companies": [ 3 | { 4 | "name": "MacroHard", 5 | "symbol": "MCH", 6 | "volume": 100000, 7 | "price": 600, 8 | "variation": 200, 9 | "period": 100 10 | }, 11 | { 12 | "name": "Divinator", 13 | "symbol": "DVN", 14 | "volume": 500000, 15 | "price": 800, 16 | "variation": 50, 17 | "period": 100 18 | }, 19 | { 20 | "name": "Black Coat", 21 | "symbol": "BCT", 22 | "volume": 90000, 23 | "price": 300, 24 | "variation": 150, 25 | "period": 100 26 | } 27 | ] 28 | 29 | } -------------------------------------------------------------------------------- /scripts/cleanup-project.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | PROJECT=$(oc project -q) 3 | echo "Cleaning up current project: ${PROJECT}" 4 | # https://docs.openshift.org/latest/cli_reference/basic_cli_operations.html#object-types 5 | oc delete build --all 6 | oc delete bc --all 7 | 8 | oc delete dc --all 9 | oc delete deploy --all 10 | 11 | oc delete is --all 12 | oc delete istag --all 13 | oc delete isimage --all 14 | 15 | oc delete job --all 16 | 17 | oc delete po --all 18 | oc delete rc --all 19 | oc delete rs --all 20 | 21 | oc delete secrets --all 22 | oc delete configmap --all 23 | 24 | oc delete services --all 25 | 26 | oc delete routes --all 27 | 28 | oc delete template --all 29 | 30 | -------------------------------------------------------------------------------- /scripts/create-project.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RED='\033[0;31m' 4 | NC='\033[0m' # No Color 5 | YELLOW='\033[0;33m' 6 | BLUE='\033[0;34m' 7 | 8 | PROJECT=vertx-kubernetes-workshop 9 | 10 | echo -e "${BLUE}Creating project ${PROJECT} ${NC}" 11 | oc new-project ${PROJECT} 12 | echo -e "${BLUE}Adding permissions to ${PROJECT} ${NC}" 13 | oc policy add-role-to-user view admin -n ${PROJECT} 14 | oc policy add-role-to-user view -n ${PROJECT} -z default 15 | oc policy add-role-to-group view system:serviceaccounts -n ${PROJECT} 16 | 17 | oc project 18 | echo -e "${BLUE}Done ! ${NC}" -------------------------------------------------------------------------------- /scripts/deploy-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "Deploying 3rd party curency service" 3 | cd currency-3rdparty-service 4 | mvn clean fabric8:deploy 5 | 6 | cd ../micro-trader-dashboard 7 | echo "Deploying the dashboard" 8 | mvn clean fabric8:deploy 9 | 10 | cd ../quote-generator 11 | echo "Deploying the quote generator" 12 | oc create configmap app-config --from-file=src/kubernetes/config.json 13 | mvn clean fabric8:deploy -Psolution 14 | 15 | cd ../portfolio-service 16 | echo "Deploying the portfolio service" 17 | mvn clean fabric8:deploy -Psolution 18 | 19 | cd ../compulsive-traders 20 | mvn clean fabric8:deploy -Psolution 21 | oc scale dc compulsive-traders --replicas=2 22 | 23 | cd ../audit-service 24 | echo "Deploying the audit service" 25 | oc create -f src/kubernetes/database-secret.yaml 26 | oc new-app -e POSTGRESQL_USER=admin -ePOSTGRESQL_PASSWORD=secret -ePOSTGRESQL_DATABASE=audit registry.access.redhat.com/rhscl/postgresql-94-rhel7 --name=audit-database 27 | mvn clean fabric8:deploy -Psolution 28 | 29 | 30 | cd ../currency-service 31 | echo "Deploying the currency service proxy" 32 | mvn clean fabric8:deploy -Psolution -------------------------------------------------------------------------------- /vertx-exercises/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.vertx.workshop 9 | vertx-kubernetes-workshop 10 | 1.0-SNAPSHOT 11 | 12 | 13 | vertx-exercises 14 | 15 | 16 | 17 | 18 | solution 19 | 20 | src/main/java 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | org.codehaus.mojo 29 | exec-maven-plugin 30 | 1.6.0 31 | 32 | 33 | 34 | exec 35 | 36 | 37 | 38 | 39 | maven 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /vertx-exercises/src/main/java/io/vertx/workshop/exercise/Exercise1.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise; 2 | 3 | import io.vertx.core.Vertx; 4 | 5 | /** 6 | * * Create the Vert.x instance 7 | * * Create a HTTP server 8 | */ 9 | public class Exercise1 { 10 | 11 | public static void main(String[] args) { 12 | // 1 - Create the Vert.x instance using Vertx.vertx (use io.vertx.core.Vertx) 13 | // TODO 14 | 15 | // 2 - Create a HTTP server using the `createHttpServer` method. Set a request handler doing: 16 | // `req.response().end("hello")` 17 | // Call the listen method with `8080` as parameter. 18 | // TODO 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /vertx-exercises/src/main/java/io/vertx/workshop/exercise/Exercise2.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise; 2 | 3 | import io.vertx.core.Vertx; 4 | 5 | /** 6 | * * Create the HTTP server in the `Exercise2Verticle` 7 | * * Deploy this verticle from this class 8 | */ 9 | public class Exercise2 { 10 | 11 | public static void main(String[] args) { 12 | // 1 - Create the Vert.x instance using Vertx.vertx (use io.vertx.core.Vertx) 13 | // TODO 14 | 15 | // 2 - Deploy the `Exercise2Verticle` verticle using: vertx.deployVerticle(className); 16 | // TODO 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /vertx-exercises/src/main/java/io/vertx/workshop/exercise/Exercise2Verticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | 5 | /** 6 | * Create a HTTP server in the `start` method. 7 | */ 8 | public class Exercise2Verticle extends AbstractVerticle { 9 | 10 | @Override 11 | public void start() throws Exception { 12 | // You can access the vert.x instance your deployed on using the `vertx` (inherited) field 13 | 14 | // Create a HTTP server 15 | // Instead of "hello", display the name of the thread serving the request (using Thread.currentThread() 16 | // .getName()) 17 | // TODO 18 | 19 | } 20 | 21 | 22 | /** 23 | * Method used in the Exercise 3. 24 | */ 25 | private void sleep() { 26 | try { 27 | Thread.sleep(3000L); 28 | } catch (InterruptedException e) { 29 | // Ignore me 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vertx-exercises/src/main/java/io/vertx/workshop/exercise/Exercise4.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise; 2 | 3 | import io.vertx.core.Vertx; 4 | 5 | /** 6 | * Launcher for the Exercise 4, it just deploys the 2 verticles. 7 | */ 8 | public class Exercise4 { 9 | 10 | public static void main(String[] args) { 11 | Vertx vertx = Vertx.vertx(); 12 | vertx.deployVerticle(Exercise4SenderVerticle.class.getName()); 13 | vertx.deployVerticle(Exercise4ReceiverVerticle.class.getName()); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /vertx-exercises/src/main/java/io/vertx/workshop/exercise/Exercise4ReceiverVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.json.JsonObject; 5 | 6 | /** 7 | * A verticle receiving messages. 8 | */ 9 | public class Exercise4ReceiverVerticle extends AbstractVerticle { 10 | 11 | @Override 12 | public void start() throws Exception { 13 | // Retrieve the event bus and register a consumer on the "greetings" address. For each message, print it on 14 | // the console. You can retrieve the message body using `body()`. Use the method `encodePrettily` 15 | // on the retrieved Json body to print it nicely. 16 | // TODO 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /vertx-exercises/src/main/java/io/vertx/workshop/exercise/Exercise4SenderVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.eventbus.EventBus; 5 | import io.vertx.core.json.JsonObject; 6 | 7 | /** 8 | * A verticle periodically sending a message on the event bus. 9 | */ 10 | public class Exercise4SenderVerticle extends AbstractVerticle { 11 | 12 | @Override 13 | public void start() throws Exception { 14 | // Retrieve the event bus 15 | EventBus eventBus = vertx.eventBus(); 16 | 17 | // Execute the given handler every 2000 ms 18 | vertx.setPeriodic(2000, l -> { 19 | // Use the eventBus() method to retrieve the event bus and send a "{"message":hello"} JSON message on the 20 | // "greetings" address. 21 | 22 | // 1 - Create the JSON object using the JsonObject class, and `put` the 'message':'hello' entry 23 | // TODO 24 | 25 | // 2 - Use the `send` method of the event bus to _send_ the message. Messages sent with the `send` method 26 | // are received by a single consumer. Messages sent with the `publish` method are received by all 27 | // registered consumers. 28 | // TODO 29 | 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vertx-exercises/src/main/java/io/vertx/workshop/exercise/Exercise5.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise; 2 | 3 | import io.vertx.core.Vertx; 4 | 5 | /** 6 | * Launcher for the Exercise 5. 7 | */ 8 | public class Exercise5 { 9 | 10 | public static void main(String[] args) { 11 | Vertx vertx = Vertx.vertx(); 12 | vertx.deployVerticle(Exercise5HttpVerticle.class.getName()); 13 | vertx.deployVerticle(Exercise5ProcessorVerticle.class.getName()); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /vertx-exercises/src/main/java/io/vertx/workshop/exercise/Exercise5HttpVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.json.JsonObject; 5 | 6 | /** 7 | * A verticle using the request-reply event bus delivery mechanism to handle HTTP requests. 8 | */ 9 | public class Exercise5HttpVerticle extends AbstractVerticle { 10 | 11 | @Override 12 | public void start() throws Exception { 13 | vertx.createHttpServer() 14 | .requestHandler(req -> { 15 | 16 | // 1 - Retrieve the `name` (query) parameter, set it to `world if null`. You can retrieve the 17 | // parameter using: `req.getParam()` 18 | // TODO 19 | 20 | // 2 - Send a message on the event bus using the `send` method. Pass a reply handler receiving the 21 | // response. As the expected object is a Json structure, you can use `vertx.eventBus() 22 | // .send(...`). 23 | // In the reply handler, you receive an `AsyncResult`. This structure describes the outcome from an 24 | // asynchronous operation: a success (and a result) or a failure (and a cause). If it's a failure 25 | // (check with the `failed` method), write a 500 HTTP response with the cause (`cause.getMessage()`) as 26 | // payload. On success, write the body into the HTTP response. 27 | // TODO 28 | 29 | }) 30 | .listen(8080); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vertx-exercises/src/main/java/io/vertx/workshop/exercise/Exercise5ProcessorVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.eventbus.EventBus; 5 | import io.vertx.core.json.JsonObject; 6 | 7 | /** 8 | * A verticle replying to incoming messages. 9 | */ 10 | public class Exercise5ProcessorVerticle extends AbstractVerticle { 11 | 12 | @Override 13 | public void start() throws Exception { 14 | EventBus eventBus = vertx.eventBus(); 15 | 16 | // Register a consumer and call the `reply` method with a JSON object containing the greeting message. ~ 17 | // parameter is passed in the incoming message body (a name). For example, if the incoming message is the 18 | // String "vert.x", the reply contains: `{"message" : "hello vert.x"}`. 19 | // Unlike the previous exercise, the incoming message has a `String` body. 20 | // TODO 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /vertx-exercises/src/main/java/io/vertx/workshop/exercise/Exercise6.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise; 2 | 3 | import io.vertx.core.Vertx; 4 | 5 | /** 6 | * Launcher for the Exercise 6. 7 | */ 8 | public class Exercise6 { 9 | 10 | public static void main(String[] args) { 11 | Vertx vertx = Vertx.vertx(); 12 | vertx.deployVerticle(Exercise6HttpVerticle.class.getName()); 13 | // Keep using the once developed previously 14 | vertx.deployVerticle(Exercise5ProcessorVerticle.class.getName()); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /vertx-exercises/src/main/java/io/vertx/workshop/exercise/Exercise6HttpVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.workshop.exercise; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.core.json.JsonObject; 5 | import io.vertx.reactivex.core.AbstractVerticle; 6 | import io.vertx.reactivex.core.http.HttpServer; 7 | 8 | /** 9 | * A verticle using the request-reply event bus delivery mechanism to handle HTTP requests. 10 | */ 11 | public class Exercise6HttpVerticle extends AbstractVerticle { 12 | 13 | @Override 14 | public void start() throws Exception { 15 | vertx.createHttpServer() 16 | .requestHandler(req -> { 17 | String name = req.getParam("name"); 18 | if (name == null) { 19 | name = "world"; 20 | } 21 | 22 | // Send a message on the event bus using the `send` method. Pass a reply handler receiving the 23 | // response. As the expected object is a Json structure, you can use `vertx.eventBus() 24 | // .send(...`). 25 | // Unlike in the previous exercise, we use the `rxSend` method to retrieve a `Single` stream. We then 26 | // _map_ the result to extract the (encoded as String) Json structure. 27 | // In RX, we must `subscribe` to the stream to trigger the processing. Without nothing happens. There 28 | // are several `subscribe` method, but here we recommend the `BiConsumer` format `(res, err) -> ...` 29 | // If it's a failure (err != null), write a 500 HTTP response with the cause (`err.getMessage()`) as 30 | // payload. On success, write the body (`res`) into the HTTP response. 31 | // TODO 32 | 33 | }) 34 | .listen(8080); 35 | } 36 | } 37 | --------------------------------------------------------------------------------