├── .gitignore ├── build.gradle ├── docker ├── Dockerfile ├── aws-run.txt ├── docker-build-push.sh ├── reactivetest-netty-task.json └── reactivetest-tomcat-task.json ├── gatling └── Simulation.scala └── src └── main ├── java └── com │ └── example │ ├── reactivetest │ ├── MainApplication.java │ ├── User.java │ ├── UserRepository.java │ └── UserRestService.java │ └── samples │ ├── TestWithCB.java │ └── TestWithReactive.java └── resources └── application.properties /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | nbproject/private/ 21 | build/ 22 | nbbuild/ 23 | dist/ 24 | nbdist/ 25 | .nb-gradle/ 26 | 27 | app.jar 28 | bin/ 29 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.0.0.RC1' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://repo.spring.io/snapshot" } 8 | maven { url "https://repo.spring.io/milestone" } 9 | } 10 | dependencies { 11 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 12 | } 13 | } 14 | 15 | apply plugin: 'java' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'org.springframework.boot' 18 | apply plugin: 'io.spring.dependency-management' 19 | 20 | group = 'com.example' 21 | version = '0.0.1' 22 | sourceCompatibility = 8 23 | 24 | repositories { 25 | mavenCentral() 26 | maven { url "https://repo.spring.io/snapshot" } 27 | maven { url "https://repo.spring.io/milestone" } 28 | } 29 | 30 | 31 | dependencies { 32 | //netty 33 | compile('org.springframework.boot:spring-boot-starter-webflux') 34 | 35 | //tomcat 36 | //compile('org.springframework.boot:spring-boot-starter-webflux'){ exclude module: "spring-boot-starter-netty"} 37 | //compile('org.springframework.boot:spring-boot-starter-tomcat') 38 | 39 | runtime('org.springframework.boot:spring-boot-devtools') 40 | compileOnly('org.projectlombok:lombok:1.16.20') 41 | testCompile('org.springframework.boot:spring-boot-starter-test') 42 | testCompile('io.projectreactor:reactor-test') 43 | } 44 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jdk-alpine 2 | VOLUME /tmp 3 | ADD app.jar app.jar 4 | EXPOSE 8080 5 | ENTRYPOINT ["java","-Xmx2048m","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] -------------------------------------------------------------------------------- /docker/aws-run.txt: -------------------------------------------------------------------------------- 1 | 2 | # reactivetest-tomcat 3 | aws ecs register-task-definition --cli-input-json file://reactivetest-tomcat-task.json 4 | aws ecs create-service --cluster default --service-name reactivetest-tomcat-service --task-definition reactivetest-tomcat:1 --desired-count 1 --launch-type "FARGATE" --network-configuration "awsvpcConfiguration={subnets=[subnet-eaca3086],securityGroups=[sg-4c59a03b],assignPublicIp="ENABLED"}" 5 | 6 | # reactivetest-netty 7 | aws ecs register-task-definition --cli-input-json file://reactivetest-netty-task.json 8 | aws ecs create-service --cluster default --service-name reactivetest-netty-service --task-definition reactivetest-netty:4 --desired-count 1 --launch-type "FARGATE" --network-configuration "awsvpcConfiguration={subnets=[subnet-eaca3086],securityGroups=[sg-4c59a03b],assignPublicIp="ENABLED"}" 9 | 10 | ------------- 11 | 12 | aws ecs update-service --service reactivetest-tomcat-service --desired-count 0 13 | aws ecs update-service --service reactivetest-netty-service --desired-count 0 14 | 15 | aws ecs delete-service --service reactivetest-tomcat-service 16 | aws ecs delete-service --service reactivetest-netty-service 17 | 18 | -------------------------------------------------------------------------------- /docker/docker-build-push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REGISTRY_SRV=trincao 4 | APP_NAME=reactivetest 5 | #IMG_NAME=reactivetest-netty 6 | IMG_NAME=reactivetest-tomcat 7 | APP_VERSION=0.0.1 8 | 9 | gradle -p .. build 10 | cp ../build/libs/${APP_NAME}-${APP_VERSION}.jar ./app.jar 11 | 12 | docker build -t ${IMG_NAME}:${APP_VERSION} . 13 | rm app.jar 14 | 15 | docker tag ${IMG_NAME}:${APP_VERSION} ${REGISTRY_SRV}/${IMG_NAME}:${APP_VERSION} 16 | docker push ${REGISTRY_SRV}/${IMG_NAME}:${APP_VERSION} 17 | 18 | 19 | -------------------------------------------------------------------------------- /docker/reactivetest-netty-task.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": "reactivetest-netty", 3 | "networkMode": "awsvpc", 4 | "containerDefinitions": [ 5 | { 6 | "name": "reactivetest-netty", 7 | "image": "trincao/reactivetest-netty:0.0.1", 8 | "portMappings": [ 9 | { 10 | "containerPort": 8080, 11 | "hostPort": 8080, 12 | "protocol": "tcp" 13 | } 14 | ], 15 | "logConfiguration": { 16 | "logDriver": "awslogs", 17 | "options": { 18 | "awslogs-group": "/ecs/reactivetest", 19 | "awslogs-region": "us-east-1", 20 | "awslogs-stream-prefix": "ecs" 21 | } 22 | }, 23 | "essential": true 24 | } 25 | ], 26 | "requiresCompatibilities": [ 27 | "FARGATE" 28 | ], 29 | "cpu": "256", 30 | "memory": "2048", 31 | "executionRoleArn": "ecsTaskExecutionRole" 32 | } -------------------------------------------------------------------------------- /docker/reactivetest-tomcat-task.json: -------------------------------------------------------------------------------- 1 | { 2 | "family": "reactivetest-tomcat", 3 | "networkMode": "awsvpc", 4 | "containerDefinitions": [ 5 | { 6 | "name": "reactivetest-tomcat", 7 | "image": "trincao/reactivetest-tomcat:0.0.1", 8 | "portMappings": [ 9 | { 10 | "containerPort": 8080, 11 | "hostPort": 8080, 12 | "protocol": "tcp" 13 | } 14 | ], 15 | "logConfiguration": { 16 | "logDriver": "awslogs", 17 | "options": { 18 | "awslogs-group": "/ecs/reactivetest", 19 | "awslogs-region": "us-east-1", 20 | "awslogs-stream-prefix": "ecs" 21 | } 22 | }, 23 | "essential": true 24 | } 25 | ], 26 | "requiresCompatibilities": [ 27 | "FARGATE" 28 | ], 29 | "cpu": "256", 30 | "memory": "2048", 31 | "executionRoleArn": "ecsTaskExecutionRole" 32 | } -------------------------------------------------------------------------------- /gatling/Simulation.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.concurrent.duration._ 3 | 4 | import io.gatling.core.Predef._ 5 | import io.gatling.http.Predef._ 6 | import io.gatling.jdbc.Predef._ 7 | 8 | class ServiceSimulation extends Simulation { 9 | var name="async" 10 | var users=1 11 | var rampupTime=30 seconds 12 | var delay=1 13 | var repeatTimes=1 14 | 15 | var url="http://localhost:8080" 16 | 17 | 18 | def startup(){ 19 | val httpProtocol = http.baseURL(url) 20 | 21 | val page = repeat(repeatTimes, "n") { 22 | exec(http(name) 23 | .get("/user/"+name+"?delay="+delay) 24 | ) 25 | } 26 | 27 | val scn = scenario("page").exec(page) 28 | 29 | setUp(scn.inject(rampUsers(users).over(rampupTime))).protocols(httpProtocol) 30 | } 31 | } 32 | 33 | class AsyncSimulation extends ServiceSimulation { 34 | name="async" 35 | //netty 36 | url="http://34.239.109.181:8080" 37 | } 38 | class SyncSimulation extends ServiceSimulation { 39 | name="sync" 40 | //tomcat 41 | url="http://52.90.193.73:8080" 42 | } 43 | 44 | class Async500d1u extends AsyncSimulation { 45 | users=1 46 | delay=500 47 | repeatTimes=240 48 | startup() 49 | } 50 | 51 | class Async500d10u extends AsyncSimulation { 52 | users=10 53 | delay=500 54 | repeatTimes=240 55 | startup() 56 | } 57 | 58 | class Async500d100u extends AsyncSimulation { 59 | users=100 60 | delay=500 61 | repeatTimes=240 62 | startup() 63 | } 64 | 65 | class Async500d200u extends AsyncSimulation { 66 | users=200 67 | delay=500 68 | repeatTimes=240 69 | startup() 70 | } 71 | 72 | class Async500d500u extends AsyncSimulation { 73 | users=500 74 | delay=500 75 | repeatTimes=240 76 | startup() 77 | } 78 | 79 | class Async500d1000u extends AsyncSimulation { 80 | users=1000 81 | delay=500 82 | repeatTimes=240 83 | startup() 84 | } 85 | 86 | class Async500d2000u extends AsyncSimulation { 87 | users=2000 88 | delay=500 89 | repeatTimes=240 90 | startup() 91 | } 92 | 93 | 94 | //SYNC 500d 95 | class Sync500d1u extends SyncSimulation { 96 | users=1 97 | delay=500 98 | repeatTimes=240 99 | startup() 100 | } 101 | 102 | class Sync500d10u extends SyncSimulation { 103 | users=10 104 | delay=500 105 | repeatTimes=240 106 | startup() 107 | } 108 | 109 | class Sync500d100u extends SyncSimulation { 110 | users=100 111 | delay=500 112 | repeatTimes=240 113 | startup() 114 | } 115 | 116 | class Sync500d200u extends SyncSimulation { 117 | users=200 118 | delay=500 119 | repeatTimes=240 120 | startup() 121 | } 122 | 123 | class Sync500d500u extends SyncSimulation { 124 | users=500 125 | delay=500 126 | repeatTimes=240 127 | startup() 128 | } 129 | 130 | class Sync500d1000u extends SyncSimulation { 131 | users=1000 132 | delay=500 133 | repeatTimes=240 134 | startup() 135 | } 136 | 137 | class Sync500d2000u extends SyncSimulation { 138 | users=2000 139 | delay=500 140 | repeatTimes=240 141 | startup() 142 | } 143 | 144 | 145 | 146 | 147 | // ASYNC 2000D 148 | class Async2000d500u extends AsyncSimulation { 149 | users=500 150 | delay=2000 151 | repeatTimes=60 152 | startup() 153 | } 154 | 155 | class Async2000d1000u extends AsyncSimulation { 156 | users=1000 157 | delay=2000 158 | repeatTimes=60 159 | startup() 160 | } 161 | 162 | class Async2000d2000u extends AsyncSimulation { 163 | users=2000 164 | delay=2000 165 | repeatTimes=60 166 | startup() 167 | } 168 | 169 | // SYNC 2000D 170 | class Sync2000d500u extends SyncSimulation { 171 | users=500 172 | delay=2000 173 | repeatTimes=60 174 | startup() 175 | } 176 | 177 | class Sync2000d1000u extends SyncSimulation { 178 | users=1000 179 | delay=2000 180 | repeatTimes=60 181 | startup() 182 | } 183 | 184 | class Sync2000d2000u extends SyncSimulation { 185 | users=2000 186 | delay=2000 187 | repeatTimes=60 188 | startup() 189 | } 190 | 191 | -------------------------------------------------------------------------------- /src/main/java/com/example/reactivetest/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.reactivetest; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MainApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(MainApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/example/reactivetest/User.java: -------------------------------------------------------------------------------- 1 | package com.example.reactivetest; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class User { 9 | private long id; 10 | private String email; 11 | private String name; 12 | private String phoneNr; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/example/reactivetest/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.reactivetest; 2 | 3 | import java.time.Duration; 4 | import java.util.Arrays; 5 | import java.util.Collection; 6 | import java.util.List; 7 | 8 | import org.springframework.stereotype.Component; 9 | 10 | import reactor.core.publisher.Flux; 11 | 12 | @Component 13 | public class UserRepository { 14 | 15 | private static final List userList = Arrays.asList(new User[] { // 16 | new User(1, "user1@example.com", "user1", "1230001"), // 17 | new User(2, "user2@example.com", "user2", "1230002"), // 18 | new User(3, "user3@example.com", "user3", "1230003") }); 19 | 20 | /** 21 | * Retrieve user list synchronously. Simulate the delay of a blocking call to a 22 | * DB 23 | */ 24 | public Collection getUsersSync(int responseDelay) { 25 | try { 26 | Thread.sleep(responseDelay); 27 | } catch (InterruptedException e) {} 28 | return userList; 29 | } 30 | 31 | /** 32 | * Retrieve user list asynchronously. Simulate delay of a call to a DB 33 | */ 34 | public Flux getUsersAsync(int responseDelay) { 35 | return Flux.fromIterable(userList).// 36 | delaySubscription(Duration.ofMillis(responseDelay)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/example/reactivetest/UserRestService.java: -------------------------------------------------------------------------------- 1 | package com.example.reactivetest; 2 | 3 | import java.util.Collection; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import reactor.core.publisher.Flux; 12 | 13 | /** 14 | * REST service with methods to fetch users synchronously or asynchrounously (reactive). 15 | */ 16 | @RestController 17 | @RequestMapping("/user") 18 | public class UserRestService { 19 | @Autowired 20 | UserRepository repository; 21 | 22 | @GetMapping("/sync") 23 | public Collection syncList(@RequestParam(defaultValue="500", value = "delay", required = false) int delay) 24 | throws InterruptedException { 25 | return repository.getUsersSync(delay); 26 | } 27 | 28 | @GetMapping("/async") 29 | public Flux asyncList(@RequestParam(defaultValue="500", value = "delay", required = false) int delay) { 30 | return repository.getUsersAsync(delay); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/example/samples/TestWithCB.java: -------------------------------------------------------------------------------- 1 | package com.example.samples; 2 | 3 | import com.example.reactivetest.User; 4 | 5 | public class TestWithCB { 6 | public void asyncWithCallbacks(int id) { 7 | getUserFromDB(id, user -> { 8 | convertUser(user, convertedUser -> { 9 | processResult(convertedUser, processedUser -> { 10 | showResult(processedUser); 11 | }); 12 | }); 13 | }); 14 | } 15 | 16 | public void getUserFromDB(int id, DbResult result) { 17 | // ... 18 | } 19 | 20 | public void convertUser(User user, ConvertStatus status) { 21 | // ... 22 | } 23 | 24 | public void processResult(User user, ProcessStatus status) { 25 | // ... 26 | } 27 | 28 | private void showResult(User user) { 29 | // ... 30 | } 31 | 32 | public interface DbResult { 33 | public void onResult(User user); 34 | } 35 | 36 | public interface ProcessStatus { 37 | public void onResult(User user); 38 | } 39 | 40 | public interface ConvertStatus { 41 | public void onResult(User user); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/example/samples/TestWithReactive.java: -------------------------------------------------------------------------------- 1 | package com.example.samples; 2 | 3 | import com.example.reactivetest.User; 4 | 5 | import reactor.core.publisher.Flux; 6 | 7 | public class TestWithReactive { 8 | public void asyncWithReactive(int id) { 9 | getUserFromDBAsync(id) 10 | .map(user -> convertUser(user)) 11 | .map(user -> processResult(user)) 12 | .subscribe(user -> showResults(user)); 13 | } 14 | 15 | public void sync(int id) { 16 | User user = getUserFromDBSync(id); 17 | user = convertUser(user); 18 | user = processResult(user); 19 | showResults(user); 20 | } 21 | 22 | public Flux getUserFromDBAsync(int id) { 23 | return Flux.just(new User(id, null, null, null)); 24 | } 25 | 26 | public User getUserFromDBSync(int id) { 27 | return new User(id, null, null, null); 28 | } 29 | 30 | public User convertUser(User user) { 31 | return user; 32 | } 33 | 34 | public User processResult(User user) { 35 | return user; 36 | } 37 | 38 | private void showResults(User user) { 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port = 8080 2 | server.tomcat.max-threads=10000 3 | --------------------------------------------------------------------------------