Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,33 @@ This means that whenever you are working with variables coming Spring, you gener
One particular place to watch out for this is when using Spring's `@RequestParam` and `@PathVariable` annotations in controllers.

## Spring's ThreadLocal Context
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.
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.
This becomes particularly annoying when interfacing between things like controller entry points and services and utilities that are built
around IO/Future/ZIO etc. monads. Effectively, trying to access something like Spring Security's SecurityContext from these methods
will not work. The best solution I have found is to pass the SecurityContext and any other ThreadLocal context as an argument to
will not work. Without going into too much detail WebFlux has the same basic problem, even though its not technically using ThreadLocal context.

The best solution I have found is to pass the SecurityContext and any other ThreadLocal / pseudo global context data as an argument to
these methods. This is not ideal, but it is the best solution I have found so far.

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

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
about it.
### Async Controllers
The original version of this project used WebMVC which is built on top of Apache Tomcat and has its own async programming model.
I've since switched to using WebFlux which is built on top of Netty and is generally considered to be more performant, particularly
when it comes to servicing large numbers of requests concurrently. I would not be surprised if this changes in the future
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)
of this repository.

### Async Database Drivers
This project uses Doobie, which is built on top of JDBC which is synchronous. There is another library, Skunk, which is written
by the same author and offers similar functionality. It's fully asynchronous but also locks you into using Postgres.

Another option would be to use one Spring's database facilities that supports R2DBC, which is also async. I've not tried this approach
yet but imagine it could be wrapped with cats-effect IO similarly to what was done with [Mono] in the controller layer.

# Future Improvements
## Spring Security
Expand Down
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ repositories {
dependencies {
implementation 'org.scala-lang:scala3-library_3:3.6.4'
implementation 'org.typelevel:cats-effect_3:3.6.1'
implementation('org.springframework.boot:spring-boot-starter-web') {
implementation("org.springframework.boot:spring-boot-starter-webflux") {
exclude group: 'com.fasterxml.jackson.core'
exclude group: 'com.fasterxml.jackson.datatype'
exclude group: 'com.fasterxml.jackson.module'
Expand Down Expand Up @@ -55,6 +55,10 @@ dependencies {
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

testImplementation 'org.springframework.boot:spring-boot-starter-test'

testImplementation('com.github.javafaker:javafaker:1.0.2') {
exclude group: 'org.yaml'
}
}

test {
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ version: '3.8'

services:
sse_app:
container_name: sse_app
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
container_name: sse_app
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://sse_postgres:5432/postgres
SPRING_DATASOURCE_USERNAME: postgres
Expand All @@ -16,8 +16,8 @@ services:
- sse_postgres

sse_postgres:
image: postgres:15
container_name: sse_postgres
image: postgres:15
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
Expand Down
2 changes: 2 additions & 0 deletions extras/taurus/benchmark_localhost.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
docker run -it --rm -v ./tests:/bzt-configs blazemeter/taurus test.yml -o settings.check-plugins=false

38 changes: 38 additions & 0 deletions extras/taurus/tests/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#modules:
# jmeter:
# disable-plugins:
# - aggregate-report
# - view-results-tree
# - view-results-in-table
# - summary-report
# java-opts:
# - "-Djava.awt.headless=true"
# - "-XX:-TieredCompilation"
# - "-Xmx512m"

execution:
- concurrency: 10
ramp-up: 30s
hold-for: 1m
scenario: simple

scenarios:
simple:
requests:
- url: http://host.docker.internal:8080/
method: GET

#settings:
# artifacts-dir: /output

reporting:
- module: console
- module: final-stats
summary: true
# - module: junit-xml
# filename: /output/results.xml

monitoring:
- module: local
cpu: true
memory: true
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
4 changes: 3 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading