Skip to content

Commit 710dc97

Browse files
authored
Switch to WebFlux / Jetty (#2)
* adds empty async controller to be used later * basic webflux implementation * remove logging from json encoder * add lightweight benchmarking script to help with optimizing async setup * benchmarking script tweak * update readme * adds geometry data to Address to demonstrate postgis integration. also adds to testutils. * update gradle * explicitly set default profile * better table enumeration for resetting persistence in tests * update readme
1 parent 804c3c4 commit 710dc97

27 files changed

Lines changed: 651 additions & 404 deletions

README.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,33 @@ This means that whenever you are working with variables coming Spring, you gener
8181
One particular place to watch out for this is when using Spring's `@RequestParam` and `@PathVariable` annotations in controllers.
8282

8383
## Spring's ThreadLocal Context
84-
Much of Spring's async programming model relies on ThreadLocal context. This used to be a common pattern in Java, but not one that is used in Scala.
84+
Much of Spring's async programming model relies on ThreadLocal context, particularly when using WebMVC. This used to be a common pattern in Java, but not one that is used in Scala.
8585
This becomes particularly annoying when interfacing between things like controller entry points and services and utilities that are built
8686
around IO/Future/ZIO etc. monads. Effectively, trying to access something like Spring Security's SecurityContext from these methods
87-
will not work. The best solution I have found is to pass the SecurityContext and any other ThreadLocal context as an argument to
87+
will not work. Without going into too much detail WebFlux has the same basic problem, even though its not technically using ThreadLocal context.
88+
89+
The best solution I have found is to pass the SecurityContext and any other ThreadLocal / pseudo global context data as an argument to
8890
these methods. This is not ideal, but it is the best solution I have found so far.
8991

9092
## Async Programming
9193
Spring has it's own mechanisms for async programming, and it takes some work to adapt it to be compatible with IO monads.
92-
Even after adapting these mechanisms we are left with having to manage an additional threadpool to accommodate Spring.
93-
The other challenge here is adapting the handling of uncaught exceptions so that Spring's conventional mechanisms will
94+
Even after adapting these mechanisms we are left with having to manage an additional threadpool(s) to accommodate Spring.
95+
Another challenge here is adapting the handling of uncaught exceptions so that Spring's conventional mechanisms will
9496
continue to function.
9597

96-
I've not gotten around to adding this to the example yet, but it is doable, and once it's done you can pretty much forget
97-
about it.
98+
### Async Controllers
99+
The original version of this project used WebMVC which is built on top of Apache Tomcat and has its own async programming model.
100+
I've since switched to using WebFlux which is built on top of Netty and is generally considered to be more performant, particularly
101+
when it comes to servicing large numbers of requests concurrently. I would not be surprised if this changes in the future
102+
thanks to the work being done on Project Loom. For those interested in exploring this further, check out the [webmvc tag](https://github.com/halfhp/ScalaSpringExperiment/releases/tag/webmvc)
103+
of this repository.
104+
105+
### Async Database Drivers
106+
This project uses Doobie, which is built on top of JDBC which is synchronous. There is another library, Skunk, which is written
107+
by the same author and offers similar functionality. It's fully asynchronous but also locks you into using Postgres.
108+
109+
Another option would be to use one Spring's database facilities that supports R2DBC, which is also async. I've not tried this approach
110+
yet but imagine it could be wrapped with cats-effect IO similarly to what was done with [Mono] in the controller layer.
98111

99112
# Future Improvements
100113
## Spring Security

build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ repositories {
2121
dependencies {
2222
implementation 'org.scala-lang:scala3-library_3:3.6.4'
2323
implementation 'org.typelevel:cats-effect_3:3.6.1'
24-
implementation('org.springframework.boot:spring-boot-starter-web') {
24+
implementation("org.springframework.boot:spring-boot-starter-webflux") {
2525
exclude group: 'com.fasterxml.jackson.core'
2626
exclude group: 'com.fasterxml.jackson.datatype'
2727
exclude group: 'com.fasterxml.jackson.module'
@@ -55,6 +55,10 @@ dependencies {
5555
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
5656

5757
testImplementation 'org.springframework.boot:spring-boot-starter-test'
58+
59+
testImplementation('com.github.javafaker:javafaker:1.0.2') {
60+
exclude group: 'org.yaml'
61+
}
5862
}
5963

6064
test {

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ version: '3.8'
22

33
services:
44
sse_app:
5+
container_name: sse_app
56
build:
67
context: .
78
dockerfile: Dockerfile
89
ports:
910
- "8080:8080"
10-
container_name: sse_app
1111
environment:
1212
SPRING_DATASOURCE_URL: jdbc:postgresql://sse_postgres:5432/postgres
1313
SPRING_DATASOURCE_USERNAME: postgres
@@ -16,8 +16,8 @@ services:
1616
- sse_postgres
1717

1818
sse_postgres:
19-
image: postgres:15
2019
container_name: sse_postgres
20+
image: postgres:15
2121
environment:
2222
POSTGRES_DB: postgres
2323
POSTGRES_USER: postgres
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
docker run -it --rm -v ./tests:/bzt-configs blazemeter/taurus test.yml -o settings.check-plugins=false
2+

extras/taurus/tests/test.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#modules:
2+
# jmeter:
3+
# disable-plugins:
4+
# - aggregate-report
5+
# - view-results-tree
6+
# - view-results-in-table
7+
# - summary-report
8+
# java-opts:
9+
# - "-Djava.awt.headless=true"
10+
# - "-XX:-TieredCompilation"
11+
# - "-Xmx512m"
12+
13+
execution:
14+
- concurrency: 10
15+
ramp-up: 30s
16+
hold-for: 1m
17+
scenario: simple
18+
19+
scenarios:
20+
simple:
21+
requests:
22+
- url: http://host.docker.internal:8080/
23+
method: GET
24+
25+
#settings:
26+
# artifacts-dir: /output
27+
28+
reporting:
29+
- module: console
30+
- module: final-stats
31+
summary: true
32+
# - module: junit-xml
33+
# filename: /output/results.xml
34+
35+
monitoring:
36+
- module: local
37+
cpu: true
38+
memory: true

gradle/wrapper/gradle-wrapper.jar

-15.7 KB
Binary file not shown.
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
4+
networkTimeout=10000
5+
validateDistributionUrl=true
46
zipStoreBase=GRADLE_USER_HOME
57
zipStorePath=wrapper/dists

0 commit comments

Comments
 (0)