├── .envrc ├── .github ├── CODEOWNERS ├── scripts │ ├── matrix-services.js │ └── variables.js └── workflows │ └── main.yml ├── .gitignore ├── .idea └── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── builder ├── database.sh ├── runner.sh └── service.sh ├── config.json ├── docker-compose.yml ├── eco-benchmark-boavizta.drawio.svg ├── eco-benchmark.code-workspace ├── migrations ├── 20220408080000_init.sql ├── Dockerfile └── README.md ├── readme.md ├── results ├── ecobenchmark.ipynb ├── parse-raw-data.py └── results │ ├── 20220627-120000.csv │ └── 20220628-193500.csv ├── runner ├── Dockerfile ├── README.md └── scenario.js ├── script ├── hosts ├── inventory-go-pgx ├── inventory-jvm-java-quarkus ├── inventory-jvm-java-quarkus-reactive ├── inventory-jvm-kotlin-spring ├── inventory-native-java-quarkus ├── inventory-node-express-sequelize ├── inventory-php-symfony ├── inventory-rust-actix-sqlx ├── readme.md ├── roles │ ├── cleanup │ │ └── tasks │ │ │ └── main.yml │ ├── deploy │ │ └── tasks │ │ │ └── main.yml │ ├── execute │ │ └── tasks │ │ │ └── main.yml │ ├── gathering │ │ └── tasks │ │ │ └── main.yml │ └── install │ │ └── tasks │ │ └── main.yml ├── run-all.sh └── site.yml ├── service ├── EcobenchmarkBackEnd-0.0.1-swagger.yaml ├── go-pgx │ ├── Dockerfile │ ├── README.md │ ├── common │ │ └── JSONDate.go │ ├── controllers │ │ ├── add_account │ │ │ ├── controller.go │ │ │ ├── input.go │ │ │ └── output.go │ │ ├── add_list │ │ │ ├── controller.go │ │ │ ├── input.go │ │ │ └── output.go │ │ ├── add_task_to_list │ │ │ ├── controller.go │ │ │ ├── input.go │ │ │ └── output.go │ │ ├── get_lists │ │ │ ├── controller.go │ │ │ └── output.go │ │ └── get_stats │ │ │ ├── controller.go │ │ │ └── output.go │ ├── docker-compose.yml │ ├── go.mod │ ├── go.sum │ ├── infra │ │ └── storage │ │ │ └── storage.go │ ├── main.go │ └── usecases │ │ ├── add_account │ │ ├── add_account.go │ │ ├── interfaces.go │ │ └── output.go │ │ ├── add_list │ │ ├── add_list.go │ │ ├── input.go │ │ ├── interfaces.go │ │ └── output.go │ │ ├── add_task_to_list │ │ ├── add_task.go │ │ ├── input.go │ │ ├── interfaces.go │ │ └── output.go │ │ ├── get_lists │ │ ├── get_lists.go │ │ ├── input.go │ │ ├── interfaces.go │ │ └── output.go │ │ └── get_stats │ │ ├── get_stats.go │ │ ├── interfaces.go │ │ └── output.go ├── jvm-java-quarkus-reactive │ ├── .dockerignore │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── .gitignore │ │ │ ├── MavenWrapperDownloader.java │ │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── README.md │ ├── docker-compose.yml │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── docker │ │ │ ├── Dockerfile.jvm │ │ │ ├── Dockerfile.legacy-jar │ │ │ ├── Dockerfile.native │ │ │ └── Dockerfile.native-micro │ │ ├── java │ │ │ └── com │ │ │ │ └── ecobenchmark │ │ │ │ ├── GreetingResource.java │ │ │ │ ├── controllers │ │ │ │ ├── Healthcheck.java │ │ │ │ ├── addaccount │ │ │ │ │ ├── AccountRequest.java │ │ │ │ │ ├── AccountResponse.java │ │ │ │ │ └── AddAccountResource.java │ │ │ │ ├── addlist │ │ │ │ │ ├── AddListResource.java │ │ │ │ │ ├── ListRequest.java │ │ │ │ │ └── ListResponse.java │ │ │ │ ├── addtask │ │ │ │ │ ├── AddTaskResource.java │ │ │ │ │ ├── TaskRequest.java │ │ │ │ │ └── TaskResponse.java │ │ │ │ ├── getlists │ │ │ │ │ ├── GetListsResource.java │ │ │ │ │ ├── ListResponse.java │ │ │ │ │ └── TaskResponse.java │ │ │ │ └── getstats │ │ │ │ │ ├── GetStatsResource.java │ │ │ │ │ └── StatsResponse.java │ │ │ │ ├── entities │ │ │ │ ├── Account.java │ │ │ │ ├── ListEntity.java │ │ │ │ ├── Task.java │ │ │ │ └── TimestampWithTimezone.java │ │ │ │ └── repositories │ │ │ │ ├── AccountRepository.java │ │ │ │ ├── ListEntityRepository.java │ │ │ │ └── TaskRepository.java │ │ └── resources │ │ │ ├── META-INF │ │ │ └── resources │ │ │ │ └── index.html │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── com │ │ └── ecobenchmark │ │ ├── FullScenarioTest.java │ │ ├── GreetingResourceIT.java │ │ └── GreetingResourceTest.java ├── jvm-java-quarkus │ ├── .dockerignore │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── .gitignore │ │ │ ├── MavenWrapperDownloader.java │ │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── README.md │ ├── docker-compose.yml │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── docker │ │ │ ├── Dockerfile.jvm │ │ │ ├── Dockerfile.legacy-jar │ │ │ ├── Dockerfile.native │ │ │ └── Dockerfile.native-micro │ │ ├── java │ │ │ └── com │ │ │ │ └── ecobenchmark │ │ │ │ ├── GreetingResource.java │ │ │ │ ├── controllers │ │ │ │ ├── Healthcheck.java │ │ │ │ ├── addaccount │ │ │ │ │ ├── AccountRequest.java │ │ │ │ │ ├── AccountResponse.java │ │ │ │ │ └── AddAccountResource.java │ │ │ │ ├── addlist │ │ │ │ │ ├── AddListResource.java │ │ │ │ │ ├── ListRequest.java │ │ │ │ │ └── ListResponse.java │ │ │ │ ├── addtask │ │ │ │ │ ├── AddTaskResource.java │ │ │ │ │ ├── TaskRequest.java │ │ │ │ │ └── TaskResponse.java │ │ │ │ ├── getlists │ │ │ │ │ ├── GetListsResource.java │ │ │ │ │ ├── ListResponse.java │ │ │ │ │ └── TaskResponse.java │ │ │ │ └── getstats │ │ │ │ │ ├── GetStatsResource.java │ │ │ │ │ └── StatsResponse.java │ │ │ │ ├── entities │ │ │ │ ├── Account.java │ │ │ │ ├── ListEntity.java │ │ │ │ └── Task.java │ │ │ │ └── repositories │ │ │ │ ├── AccountRepository.java │ │ │ │ ├── ListEntityRepository.java │ │ │ │ └── TaskRepository.java │ │ └── resources │ │ │ ├── META-INF │ │ │ └── resources │ │ │ │ └── index.html │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── com │ │ └── ecobenchmark │ │ ├── FullScenarioTest.java │ │ ├── GreetingResourceIT.java │ │ └── GreetingResourceTest.java ├── jvm-kotlin-spring │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── build.gradle.kts │ ├── docker-compose.yml │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── ecobenchmark │ │ │ └── kotlinspringjpa │ │ │ ├── KotlinSpringJpaApplication.kt │ │ │ ├── controllers │ │ │ ├── Healthcheck.kt │ │ │ ├── addaccount │ │ │ │ ├── AccountRequest.kt │ │ │ │ ├── AccountResponse.kt │ │ │ │ └── AddAccount.kt │ │ │ ├── addlist │ │ │ │ ├── AddList.kt │ │ │ │ ├── ListRequest.kt │ │ │ │ └── ListResponse.kt │ │ │ ├── addtask │ │ │ │ ├── AddTask.kt │ │ │ │ ├── TaskRequest.kt │ │ │ │ └── TaskResponse.kt │ │ │ ├── getlists │ │ │ │ ├── GetLists.kt │ │ │ │ ├── ListResponse.kt │ │ │ │ └── TaskResponse.kt │ │ │ └── getstats │ │ │ │ ├── GetStats.kt │ │ │ │ └── StatsResponse.kt │ │ │ ├── entities │ │ │ ├── Account.kt │ │ │ ├── ListEntity.kt │ │ │ └── Task.kt │ │ │ └── repositories │ │ │ ├── AccountRepository.kt │ │ │ ├── ListRepository.kt │ │ │ └── TaskRepository.kt │ │ └── resources │ │ └── application.properties ├── native-java-quarkus │ ├── .dockerignore │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── .gitignore │ │ │ ├── MavenWrapperDownloader.java │ │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── README.md │ ├── docker-compose.yml │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── docker │ │ │ ├── Dockerfile.jvm │ │ │ ├── Dockerfile.legacy-jar │ │ │ ├── Dockerfile.native │ │ │ └── Dockerfile.native-micro │ │ ├── java │ │ │ └── com │ │ │ │ └── ecobenchmark │ │ │ │ ├── GreetingResource.java │ │ │ │ ├── controllers │ │ │ │ ├── Healthcheck.java │ │ │ │ ├── addaccount │ │ │ │ │ ├── AccountRequest.java │ │ │ │ │ ├── AccountResponse.java │ │ │ │ │ └── AddAccountResource.java │ │ │ │ ├── addlist │ │ │ │ │ ├── AddListResource.java │ │ │ │ │ ├── ListRequest.java │ │ │ │ │ └── ListResponse.java │ │ │ │ ├── addtask │ │ │ │ │ ├── AddTaskResource.java │ │ │ │ │ ├── TaskRequest.java │ │ │ │ │ └── TaskResponse.java │ │ │ │ ├── getlists │ │ │ │ │ ├── GetListsResource.java │ │ │ │ │ ├── ListResponse.java │ │ │ │ │ └── TaskResponse.java │ │ │ │ └── getstats │ │ │ │ │ ├── GetStatsResource.java │ │ │ │ │ └── StatsResponse.java │ │ │ │ ├── entities │ │ │ │ ├── Account.java │ │ │ │ ├── ListEntity.java │ │ │ │ └── Task.java │ │ │ │ └── repositories │ │ │ │ ├── AccountRepository.java │ │ │ │ ├── ListEntityRepository.java │ │ │ │ └── TaskRepository.java │ │ └── resources │ │ │ ├── META-INF │ │ │ └── resources │ │ │ │ └── index.html │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── com │ │ └── ecobenchmark │ │ ├── FullScenarioTest.java │ │ ├── GreetingResourceIT.java │ │ └── GreetingResourceTest.java ├── node-express-sequelize │ ├── .gitignore │ ├── Dockerfile │ ├── docker-compose.yml │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── handler │ │ ├── create-account.js │ │ ├── create-list.js │ │ ├── create-task.js │ │ ├── healthcheck.js │ │ ├── index.js │ │ ├── list-list.js │ │ └── stats.js │ │ ├── main.js │ │ ├── middleware │ │ └── error-handler.js │ │ ├── model │ │ ├── account.js │ │ ├── index.js │ │ ├── list.js │ │ └── task.js │ │ └── service │ │ └── database.js ├── php-symfony-apache2-dev │ ├── .env │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── apache.conf │ ├── bin │ │ └── console │ ├── composer.json │ ├── composer.lock │ ├── config │ │ ├── bundles.php │ │ ├── packages │ │ │ ├── cache.yaml │ │ │ ├── doctrine.yaml │ │ │ ├── doctrine_migrations.yaml │ │ │ ├── framework.yaml │ │ │ ├── routing.yaml │ │ │ ├── sensio_framework_extra.yaml │ │ │ └── web_profiler.yaml │ │ ├── preload.php │ │ ├── routes.yaml │ │ ├── routes │ │ │ ├── annotations.yaml │ │ │ ├── framework.yaml │ │ │ └── web_profiler.yaml │ │ └── services.yaml │ ├── docker-compose.yml │ ├── migrations │ │ └── .gitignore │ ├── public │ │ └── index.php │ ├── src │ │ ├── Controller │ │ │ ├── AddAccount │ │ │ │ ├── AccountRequest.php │ │ │ │ ├── AccountResponse.php │ │ │ │ └── AddAccount.php │ │ │ ├── AddList │ │ │ │ ├── AddList.php │ │ │ │ ├── ListRequest.php │ │ │ │ └── ListResponse.php │ │ │ ├── AddTask │ │ │ │ ├── AddTask.php │ │ │ │ ├── TaskRequest.php │ │ │ │ └── TaskResponse.php │ │ │ ├── GetLists │ │ │ │ ├── GetLists.php │ │ │ │ ├── ListResponse.php │ │ │ │ └── TaskResponse.php │ │ │ ├── GetStats │ │ │ │ ├── GetStats.php │ │ │ │ └── StatResponse.php │ │ │ └── Healthcheck.php │ │ ├── Entity │ │ │ ├── Account.php │ │ │ ├── ListEntity.php │ │ │ └── Task.php │ │ ├── Kernel.php │ │ └── Repository │ │ │ ├── .gitignore │ │ │ ├── AccountRepository.php │ │ │ ├── ListEntityRepository.php │ │ │ └── TaskRepository.php │ ├── symfony.lock │ └── templates │ │ └── base.html.twig ├── php-symfony-apache2 │ ├── .env │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── apache.conf │ ├── bin │ │ └── console │ ├── composer.json │ ├── composer.lock │ ├── config │ │ ├── bundles.php │ │ ├── packages │ │ │ ├── cache.yaml │ │ │ ├── doctrine.yaml │ │ │ ├── doctrine_migrations.yaml │ │ │ ├── framework.yaml │ │ │ ├── routing.yaml │ │ │ ├── sensio_framework_extra.yaml │ │ │ └── web_profiler.yaml │ │ ├── preload.php │ │ ├── routes.yaml │ │ ├── routes │ │ │ ├── annotations.yaml │ │ │ ├── framework.yaml │ │ │ └── web_profiler.yaml │ │ └── services.yaml │ ├── docker-compose.yml │ ├── docker │ │ └── php.ini │ ├── migrations │ │ └── .gitignore │ ├── public │ │ └── index.php │ ├── src │ │ ├── Controller │ │ │ ├── AddAccount │ │ │ │ ├── AccountRequest.php │ │ │ │ ├── AccountResponse.php │ │ │ │ └── AddAccount.php │ │ │ ├── AddList │ │ │ │ ├── AddList.php │ │ │ │ ├── ListRequest.php │ │ │ │ └── ListResponse.php │ │ │ ├── AddTask │ │ │ │ ├── AddTask.php │ │ │ │ ├── TaskRequest.php │ │ │ │ └── TaskResponse.php │ │ │ ├── GetLists │ │ │ │ ├── GetLists.php │ │ │ │ ├── ListResponse.php │ │ │ │ └── TaskResponse.php │ │ │ ├── GetStats │ │ │ │ ├── GetStats.php │ │ │ │ └── StatResponse.php │ │ │ └── Healthcheck.php │ │ ├── Entity │ │ │ ├── Account.php │ │ │ ├── ListEntity.php │ │ │ └── Task.php │ │ ├── Kernel.php │ │ └── Repository │ │ │ ├── .gitignore │ │ │ ├── AccountRepository.php │ │ │ ├── ListEntityRepository.php │ │ │ └── TaskRepository.php │ ├── symfony.lock │ └── templates │ │ └── base.html.twig ├── php-symfony-nginx │ ├── .env │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── bin │ │ └── console │ ├── composer.json │ ├── composer.lock │ ├── config │ │ ├── bundles.php │ │ ├── packages │ │ │ ├── cache.yaml │ │ │ ├── doctrine.yaml │ │ │ ├── doctrine_migrations.yaml │ │ │ ├── framework.yaml │ │ │ ├── routing.yaml │ │ │ ├── sensio_framework_extra.yaml │ │ │ └── web_profiler.yaml │ │ ├── preload.php │ │ ├── routes.yaml │ │ ├── routes │ │ │ ├── annotations.yaml │ │ │ ├── framework.yaml │ │ │ └── web_profiler.yaml │ │ └── services.yaml │ ├── docker-compose.yml │ ├── docker │ │ ├── default.conf │ │ ├── entrypoint.sh │ │ ├── php.ini │ │ └── www.conf │ ├── migrations │ │ └── .gitignore │ ├── public │ │ └── index.php │ ├── src │ │ ├── Controller │ │ │ ├── AddAccount │ │ │ │ ├── AccountRequest.php │ │ │ │ ├── AccountResponse.php │ │ │ │ └── AddAccount.php │ │ │ ├── AddList │ │ │ │ ├── AddList.php │ │ │ │ ├── ListRequest.php │ │ │ │ └── ListResponse.php │ │ │ ├── AddTask │ │ │ │ ├── AddTask.php │ │ │ │ ├── TaskRequest.php │ │ │ │ └── TaskResponse.php │ │ │ ├── GetLists │ │ │ │ ├── GetLists.php │ │ │ │ ├── ListResponse.php │ │ │ │ └── TaskResponse.php │ │ │ ├── GetStats │ │ │ │ ├── GetStats.php │ │ │ │ └── StatResponse.php │ │ │ └── Healthcheck.php │ │ ├── Entity │ │ │ ├── Account.php │ │ │ ├── ListEntity.php │ │ │ └── Task.php │ │ ├── Kernel.php │ │ └── Repository │ │ │ ├── .gitignore │ │ │ ├── AccountRepository.php │ │ │ ├── ListEntityRepository.php │ │ │ └── TaskRepository.php │ ├── symfony.lock │ └── templates │ │ └── base.html.twig ├── python-fastapi │ ├── Dockerfile │ ├── app │ │ ├── database.py │ │ └── main.py │ ├── docker-compose.yml │ └── requirements.txt ├── python-flask │ ├── .envrc │ ├── Dockerfile │ ├── app │ │ ├── database.py │ │ └── main.py │ ├── docker-compose.yml │ └── requirements.txt ├── rust-actix-native │ ├── .dockerignore │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── Dockerfile │ ├── docker-compose.yml │ └── src │ │ ├── error.rs │ │ ├── handler │ │ ├── accounts_create.rs │ │ ├── health.rs │ │ ├── lists_create.rs │ │ ├── lists_list.rs │ │ ├── mod.rs │ │ ├── stats.rs │ │ └── tasks_create.rs │ │ ├── main.rs │ │ ├── model │ │ ├── account │ │ │ ├── create.rs │ │ │ └── mod.rs │ │ ├── list │ │ │ ├── create.rs │ │ │ ├── list.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── stats.rs │ │ └── task │ │ │ ├── create.rs │ │ │ └── mod.rs │ │ └── service │ │ ├── database.rs │ │ └── mod.rs ├── rust-actix-sqlx │ ├── .dockerignore │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── Dockerfile │ ├── docker-compose.yml │ └── src │ │ ├── error.rs │ │ ├── handler │ │ ├── accounts_create.rs │ │ ├── health.rs │ │ ├── lists_create.rs │ │ ├── lists_list.rs │ │ ├── mod.rs │ │ ├── stats.rs │ │ └── tasks_create.rs │ │ ├── main.rs │ │ ├── model │ │ ├── account │ │ │ ├── create.rs │ │ │ └── mod.rs │ │ ├── list │ │ │ ├── create.rs │ │ │ ├── list.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── stats.rs │ │ └── task │ │ │ ├── create.rs │ │ │ └── mod.rs │ │ └── service │ │ ├── database.rs │ │ └── mod.rs ├── rust-axum-sqlx │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── Dockerfile │ ├── docker-compose.yml │ └── src │ │ ├── error.rs │ │ ├── handler │ │ ├── accounts_create.rs │ │ ├── healthcheck.rs │ │ ├── lists_create.rs │ │ ├── lists_list.rs │ │ ├── mod.rs │ │ ├── stats.rs │ │ └── tasks_create.rs │ │ ├── main.rs │ │ ├── model │ │ ├── account │ │ │ ├── create.rs │ │ │ └── mod.rs │ │ ├── list │ │ │ ├── create.rs │ │ │ ├── list.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── stats.rs │ │ └── task │ │ │ ├── create.rs │ │ │ └── mod.rs │ │ └── service │ │ ├── database.rs │ │ └── mod.rs └── specs.md ├── test ├── README.md └── check-service-conformity.feature └── validator ├── Dockerfile ├── README.md └── scenario.js /.envrc: -------------------------------------------------------------------------------- 1 | layout python3 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jdrouet @youenchene 2 | /builder/ @jdrouet 3 | /migrations/ @jdrouet @youenchene 4 | /results/ecobenchmark.ipynb @youenchene 5 | /runner/ @jdrouet 6 | /script/ @jdrouet 7 | /service/go-pgx/ @youenchene 8 | /service/jvm-kotlin-spring/ @youenchene 9 | /service/node-express-sequelize/ @jdrouet 10 | /service/php-symfony/ @youenchene 11 | /service/rust-actix-native/ @jdrouet 12 | /service/rust-actix-sqlx/ @jdrouet 13 | /service/rust-axum-sqlx/ @jdrouet 14 | /validator/ @jdrouet 15 | -------------------------------------------------------------------------------- /.github/scripts/matrix-services.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs/promises'); 2 | 3 | fs.readdir('./service', { withFileTypes: true }) 4 | .then((entries) => { 5 | const dir_names = entries 6 | .filter((entry) => entry.isDirectory()) 7 | .map((entry) => entry.name); 8 | console.log(`::set-output name=services::${JSON.stringify(dir_names)}`); 9 | }) 10 | .catch((err) => { 11 | console.error(err); 12 | }); 13 | -------------------------------------------------------------------------------- /.github/scripts/variables.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | 3 | const config = JSON.parse(fs.readFileSync('./config.json')); 4 | 5 | const officialRepo = process.env.GITHUB_REPOSITORY === 'Boavizta/ecobenchmark-applicationweb-backend'; 6 | const usecaseFromEnv = (() => { 7 | if (process.env.GITHUB_REF) { 8 | if (process.env.GITHUB_REF === 'ref/head/main') { 9 | return 'default'; 10 | } 11 | if (process.env.GITHUB_REF.startsWith('ref/head/usecase-')) { 12 | return process.env.GITHUB_REF.substr(17); 13 | } 14 | } 15 | return 'other'; 16 | })(); 17 | const canPushImage = officialRepo && config.usecase === usecaseFromEnv; 18 | 19 | console.log(`::set-output name=usecase::${config.usecase}`); 20 | console.log(`::set-output name=push::${canPushImage}`); 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | runner/eco-bench-runner 2 | 3 | results/*.json 4 | results/*.png 5 | 6 | *.code-worksapce 7 | 8 | #Karate 9 | test/target/ 10 | test/karate*.jar 11 | #Maven 12 | target/ 13 | pom.xml.tag 14 | pom.xml.releaseBackup 15 | pom.xml.versionsBackup 16 | release.properties 17 | .flattened-pom.xml 18 | 19 | # Eclipse 20 | .project 21 | .classpath 22 | .settings/ 23 | bin/ 24 | 25 | # IntelliJ 26 | .idea 27 | *.ipr 28 | *.iml 29 | *.iws 30 | 31 | # NetBeans 32 | nb-configuration.xml 33 | 34 | # Visual Studio Code 35 | .vscode 36 | .factorypath 37 | 38 | # OSX 39 | .DS_Store 40 | 41 | # Vim 42 | *.swp 43 | *.swo 44 | 45 | # patch 46 | *.orig 47 | *.rej 48 | 49 | # Local environment 50 | .env 51 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build-runner: 2 | docker buildx build --output type=local,dest=$(shell pwd)/runner runner 3 | 4 | -------------------------------------------------------------------------------- /builder/database.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | 4 | if test "$#" -ne 1; then 5 | echo "Invalid number of arguments..." 6 | echo 7 | echo "Usage: ./builder/database.sh " 8 | echo 9 | echo "Available tags:" 10 | echo "default (the use case to compare to)" 11 | echo 12 | exit 1 13 | fi 14 | 15 | export ORG=${ORG:-jdrouet} 16 | export use_case=$1 17 | 18 | echo "Building database for use case $use_case" 19 | 20 | docker buildx build --push --tag "$ORG/eco-benchmark:database-$use_case" ./migrations 21 | 22 | exit 0 23 | 24 | -------------------------------------------------------------------------------- /builder/runner.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | 4 | if test "$#" -ne 1; then 5 | echo "Invalid number of arguments..." 6 | echo 7 | echo "Usage: ./builder/runner.sh " 8 | echo 9 | echo "Available tags:" 10 | echo "default (the use case to compare to)" 11 | echo 12 | exit 1 13 | fi 14 | 15 | export ORG=${ORG:-jdrouet} 16 | export use_case=$1 17 | 18 | echo "Building runner for use case $use_case" 19 | 20 | docker buildx build --push --tag "$ORG/eco-benchmark:runner-$use_case" ./runner 21 | 22 | exit 0 23 | 24 | -------------------------------------------------------------------------------- /builder/service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if test "$#" -ne 2; then 4 | echo "Invalid number of arguments..." 5 | echo 6 | echo "Usage: ./builder/service.sh " 7 | echo 8 | echo "Available services:" 9 | ls service 10 | echo 11 | echo "Available tags:" 12 | echo "default (the use case to compare to)" 13 | echo 14 | exit 1 15 | fi 16 | 17 | export ORG=${ORG:-jdrouet} 18 | export service=$1 19 | export use_case=$2 20 | 21 | echo "Building service $service for use case $use_case" 22 | 23 | docker buildx build --push --tag "$ORG/eco-benchmark:service-$service-$use_case" ./service/$service 24 | 25 | exit 0 26 | 27 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "usecase": "default" 3 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | rust-actix-sqlx: 3 | image: jdrouet/eco-benchmark:service-rust-actix-sqlx-default 4 | depends_on: 5 | - database 6 | environment: 7 | - DATABASE_URL=postgresql://postgres:mysecretpassword@database:5432/postgres 8 | ports: 9 | - 8080:8080 10 | restart: unless-stopped 11 | 12 | go-pgx: 13 | image: jdrouet/eco-benchmark:service-go-pgx-default 14 | depends_on: 15 | - database 16 | environment: 17 | - DATABASE_URL=postgresql://postgres:mysecretpassword@database:5432/postgres 18 | ports: 19 | - 8080:8080 20 | restart: unless-stopped 21 | 22 | php-symfony: 23 | image: jdrouet/eco-benchmark:service-php-symfony-default 24 | depends_on: 25 | - database 26 | environment: 27 | - DATABASE_URL=postgresql://postgres:mysecretpassword@database:5432/postgres 28 | ports: 29 | - 8081:8080 30 | restart: unless-stopped 31 | 32 | database: 33 | image: postgres:14.2-bullseye 34 | environment: 35 | - POSTGRES_PASSWORD=mysecretpassword 36 | volumes: 37 | - ${PWD}/migrations:/docker-entrypoint-initdb.d:ro 38 | ports: 39 | - 5432:5432 40 | -------------------------------------------------------------------------------- /eco-benchmark.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ] 7 | } -------------------------------------------------------------------------------- /migrations/20220408080000_init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE account 2 | ( 3 | id uuid primary key default gen_random_uuid(), 4 | login text, 5 | creation_date timestamptz default now() 6 | ); 7 | 8 | CREATE INDEX idx_account_creation_date 9 | ON account(creation_date); 10 | 11 | CREATE TABLE list 12 | ( 13 | id uuid primary key default gen_random_uuid(), 14 | account_id uuid references account(id), 15 | name text, 16 | creation_date timestamptz default now() 17 | ); 18 | 19 | 20 | CREATE INDEX idx_list_account_id 21 | ON list(account_id); 22 | 23 | CREATE INDEX idx_list_creation_date 24 | ON list(creation_date); 25 | 26 | CREATE TABLE task 27 | ( 28 | id uuid primary key default gen_random_uuid(), 29 | list_id uuid references list(id), 30 | name text, 31 | description text, 32 | creation_date timestamptz default now() 33 | ); 34 | 35 | CREATE INDEX idx_task_list_id 36 | ON task(list_id); 37 | 38 | CREATE INDEX idx_task_creation_date 39 | ON task(creation_date); -------------------------------------------------------------------------------- /migrations/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:14.2-bullseye 2 | 3 | COPY *.sql /docker-entrypoint-initdb.d/ 4 | 5 | -------------------------------------------------------------------------------- /migrations/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boavizta/ecobenchmark-applicationweb-backend/aeea2451de2e4fb83333d9cc8b7090e27b5ba022/migrations/README.md -------------------------------------------------------------------------------- /runner/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM grafana/k6 2 | 3 | COPY scenario.js /config/scenario.js 4 | -------------------------------------------------------------------------------- /runner/README.md: -------------------------------------------------------------------------------- 1 | # Runner container 2 | 3 | The runner uses [k6](https://k6.io/) to test the service specified in `SERVER_HOST` env var and execute the following load testing scenarios: 4 | 5 | ## scenario.js 6 | 7 | ```mermaid 8 | sequenceDiagram 9 | participant C as HTTP Client 10 | C->>+API: Simulate login 11 | API-->>-C: Logged In 12 | loop 10 times 13 | C->>+API: Create List 14 | API-->>-C: list 15 | loop 20 times 16 | C->>+API: Create Task 17 | API-->>-C: task 18 | end 19 | end 20 | loop for all pages 21 | C->>+API: List Tasks 22 | API-->>-C: tasks[] 23 | end 24 | C->>+API: Get statistics 25 | API-->>-C: all accounts, lists, tasks 26 | ``` -------------------------------------------------------------------------------- /script/hosts: -------------------------------------------------------------------------------- 1 | [application] 2 | app_server ansible_host="{{ server_application_host }}" ansible_user="{{ server_application_user }}" 3 | 4 | [database] 5 | db_server ansible_host="{{ server_database_host }}" ansible_user="{{ server_database_user }}" 6 | 7 | [runner] 8 | run_server ansible_host="{{ server_runner_host }}" ansible_user="{{ server_runner_user }}" 9 | 10 | [all:vars] 11 | ansible_python_interpreter=/usr/bin/python3 12 | 13 | database_host="{{ server_database_host }}" 14 | database_username=postgres 15 | database_password=password 16 | database_name=postgres 17 | 18 | virtual_users=20 19 | duration=600 20 | run_id=overrideme 21 | 22 | [application:vars] 23 | instance_type=application 24 | 25 | [database:vars] 26 | instance_type=database 27 | 28 | [runner:vars] 29 | instance_type=runner 30 | -------------------------------------------------------------------------------- /script/inventory-go-pgx: -------------------------------------------------------------------------------- 1 | [all:vars] 2 | service=go-pgx 3 | 4 | [application:vars] 5 | database_url="postgres://{{ database_username }}:{{ database_password }}@{{ database_host }}:5432/{{ database_name }}" 6 | -------------------------------------------------------------------------------- /script/inventory-jvm-java-quarkus: -------------------------------------------------------------------------------- 1 | [all:vars] 2 | service=jvm-java-quarkus 3 | 4 | [application:vars] 5 | database_url="jdbc:postgresql://{{ server_database_host }}:5432/{{ database_name }}" 6 | -------------------------------------------------------------------------------- /script/inventory-jvm-java-quarkus-reactive: -------------------------------------------------------------------------------- 1 | [all:vars] 2 | service=jvm-java-quarkus-reactive 3 | 4 | [application:vars] 5 | database_url="postgresql://{{ server_database_host }}:5432/{{ database_name }}" 6 | -------------------------------------------------------------------------------- /script/inventory-jvm-kotlin-spring: -------------------------------------------------------------------------------- 1 | [all:vars] 2 | service=jvm-kotlin-spring 3 | 4 | [application:vars] 5 | database_url="jdbc:postgresql://{{ server_database_host }}:5432/{{ database_name }}" 6 | -------------------------------------------------------------------------------- /script/inventory-native-java-quarkus: -------------------------------------------------------------------------------- 1 | [all:vars] 2 | service=native-java-quarkus 3 | 4 | [application:vars] 5 | database_url="jdbc:postgresql://{{ server_database_host }}:5432/{{ database_name }}" 6 | -------------------------------------------------------------------------------- /script/inventory-node-express-sequelize: -------------------------------------------------------------------------------- 1 | [all:vars] 2 | service=node-express-sequelize 3 | 4 | [application:vars] 5 | database_url="postgres://{{ database_username }}:{{ database_password }}@{{ database_host }}:5432/{{ database_name }}" 6 | -------------------------------------------------------------------------------- /script/inventory-php-symfony: -------------------------------------------------------------------------------- 1 | [all:vars] 2 | service=php-symfony 3 | 4 | [application:vars] 5 | database_url="postgres://{{ database_username }}:{{ database_password }}@{{ database_host }}:5432/{{ database_name }}" 6 | -------------------------------------------------------------------------------- /script/inventory-rust-actix-sqlx: -------------------------------------------------------------------------------- 1 | [all:vars] 2 | service=rust-actix-sqlx 3 | 4 | [application:vars] 5 | database_url="postgres://postgres:password@{{ server_database_host }}:5432/postgres" 6 | -------------------------------------------------------------------------------- /script/readme.md: -------------------------------------------------------------------------------- 1 | # Eco benchmark script 2 | 3 | This folder is actually the core of the system. It will orchestrate the database, server and watchers on the remote servers. 4 | Once everything is started, it will run the runner on your local machine. 5 | If you want to run it at home, you'll need to update the `host-*` files in order to make them point to your servers. 6 | Then, you'll need to run the following command from within this folder. 7 | 8 | ```bash 9 | ansible-playbook -u THE_USER_TO_CONNECT_YOUR_SERVERS -i hosts-default site.yml --extra-vars "service=THE_NAME_OF_THE_SERVICE output_directory=WHERE_THE_RESULT_WILL_BE_WRITEN run_id=A_RANDOM_RUN_ID" 10 | ``` 11 | 12 | - `service` is the name of the service you want to test (can be `go-pgx`, `php-symfony`, etc). 13 | - `output_directory` is where the result of the tests will be written at the end. You can put `$PWD` as a value. 14 | - `run_id` is a unique id amongs all the runs. This allows to save several results for the same test. 15 | 16 | To switch use cases, just use a different inventory (in this case `hosts-default`). 17 | 18 | -------------------------------------------------------------------------------- /script/roles/cleanup/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: remove all existing running containers 3 | community.docker.docker_container: 4 | name: "{{ item }}" 5 | state: absent 6 | loop: 7 | - eco-benchmark-scaphandre 8 | - eco-benchmark-database 9 | - eco-benchmark-application 10 | - eco-benchmark-watcher 11 | tags: 12 | - cleanup 13 | 14 | - name: clean existing result files 15 | community.docker.docker_container: 16 | name: eco-benchmark-cleanup 17 | image: debian:buster 18 | detach: no 19 | volumes: 20 | - /tmp:/tmp 21 | command: 22 | - "/bin/rm" 23 | - "-f" 24 | - "/tmp/{{ service }}_{{ use_case }}_{{ run_id }}*" 25 | tags: 26 | - cleanup 27 | 28 | -------------------------------------------------------------------------------- /script/roles/execute/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: start runner 3 | community.docker.docker_container: 4 | name: eco-benchmark-runner 5 | image: "jdrouet/eco-benchmark:runner-{{ use_case }}" 6 | detach: no 7 | user: "1000:1000" 8 | volumes: 9 | - "{{ ansible_env.HOME }}:/tmp" 10 | command: 11 | - "run" 12 | - "/config/scenario.js" 13 | - "--vus" 14 | - "{{ virtual_users | to_json }}" 15 | - "--duration" 16 | - "{{ duration }}s" 17 | - "-e" 18 | - "SERVER_HOST={{ server_application_host }}:8080" 19 | - "--summary-export" 20 | - "/tmp/{{ service }}_{{ use_case }}_{{ run_id }}_k6-summary.json" 21 | # - "--out" 22 | # - "json=/tmp/{{ service }}-{{ use_case }}-{{ run_id }}-k6.json" 23 | -------------------------------------------------------------------------------- /script/roles/install/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install python-pip 3 | apt: 4 | name: python3-pip 5 | tags: 6 | - install 7 | - python 8 | 9 | - name: install docker-py 10 | pip: 11 | name: docker 12 | # executable: pip3 13 | tags: 14 | - install 15 | - docker 16 | -------------------------------------------------------------------------------- /script/run-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | # run 6 | function run_once() { 7 | run_id=$(date +%s) 8 | extra_vars="server_application_host=$SERVER_APPLICATION_HOST" 9 | extra_vars="$extra_vars server_application_user=$SERVER_APPLICATION_USER" 10 | extra_vars="$extra_vars server_database_host=$SERVER_DATABASE_HOST" 11 | extra_vars="$extra_vars server_database_user=$SERVER_DATABASE_USER" 12 | extra_vars="$extra_vars server_runner_host=$SERVER_RUNNER_HOST" 13 | extra_vars="$extra_vars server_runner_user=$SERVER_RUNNER_USER" 14 | ansible-playbook \ 15 | -i script/hosts \ 16 | -i script/inventory-$1 \ 17 | script/site.yml \ 18 | --skip-tags install \ 19 | --extra-vars "$extra_vars service=$1 use_case=$2 output_directory=$PWD/results run_id=$run_id" 20 | } 21 | 22 | function run() { 23 | for i in {1..10}; do 24 | run_once $1 $2 25 | done 26 | } 27 | 28 | run go-pgx no-index 29 | 30 | run jvm-kotlin-spring no-index 31 | 32 | run jvm-java-quarkus no-index 33 | 34 | run jvm-java-quarkus-reactive no-index 35 | 36 | run native-java-quarkus no-index 37 | 38 | run node-express-sequelize no-index 39 | 40 | run php-symfony no-index 41 | 42 | run rust-actix-sqlx no-index 43 | 44 | -------------------------------------------------------------------------------- /script/site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | become: true 4 | roles: 5 | - role: "nickjj.docker" 6 | tags: 7 | - install 8 | - docker 9 | - role: install 10 | 11 | - hosts: all 12 | roles: 13 | - cleanup 14 | - deploy 15 | 16 | - hosts: runner 17 | roles: 18 | - execute 19 | 20 | - hosts: all 21 | roles: 22 | - gathering 23 | -------------------------------------------------------------------------------- /service/go-pgx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17.5-bullseye 2 | 3 | WORKDIR /workdir 4 | ENV GOOS=linux\ 5 | GOARCH=amd64\ 6 | CGO_ENABLED=0 7 | COPY go.mod go.mod 8 | COPY go.sum go.sum 9 | RUN go mod download 10 | COPY . . 11 | RUN go build -o out/app main.go 12 | 13 | FROM scratch 14 | 15 | WORKDIR /workdir 16 | COPY --from=0 /workdir/out/app app 17 | 18 | CMD ["/workdir/app"] 19 | -------------------------------------------------------------------------------- /service/go-pgx/common/JSONDate.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | //src: https://programmer.group/elegant-implementation-of-go-json-time-format.html 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | const timeFormatYMDhms = "2006-01-02T15:04:05Z" // Time format used by json 11 | type JSONDATE struct { 12 | time.Time // Time types used by json 13 | } 14 | 15 | // JSONDATE deserialization 16 | func (self *JSONDATE) UnmarshalJSON(data []byte) (err error) { 17 | newTime, _ := time.ParseInLocation("\""+timeFormatYMDhms+"\"", string(data), time.Local) 18 | *&self.Time = newTime 19 | return 20 | } 21 | 22 | // JSONDATE serialization 23 | func (self JSONDATE) MarshalJSON() ([]byte, error) { 24 | timeStr := fmt.Sprintf("\"%s\"", self.Format(timeFormatYMDhms)) 25 | return []byte(timeStr), nil 26 | } 27 | 28 | //Output string 29 | func (self JSONDATE) String() string { 30 | return self.Time.Format(timeFormatYMDhms) 31 | } 32 | -------------------------------------------------------------------------------- /service/go-pgx/controllers/add_account/controller.go: -------------------------------------------------------------------------------- 1 | package add_account 2 | 3 | import ( 4 | echo "github.com/labstack/echo/v4" 5 | "github.com/pkg/errors" 6 | "go_pgx/common" 7 | "go_pgx/usecases/add_account" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | func Controller(storage add_account.Storage) func(c echo.Context) error { 13 | useCase := add_account.AddAccount{Storage: storage} 14 | 15 | return func(c echo.Context) error { 16 | request := requestBody{} 17 | if err := c.Bind(&request); err != nil { 18 | return c.NoContent(http.StatusBadRequest) 19 | } 20 | 21 | createdAccount, err := useCase.Execute(request.Login) 22 | 23 | if err != nil { 24 | log.Printf("%+v", errors.Wrap(err, "failed to handle the request to add the account")) 25 | return c.String(http.StatusInternalServerError, "failed to add the account") 26 | } 27 | 28 | return c.JSON( 29 | http.StatusCreated, 30 | responseBody{ 31 | Id: createdAccount.Id.String(), 32 | Login: createdAccount.Login, 33 | CreationDate: common.JSONDATE{Time: createdAccount.CreationDate}, 34 | }, 35 | ) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /service/go-pgx/controllers/add_account/input.go: -------------------------------------------------------------------------------- 1 | package add_account 2 | 3 | type requestBody struct { 4 | Login string `json:"login,omitempty"` 5 | } 6 | -------------------------------------------------------------------------------- /service/go-pgx/controllers/add_account/output.go: -------------------------------------------------------------------------------- 1 | package add_account 2 | 3 | import "go_pgx/common" 4 | 5 | type responseBody struct { 6 | Id string `json:"id,omitempty"` 7 | Login string `json:"login,omitempty"` 8 | CreationDate common.JSONDATE `json:"creation_date"` 9 | } 10 | -------------------------------------------------------------------------------- /service/go-pgx/controllers/add_list/input.go: -------------------------------------------------------------------------------- 1 | package add_list 2 | 3 | type requestBody struct { 4 | Name string `json:"name,omitempty"` 5 | } 6 | -------------------------------------------------------------------------------- /service/go-pgx/controllers/add_list/output.go: -------------------------------------------------------------------------------- 1 | package add_list 2 | 3 | import "go_pgx/common" 4 | 5 | type responseBody struct { 6 | Id string `json:"id,omitempty"` 7 | Name string `json:"name,omitempty"` 8 | CreationDate common.JSONDATE `json:"creation_date"` 9 | AccountId string `json:"account_id,omitempty"` 10 | } 11 | -------------------------------------------------------------------------------- /service/go-pgx/controllers/add_task_to_list/input.go: -------------------------------------------------------------------------------- 1 | package add_task_to_list 2 | 3 | type requestBody struct { 4 | Name string `json:"name,omitempty"` 5 | Description string `json:"description,omitempty"` 6 | } 7 | -------------------------------------------------------------------------------- /service/go-pgx/controllers/add_task_to_list/output.go: -------------------------------------------------------------------------------- 1 | package add_task_to_list 2 | 3 | import "go_pgx/common" 4 | 5 | type responseBody struct { 6 | Id string `json:"id,omitempty"` 7 | Name string `json:"name,omitempty"` 8 | Description string `json:"description,omitempty"` 9 | CreationDate common.JSONDATE `json:"creation_date"` 10 | ListId string `json:"list_id,omitempty"` 11 | } 12 | -------------------------------------------------------------------------------- /service/go-pgx/controllers/get_lists/output.go: -------------------------------------------------------------------------------- 1 | package get_lists 2 | 3 | import "go_pgx/common" 4 | 5 | type responseBody struct { 6 | Id string `json:"id,omitempty"` 7 | Name string `json:"name,omitempty"` 8 | Tasks []TaskBody `json:"tasks,omitempty"` 9 | CreationDate common.JSONDATE `json:"creation_date"` 10 | AccountId string `json:"account_id,omitempty"` 11 | } 12 | 13 | type TaskBody struct { 14 | Id string `json:"id,omitempty"` 15 | Name string `json:"name,omitempty"` 16 | Description string `json:"description,omitempty"` 17 | CreationDate common.JSONDATE `json:"creation_date"` 18 | } 19 | -------------------------------------------------------------------------------- /service/go-pgx/controllers/get_stats/controller.go: -------------------------------------------------------------------------------- 1 | package get_stats 2 | 3 | import ( 4 | echo "github.com/labstack/echo/v4" 5 | "github.com/pkg/errors" 6 | "go_pgx/usecases/get_stats" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | func Controller(storage get_stats.Storage) func(c echo.Context) error { 12 | useCase := get_stats.GetStatsByAccounts{Storage: storage} 13 | 14 | return func(c echo.Context) error { 15 | 16 | stats, err := useCase.Execute() 17 | 18 | if err != nil { 19 | log.Printf("%+v", errors.Wrap(err, "failed to handle the request to get the stats")) 20 | return c.String(http.StatusInternalServerError, "failed to add the stats") 21 | } 22 | 23 | return c.JSON( 24 | http.StatusCreated, 25 | statsToResponse(stats), 26 | ) 27 | } 28 | 29 | } 30 | 31 | func statsToResponse(stats []get_stats.GetStatsByAccountsResponse) []responseBody { 32 | response := make([]responseBody, len(stats)) 33 | for i, s := range stats { 34 | response[i] = responseBody{ 35 | AccountId: s.AccountId.String(), 36 | AccountLogin: s.AccountLogin, 37 | ListCount: s.ListCount, 38 | TaskAvg: s.TaskAvg, 39 | } 40 | } 41 | return response 42 | } 43 | -------------------------------------------------------------------------------- /service/go-pgx/controllers/get_stats/output.go: -------------------------------------------------------------------------------- 1 | package get_stats 2 | 3 | type responseBody struct { 4 | AccountId string `json:"account_id,omitempty"` 5 | AccountLogin string `json:"account_login,omitempty"` 6 | ListCount int64 `json:"list_count,omitempty"` 7 | TaskAvg float64 `json:"task_avg"` 8 | } 9 | -------------------------------------------------------------------------------- /service/go-pgx/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | runner: 3 | image: jdrouet/eco-benchmark:runner-default 4 | build: ../../runner 5 | command: 6 | - run 7 | - "--vus" 8 | - "2" 9 | - "--duration" 10 | - "1m" 11 | - "--env" 12 | - "SERVER_HOST=service:8080" 13 | - "/config/scenario.js" 14 | depends_on: 15 | - service 16 | 17 | service: 18 | image: jdrouet/eco-benchmark:service-go-pgx-default 19 | build: . 20 | depends_on: 21 | - database 22 | environment: 23 | - DATABASE_URL=postgres://postgres:mysecretpassword@database:5432/postgres 24 | - DATABASE_POOL_MAX=20 25 | restart: unless-stopped 26 | 27 | database: 28 | image: jdrouet/eco-benchmark:database-default 29 | build: ../../migrations 30 | environment: 31 | - POSTGRES_PASSWORD=mysecretpassword 32 | healthcheck: 33 | test: "/usr/bin/psql -U postgres postgres -c \"\\d\"" 34 | interval: 3s 35 | timeout: 1s 36 | retries: 20 37 | restart: unless-stopped 38 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/add_account/add_account.go: -------------------------------------------------------------------------------- 1 | package add_account 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "github.com/pkg/errors" 6 | "time" 7 | ) 8 | 9 | type AddAccount struct { 10 | Storage Storage 11 | } 12 | 13 | func (c *AddAccount) Execute(requestLogin string) (*AddAccountResponse, error) { 14 | id, err := uuid.NewV4() 15 | if err != nil { 16 | return nil, errors.Wrap(err, "failed to generate uuid") 17 | } 18 | 19 | accountResponse := &AddAccountResponse{ 20 | Id: id, 21 | Login: requestLogin, 22 | CreationDate: time.Now().UTC(), 23 | } 24 | 25 | err = c.Storage.AddAccount(accountResponse) 26 | if err != nil { 27 | return nil, errors.Wrapf(err, "failed to create the account %s", requestLogin) 28 | } 29 | 30 | return accountResponse, nil 31 | } 32 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/add_account/interfaces.go: -------------------------------------------------------------------------------- 1 | package add_account 2 | 3 | type Storage interface { 4 | AddAccount(account *AddAccountResponse) error 5 | } 6 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/add_account/output.go: -------------------------------------------------------------------------------- 1 | package add_account 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "time" 6 | ) 7 | 8 | type AddAccountResponse struct { 9 | Id uuid.UUID 10 | Login string 11 | CreationDate time.Time 12 | } 13 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/add_list/add_list.go: -------------------------------------------------------------------------------- 1 | package add_list 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "github.com/pkg/errors" 6 | "time" 7 | ) 8 | 9 | type AddList struct { 10 | Storage Storage 11 | } 12 | 13 | func (c *AddList) Execute(request AddListRequest) (*AddListResponse, error) { 14 | id, err := uuid.NewV4() 15 | if err != nil { 16 | return nil, errors.Wrap(err, "failed to generate uuid") 17 | } 18 | 19 | listResponse := &AddListResponse{ 20 | Id: id, 21 | Name: request.Name, 22 | AccountId: request.AccountId, 23 | CreationDate: time.Now().UTC(), 24 | } 25 | 26 | err = c.Storage.AddList(listResponse) 27 | if err != nil { 28 | return nil, errors.Wrapf(err, "failed to create the list %s", request.Name) 29 | } 30 | 31 | return listResponse, nil 32 | } 33 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/add_list/input.go: -------------------------------------------------------------------------------- 1 | package add_list 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | ) 6 | 7 | type AddListRequest struct { 8 | Name string 9 | AccountId uuid.UUID 10 | } 11 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/add_list/interfaces.go: -------------------------------------------------------------------------------- 1 | package add_list 2 | 3 | type Storage interface { 4 | AddList(list *AddListResponse) error 5 | } 6 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/add_list/output.go: -------------------------------------------------------------------------------- 1 | package add_list 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "time" 6 | ) 7 | 8 | type AddListResponse struct { 9 | Id uuid.UUID 10 | Name string 11 | CreationDate time.Time 12 | AccountId uuid.UUID 13 | } 14 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/add_task_to_list/add_task.go: -------------------------------------------------------------------------------- 1 | package add_task_to_list 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "github.com/pkg/errors" 6 | "time" 7 | ) 8 | 9 | type AddTask struct { 10 | Storage Storage 11 | } 12 | 13 | func (c *AddTask) Execute(request AddTaskRequest) (*AddTaskResponse, error) { 14 | id, err := uuid.NewV4() 15 | if err != nil { 16 | return nil, errors.Wrap(err, "failed to generate uuid") 17 | } 18 | 19 | taskResponse := &AddTaskResponse{ 20 | Id: id, 21 | Name: request.Name, 22 | Description: request.Description, 23 | ListId: request.ListId, 24 | CreationDate: time.Now().UTC(), 25 | } 26 | 27 | err = c.Storage.AddTask(taskResponse) 28 | if err != nil { 29 | return nil, errors.Wrapf(err, "failed to create the task %s", request.Name) 30 | } 31 | 32 | return taskResponse, nil 33 | } 34 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/add_task_to_list/input.go: -------------------------------------------------------------------------------- 1 | package add_task_to_list 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | ) 6 | 7 | type AddTaskRequest struct { 8 | Name string 9 | Description string 10 | ListId uuid.UUID 11 | } 12 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/add_task_to_list/interfaces.go: -------------------------------------------------------------------------------- 1 | package add_task_to_list 2 | 3 | type Storage interface { 4 | AddTask(task *AddTaskResponse) error 5 | } 6 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/add_task_to_list/output.go: -------------------------------------------------------------------------------- 1 | package add_task_to_list 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "time" 6 | ) 7 | 8 | type AddTaskResponse struct { 9 | Id uuid.UUID 10 | Name string 11 | Description string 12 | CreationDate time.Time 13 | ListId uuid.UUID 14 | } 15 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/get_lists/get_lists.go: -------------------------------------------------------------------------------- 1 | package get_lists 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | ) 6 | 7 | type GetListsByAccountId struct { 8 | Storage Storage 9 | } 10 | 11 | func (c *GetListsByAccountId) Execute(request GetListsRequest) ([]GetListsResponse, error) { 12 | 13 | response, err := c.Storage.GetListsByAccountId(request) 14 | 15 | if err != nil { 16 | return nil, errors.Wrapf(err, "failed to get the lists of account id %s", request.AccountId.String()) 17 | } 18 | 19 | return response, nil 20 | } 21 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/get_lists/input.go: -------------------------------------------------------------------------------- 1 | package get_lists 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | ) 6 | 7 | type GetListsRequest struct { 8 | Page int64 9 | AccountId uuid.UUID 10 | } 11 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/get_lists/interfaces.go: -------------------------------------------------------------------------------- 1 | package get_lists 2 | 3 | type Storage interface { 4 | GetListsByAccountId(request GetListsRequest) ([]GetListsResponse, error) 5 | } 6 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/get_lists/output.go: -------------------------------------------------------------------------------- 1 | package get_lists 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "time" 6 | ) 7 | 8 | type GetListsResponse struct { 9 | Id uuid.UUID 10 | Name string 11 | CreationDate time.Time 12 | AccountId uuid.UUID 13 | Tasks []TasksResponse 14 | } 15 | 16 | type TasksResponse struct { 17 | Id uuid.UUID 18 | Name string 19 | Description string 20 | CreationDate time.Time 21 | } 22 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/get_stats/get_stats.go: -------------------------------------------------------------------------------- 1 | package get_stats 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | ) 6 | 7 | type GetStatsByAccounts struct { 8 | Storage Storage 9 | } 10 | 11 | func (c *GetStatsByAccounts) Execute() ([]GetStatsByAccountsResponse, error) { 12 | stats, err := c.Storage.GetStatsByAccounts() 13 | if err != nil { 14 | return nil, errors.Wrapf(err, "failed to create the stats") 15 | } 16 | return stats, nil 17 | } 18 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/get_stats/interfaces.go: -------------------------------------------------------------------------------- 1 | package get_stats 2 | 3 | type Storage interface { 4 | GetStatsByAccounts() ([]GetStatsByAccountsResponse, error) 5 | } 6 | -------------------------------------------------------------------------------- /service/go-pgx/usecases/get_stats/output.go: -------------------------------------------------------------------------------- 1 | package get_stats 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | ) 6 | 7 | type GetStatsByAccountsResponse struct { 8 | AccountId uuid.UUID 9 | AccountLogin string 10 | ListCount int64 11 | TaskAvg float64 12 | } 13 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/.dockerignore: -------------------------------------------------------------------------------- 1 | !target/*-runner 2 | !target/*-runner.jar 3 | !target/lib/* 4 | !target/quarkus-app/* -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/.gitignore: -------------------------------------------------------------------------------- 1 | #Maven 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | release.properties 7 | .flattened-pom.xml 8 | 9 | # Eclipse 10 | .project 11 | .classpath 12 | .settings/ 13 | bin/ 14 | 15 | # IntelliJ 16 | .idea 17 | *.ipr 18 | *.iml 19 | *.iws 20 | 21 | # NetBeans 22 | nb-configuration.xml 23 | 24 | # Visual Studio Code 25 | .vscode 26 | .factorypath 27 | 28 | # OSX 29 | .DS_Store 30 | 31 | # Vim 32 | *.swp 33 | *.swo 34 | 35 | # patch 36 | *.orig 37 | *.rej 38 | 39 | # Local environment 40 | .env 41 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/.mvn/wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | maven-wrapper.jar 2 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/main/docker/Dockerfile.native-micro: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # It uses a micro base image, tuned for Quarkus native executables. 4 | # It reduces the size of the resulting container image. 5 | # Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. 6 | # 7 | # Before building the container image run: 8 | # 9 | # ./mvnw package -Pnative 10 | # 11 | # Then, build the image with: 12 | # 13 | # docker build -f src/main/docker/Dockerfile.native-micro -t mbras/native-java-quarkus-reactive-micro . 14 | # 15 | # Then run the container using: 16 | # 17 | # docker run -i --rm -p 8080:8080 mbras/native-java-quarkus-reactive-micro 18 | # 19 | ### 20 | FROM quay.io/quarkus/quarkus-micro-image:1.0 21 | WORKDIR /work/ 22 | RUN chown 1001 /work \ 23 | && chmod "g+rwX" /work \ 24 | && chown 1001:root /work 25 | COPY --chown=1001:root target/*-runner /work/application 26 | 27 | EXPOSE 8080 28 | USER 1001 29 | 30 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/main/java/com/ecobenchmark/GreetingResource.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import javax.ws.rs.GET; 4 | import javax.ws.rs.Path; 5 | import javax.ws.rs.Produces; 6 | import javax.ws.rs.core.MediaType; 7 | 8 | @Path("/hello") 9 | public class GreetingResource { 10 | 11 | @GET 12 | @Produces(MediaType.TEXT_PLAIN) 13 | public String hello() { 14 | return "Hello from RESTEasy Reactive"; 15 | } 16 | } -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/main/java/com/ecobenchmark/controllers/Healthcheck.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers; 2 | 3 | import io.smallrye.mutiny.Uni; 4 | 5 | import javax.ws.rs.GET; 6 | import javax.ws.rs.Path; 7 | import javax.ws.rs.core.Response; 8 | 9 | @Path("/healthcheck") 10 | public class Healthcheck { 11 | 12 | @GET 13 | public Uni healthcheck() { 14 | return Uni.createFrom().item(Response.noContent().build()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/main/java/com/ecobenchmark/controllers/addaccount/AccountRequest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers.addaccount; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import io.quarkus.runtime.annotations.RegisterForReflection; 5 | 6 | import java.time.Instant; 7 | import java.util.UUID; 8 | 9 | @RegisterForReflection 10 | public class AccountRequest { 11 | 12 | private UUID id; 13 | 14 | private String login; 15 | 16 | @JsonProperty("creation_date") 17 | private Instant creationDate; 18 | 19 | public UUID getId() { 20 | return id; 21 | } 22 | 23 | public void setId(UUID id) { 24 | this.id = id; 25 | } 26 | 27 | public String getLogin() { 28 | return login; 29 | } 30 | 31 | public void setLogin(String login) { 32 | this.login = login; 33 | } 34 | 35 | public Instant getCreationDate() { 36 | return creationDate; 37 | } 38 | 39 | public void setCreationDate(Instant creationDate) { 40 | this.creationDate = creationDate; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/main/java/com/ecobenchmark/controllers/addlist/ListRequest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers.addlist; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | @RegisterForReflection 6 | public class ListRequest { 7 | 8 | private String name; 9 | 10 | public String getName() { 11 | return name; 12 | } 13 | 14 | public void setName(String name) { 15 | this.name = name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/main/java/com/ecobenchmark/controllers/addtask/TaskRequest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers.addtask; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | @RegisterForReflection 6 | public class TaskRequest { 7 | 8 | private String name; 9 | 10 | private String description; 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public void setName(String name) { 17 | this.name = name; 18 | } 19 | 20 | public String getDescription() { 21 | return description; 22 | } 23 | 24 | public void setDescription(String description) { 25 | this.description = description; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/main/java/com/ecobenchmark/repositories/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.repositories; 2 | 3 | import com.ecobenchmark.entities.Account; 4 | import io.quarkus.hibernate.reactive.panache.PanacheRepository; 5 | import io.smallrye.mutiny.Uni; 6 | 7 | import javax.enterprise.context.ApplicationScoped; 8 | import java.util.UUID; 9 | 10 | @ApplicationScoped 11 | public class AccountRepository implements PanacheRepository { 12 | 13 | public Uni findById(UUID accountId) { 14 | return find("id", accountId).firstResult(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/main/java/com/ecobenchmark/repositories/ListEntityRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.repositories; 2 | 3 | import com.ecobenchmark.entities.ListEntity; 4 | import io.quarkus.hibernate.reactive.panache.PanacheRepository; 5 | import io.smallrye.mutiny.Uni; 6 | 7 | import javax.enterprise.context.ApplicationScoped; 8 | import java.util.UUID; 9 | 10 | @ApplicationScoped 11 | public class ListEntityRepository implements PanacheRepository { 12 | public Uni findById(UUID listId) { 13 | return find("id", listId).firstResult(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/main/java/com/ecobenchmark/repositories/TaskRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.repositories; 2 | 3 | import com.ecobenchmark.entities.Task; 4 | import io.quarkus.hibernate.reactive.panache.PanacheRepository; 5 | 6 | import javax.enterprise.context.ApplicationScoped; 7 | 8 | @ApplicationScoped 9 | public class TaskRepository implements PanacheRepository { 10 | } 11 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.datasource.db-kind=postgresql 2 | #quarkus.datasource.username=postgres 3 | #quarkus.datasource.password=mysecretpassword 4 | #quarkus.datasource.reactive.url=postgresql://127.0.0.1:5432/postgres 5 | #quarkus.datasource.reactive.max-size=20 -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/test/java/com/ecobenchmark/FullScenarioTest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import com.ecobenchmark.controllers.addaccount.AccountRequest; 4 | import io.quarkus.test.junit.QuarkusTest; 5 | import io.restassured.http.ContentType; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static io.restassured.RestAssured.given; 9 | 10 | @QuarkusTest 11 | public class FullScenarioTest { 12 | 13 | @Test 14 | public void testScenarioTodo() { 15 | AccountRequest accountRequest= new AccountRequest(); 16 | accountRequest.setLogin("user1"); 17 | given() 18 | .when() 19 | .body(accountRequest) 20 | .contentType(ContentType.JSON) 21 | .post("/api/accounts") 22 | .then() 23 | .statusCode(200); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/test/java/com/ecobenchmark/GreetingResourceIT.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import io.quarkus.test.junit.QuarkusIntegrationTest; 4 | 5 | @QuarkusIntegrationTest 6 | public class GreetingResourceIT extends GreetingResourceTest { 7 | // Execute the same tests but in packaged mode. 8 | } 9 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus-reactive/src/test/java/com/ecobenchmark/GreetingResourceTest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import io.quarkus.test.junit.QuarkusTest; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static io.restassured.RestAssured.given; 7 | import static org.hamcrest.CoreMatchers.is; 8 | 9 | @QuarkusTest 10 | public class GreetingResourceTest { 11 | 12 | @Test 13 | public void testHelloEndpoint() { 14 | given() 15 | .when().get("/hello") 16 | .then() 17 | .statusCode(200) 18 | .body(is("Hello from RESTEasy Reactive")); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /service/jvm-java-quarkus/.dockerignore: -------------------------------------------------------------------------------- 1 | !target/*-runner 2 | !target/*-runner.jar 3 | !target/lib/* 4 | !target/quarkus-app/* -------------------------------------------------------------------------------- /service/jvm-java-quarkus/.gitignore: -------------------------------------------------------------------------------- 1 | #Maven 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | release.properties 7 | .flattened-pom.xml 8 | 9 | # Eclipse 10 | .project 11 | .classpath 12 | .settings/ 13 | bin/ 14 | 15 | # IntelliJ 16 | .idea 17 | *.ipr 18 | *.iml 19 | *.iws 20 | 21 | # NetBeans 22 | nb-configuration.xml 23 | 24 | # Visual Studio Code 25 | .vscode 26 | .factorypath 27 | 28 | # OSX 29 | .DS_Store 30 | 31 | # Vim 32 | *.swp 33 | *.swo 34 | 35 | # patch 36 | *.orig 37 | *.rej 38 | 39 | # Local environment 40 | .env 41 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/.mvn/wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | maven-wrapper.jar 2 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/main/java/com/ecobenchmark/GreetingResource.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import javax.ws.rs.GET; 4 | import javax.ws.rs.Path; 5 | import javax.ws.rs.Produces; 6 | import javax.ws.rs.core.MediaType; 7 | 8 | @Path("/hello") 9 | public class GreetingResource { 10 | 11 | @GET 12 | @Produces(MediaType.TEXT_PLAIN) 13 | public String hello() { 14 | return "Hello from RESTEasy Reactive"; 15 | } 16 | } -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/main/java/com/ecobenchmark/controllers/Healthcheck.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers; 2 | 3 | import javax.ws.rs.GET; 4 | import javax.ws.rs.Path; 5 | import javax.ws.rs.core.Response; 6 | 7 | @Path("/healthcheck") 8 | public class Healthcheck { 9 | 10 | @GET 11 | public Response healthcheck() { 12 | return Response.noContent().build(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/main/java/com/ecobenchmark/controllers/addaccount/AccountRequest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers.addaccount; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import io.quarkus.runtime.annotations.RegisterForReflection; 5 | 6 | import java.time.Instant; 7 | import java.util.UUID; 8 | 9 | @RegisterForReflection 10 | public class AccountRequest { 11 | 12 | private UUID id; 13 | 14 | private String login; 15 | 16 | @JsonProperty("creation_date") 17 | private Instant creationDate; 18 | 19 | public UUID getId() { 20 | return id; 21 | } 22 | 23 | public void setId(UUID id) { 24 | this.id = id; 25 | } 26 | 27 | public String getLogin() { 28 | return login; 29 | } 30 | 31 | public void setLogin(String login) { 32 | this.login = login; 33 | } 34 | 35 | public Instant getCreationDate() { 36 | return creationDate; 37 | } 38 | 39 | public void setCreationDate(Instant creationDate) { 40 | this.creationDate = creationDate; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/main/java/com/ecobenchmark/controllers/addlist/ListRequest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers.addlist; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | @RegisterForReflection 6 | public class ListRequest { 7 | 8 | private String name; 9 | 10 | public String getName() { 11 | return name; 12 | } 13 | 14 | public void setName(String name) { 15 | this.name = name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/main/java/com/ecobenchmark/controllers/addtask/TaskRequest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers.addtask; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | @RegisterForReflection 6 | public class TaskRequest { 7 | 8 | private String name; 9 | 10 | private String description; 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public void setName(String name) { 17 | this.name = name; 18 | } 19 | 20 | public String getDescription() { 21 | return description; 22 | } 23 | 24 | public void setDescription(String description) { 25 | this.description = description; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/main/java/com/ecobenchmark/repositories/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.repositories; 2 | 3 | import com.ecobenchmark.entities.Account; 4 | import io.quarkus.hibernate.orm.panache.PanacheRepository; 5 | 6 | import javax.enterprise.context.ApplicationScoped; 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | @ApplicationScoped 11 | public class AccountRepository implements PanacheRepository { 12 | 13 | public Optional findByIdOptional(UUID accountId) { 14 | return find("id", accountId).firstResultOptional(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/main/java/com/ecobenchmark/repositories/ListEntityRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.repositories; 2 | 3 | import com.ecobenchmark.entities.ListEntity; 4 | import io.quarkus.hibernate.orm.panache.PanacheRepository; 5 | 6 | import javax.enterprise.context.ApplicationScoped; 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | @ApplicationScoped 11 | public class ListEntityRepository implements PanacheRepository { 12 | public Optional findByIdOptional(UUID listId) { 13 | return find("id", listId).firstResultOptional(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/main/java/com/ecobenchmark/repositories/TaskRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.repositories; 2 | 3 | import com.ecobenchmark.entities.Task; 4 | import io.quarkus.hibernate.orm.panache.PanacheRepository; 5 | 6 | import javax.enterprise.context.ApplicationScoped; 7 | 8 | @ApplicationScoped 9 | public class TaskRepository implements PanacheRepository { 10 | } 11 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boavizta/ecobenchmark-applicationweb-backend/aeea2451de2e4fb83333d9cc8b7090e27b5ba022/service/jvm-java-quarkus/src/main/resources/application.properties -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/test/java/com/ecobenchmark/FullScenarioTest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import com.ecobenchmark.controllers.addaccount.AccountRequest; 4 | import io.quarkus.test.junit.QuarkusTest; 5 | import io.restassured.http.ContentType; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static io.restassured.RestAssured.given; 9 | 10 | @QuarkusTest 11 | public class FullScenarioTest { 12 | 13 | @Test 14 | public void testScenarioTodo() { 15 | AccountRequest accountRequest= new AccountRequest(); 16 | accountRequest.setLogin("user1"); 17 | given() 18 | .when() 19 | .body(accountRequest) 20 | .contentType(ContentType.JSON) 21 | .post("/api/accounts") 22 | .then() 23 | .statusCode(200); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/test/java/com/ecobenchmark/GreetingResourceIT.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import io.quarkus.test.junit.QuarkusIntegrationTest; 4 | 5 | @QuarkusIntegrationTest 6 | public class GreetingResourceIT extends GreetingResourceTest { 7 | // Execute the same tests but in packaged mode. 8 | } 9 | -------------------------------------------------------------------------------- /service/jvm-java-quarkus/src/test/java/com/ecobenchmark/GreetingResourceTest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import io.quarkus.test.junit.QuarkusTest; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static io.restassured.RestAssured.given; 7 | import static org.hamcrest.CoreMatchers.is; 8 | 9 | @QuarkusTest 10 | public class GreetingResourceTest { 11 | 12 | @Test 13 | public void testHelloEndpoint() { 14 | given() 15 | .when().get("/hello") 16 | .then() 17 | .statusCode(200) 18 | .body(is("Hello from RESTEasy Reactive")); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:17.0.3_7-jdk-jammy 2 | 3 | WORKDIR /workdir 4 | COPY . . 5 | RUN ./gradlew bootjar --no-daemon --info 6 | 7 | FROM eclipse-temurin:17.0.3_7-jre-jammy 8 | 9 | WORKDIR /workdir 10 | COPY --from=0 /workdir/build/libs/kotlin-spring-jpa-0.0.1-SNAPSHOT.jar /workdir/app.jar 11 | CMD ["java", "-jar", "/workdir/app.jar"] 12 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/README.md: -------------------------------------------------------------------------------- 1 | # How to run 2 | 3 | ## Build the image 4 | 5 | ```bash 6 | docker build -t benchmark-kotlin-spring-jpa . 7 | ``` 8 | 9 | ## Run the container 10 | 11 | ```bash 12 | docker run --name=benchmark-kotlin-spring-jpa -d -p 8080:8080 \ 13 | -e SPRING_DATASOURCE_URL='jdbc:postgresql://172.17.0.1:5432/postgres' \ 14 | -e SPRING_DATASOURCE_USERNAME='postgres' \ 15 | -e SPRING_DATASOURCE_PASSWORD='mysecretpassword' \ 16 | benchmark-kotlin-spring-jpa 17 | ``` 18 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | runner: 3 | image: jdrouet/eco-benchmark:runner-default 4 | build: ../../runner 5 | command: 6 | - run 7 | - "--vus" 8 | - "2" 9 | - "--duration" 10 | - "1m" 11 | - "--env" 12 | - "SERVER_HOST=service:8080" 13 | - "/config/scenario.js" 14 | depends_on: 15 | - service 16 | 17 | service: 18 | image: jdrouet/eco-benchmark:service-jvm-kotlin-spring-default 19 | build: . 20 | depends_on: 21 | - database 22 | environment: 23 | - SPRING_DATASOURCE_URL=jdbc:postgresql://database:5432/postgres 24 | - SPRING_DATASOURCE_USERNAME=postgres 25 | - SPRING_DATASOURCE_PASSWORD=mysecretpassword 26 | restart: unless-stopped 27 | 28 | database: 29 | image: jdrouet/eco-benchmark:database-default 30 | build: ../../migrations 31 | environment: 32 | - POSTGRES_PASSWORD=mysecretpassword 33 | healthcheck: 34 | test: "/usr/bin/psql -U postgres postgres -c \"\\d\"" 35 | interval: 3s 36 | timeout: 1s 37 | retries: 20 38 | restart: unless-stopped 39 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boavizta/ecobenchmark-applicationweb-backend/aeea2451de2e4fb83333d9cc8b7090e27b5ba022/service/jvm-kotlin-spring/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "kotlin-spring-jpa" 2 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/KotlinSpringJpaApplication.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | import org.springframework.boot.runApplication 5 | 6 | @SpringBootApplication 7 | class KotlinSpringJpaApplication 8 | 9 | fun main(args: Array) { 10 | runApplication(*args) 11 | } 12 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/controllers/Healthcheck.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.controllers 2 | 3 | import org.springframework.http.ResponseEntity 4 | import org.springframework.web.bind.annotation.GetMapping 5 | import org.springframework.web.bind.annotation.RestController 6 | 7 | @RestController 8 | class Healthcheck { 9 | @GetMapping("/healthcheck") 10 | fun healthcheck(): ResponseEntity { 11 | return ResponseEntity.noContent().build() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/controllers/addaccount/AccountRequest.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.controllers.addaccount 2 | 3 | data class AccountRequest(val login: String) 4 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/controllers/addaccount/AccountResponse.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.controllers.addaccount 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import java.time.Instant 5 | import java.util.* 6 | 7 | data class AccountResponse( 8 | val id: UUID, 9 | val login: String, 10 | @JsonProperty("creation_date") 11 | val creationDate: Instant 12 | ) 13 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/controllers/addaccount/AddAccount.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.controllers.addaccount 2 | 3 | import com.ecobenchmark.kotlinspringjpa.entities.Account 4 | import com.ecobenchmark.kotlinspringjpa.repositories.AccountRepository 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.web.bind.annotation.PostMapping 7 | import org.springframework.web.bind.annotation.RequestBody 8 | import org.springframework.web.bind.annotation.RestController 9 | import java.time.Instant 10 | 11 | @RestController 12 | class AddAccount { 13 | @Autowired 14 | lateinit var repository: AccountRepository 15 | 16 | @PostMapping("/api/accounts") 17 | fun addAccount(@RequestBody accountRequest: AccountRequest): AccountResponse { 18 | val account = Account(accountRequest.login, Instant.now(), emptyList()) 19 | repository.save(account) 20 | return AccountResponse(account.id!!, account.login, account.creationDate) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/controllers/addlist/ListRequest.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.controllers.addlist 2 | 3 | data class ListRequest(val name: String) 4 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/controllers/addlist/ListResponse.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.controllers.addlist 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import java.time.Instant 5 | import java.util.* 6 | 7 | data class ListResponse( 8 | val id: UUID, 9 | val name: String, 10 | @JsonProperty("creation_date") 11 | val creationDate: Instant 12 | ) -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/controllers/addtask/TaskRequest.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.controllers.addtask 2 | 3 | data class TaskRequest(val name: String, val description: String) 4 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/controllers/addtask/TaskResponse.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.controllers.addtask 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import java.time.Instant 5 | import java.util.UUID 6 | 7 | data class TaskResponse( 8 | val id: UUID, 9 | @JsonProperty("list_id") 10 | val listId: UUID, 11 | val name: String, 12 | val description: String, 13 | @JsonProperty("creation_date") 14 | val creationDate: Instant 15 | ) -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/controllers/getlists/ListResponse.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.controllers.getlists 2 | 3 | import java.time.Instant 4 | import java.util.UUID 5 | 6 | data class ListResponse( 7 | val id: UUID, 8 | val name: String, 9 | val tasks: MutableList, 10 | val creationDate: Instant, 11 | val accountId: UUID 12 | ) 13 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/controllers/getlists/TaskResponse.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.controllers.getlists 2 | 3 | import java.time.Instant 4 | import java.util.* 5 | 6 | data class TaskResponse( 7 | val id: UUID, 8 | val name: String, 9 | val description: String, 10 | val creationDate: Instant 11 | ) 12 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/controllers/getstats/StatsResponse.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.controllers.getstats 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import java.util.* 5 | 6 | data class StatsResponse( 7 | @JsonProperty("account_id") 8 | val accountId: UUID, 9 | @JsonProperty("account_login") 10 | val accountLogin: String, 11 | @JsonProperty("list_count") 12 | val listCount: Int, 13 | @JsonProperty("task_avg") 14 | val taskAvg: Float 15 | ) 16 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/entities/Account.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.entities 2 | 3 | import org.hibernate.annotations.GenericGenerator 4 | import java.time.Instant 5 | import java.util.* 6 | import javax.persistence.Entity 7 | import javax.persistence.GeneratedValue 8 | import javax.persistence.Id 9 | import javax.persistence.OneToMany 10 | 11 | @Entity 12 | data class Account( 13 | var login: String, 14 | var creationDate: Instant, 15 | @OneToMany(mappedBy = "account") 16 | var lists: List 17 | ) { 18 | @Id 19 | @GeneratedValue(generator = "UUID") 20 | @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") 21 | var id: UUID? = null 22 | } 23 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/entities/ListEntity.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.entities 2 | 3 | import org.hibernate.annotations.GenericGenerator 4 | import java.time.Instant 5 | import java.util.* 6 | import javax.persistence.* 7 | 8 | @Entity(name = "list") 9 | class ListEntity( 10 | @ManyToOne 11 | var account: Account, 12 | var name: String, 13 | var creationDate: Instant, 14 | @OneToMany(mappedBy = "list") 15 | var tasks: List 16 | ) { 17 | @Id 18 | @GeneratedValue(generator = "UUID") 19 | @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") 20 | var id: UUID? = null 21 | } 22 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/entities/Task.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.entities 2 | 3 | import org.hibernate.annotations.GenericGenerator 4 | import java.time.Instant 5 | import java.util.* 6 | import javax.persistence.Entity 7 | import javax.persistence.GeneratedValue 8 | import javax.persistence.Id 9 | import javax.persistence.ManyToOne 10 | 11 | @Entity 12 | class Task( 13 | @ManyToOne 14 | var list: ListEntity, 15 | var name: String, 16 | var description: String, 17 | var creationDate: Instant, 18 | ) { 19 | @Id 20 | @GeneratedValue(generator = "UUID") 21 | @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") 22 | var id: UUID? = null 23 | } 24 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/repositories/AccountRepository.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.repositories 2 | 3 | import com.ecobenchmark.kotlinspringjpa.entities.Account 4 | import org.springframework.data.repository.CrudRepository 5 | import java.util.UUID 6 | 7 | interface AccountRepository: CrudRepository { 8 | } 9 | -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/repositories/ListRepository.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.repositories 2 | 3 | import com.ecobenchmark.kotlinspringjpa.entities.ListEntity 4 | import org.springframework.data.repository.CrudRepository 5 | import java.util.UUID 6 | 7 | interface ListRepository : CrudRepository { 8 | } -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/kotlin/com/ecobenchmark/kotlinspringjpa/repositories/TaskRepository.kt: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.kotlinspringjpa.repositories 2 | 3 | import com.ecobenchmark.kotlinspringjpa.entities.Task 4 | import org.springframework.data.repository.CrudRepository 5 | import java.util.UUID 6 | 7 | interface TaskRepository : CrudRepository { 8 | } -------------------------------------------------------------------------------- /service/jvm-kotlin-spring/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.hibernate.ddl-auto=validate 2 | -------------------------------------------------------------------------------- /service/native-java-quarkus/.dockerignore: -------------------------------------------------------------------------------- 1 | !target/*-runner 2 | !target/*-runner.jar 3 | !target/lib/* 4 | !target/quarkus-app/* -------------------------------------------------------------------------------- /service/native-java-quarkus/.gitignore: -------------------------------------------------------------------------------- 1 | #Maven 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | release.properties 7 | .flattened-pom.xml 8 | 9 | # Eclipse 10 | .project 11 | .classpath 12 | .settings/ 13 | bin/ 14 | 15 | # IntelliJ 16 | .idea 17 | *.ipr 18 | *.iml 19 | *.iws 20 | 21 | # NetBeans 22 | nb-configuration.xml 23 | 24 | # Visual Studio Code 25 | .vscode 26 | .factorypath 27 | 28 | # OSX 29 | .DS_Store 30 | 31 | # Vim 32 | *.swp 33 | *.swo 34 | 35 | # patch 36 | *.orig 37 | *.rej 38 | 39 | # Local environment 40 | .env 41 | -------------------------------------------------------------------------------- /service/native-java-quarkus/.mvn/wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | maven-wrapper.jar 2 | -------------------------------------------------------------------------------- /service/native-java-quarkus/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /service/native-java-quarkus/src/main/java/com/ecobenchmark/GreetingResource.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import javax.ws.rs.GET; 4 | import javax.ws.rs.Path; 5 | import javax.ws.rs.Produces; 6 | import javax.ws.rs.core.MediaType; 7 | 8 | @Path("/hello") 9 | public class GreetingResource { 10 | 11 | @GET 12 | @Produces(MediaType.TEXT_PLAIN) 13 | public String hello() { 14 | return "Hello from RESTEasy Reactive"; 15 | } 16 | } -------------------------------------------------------------------------------- /service/native-java-quarkus/src/main/java/com/ecobenchmark/controllers/Healthcheck.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers; 2 | 3 | import javax.ws.rs.GET; 4 | import javax.ws.rs.Path; 5 | import javax.ws.rs.core.Response; 6 | 7 | @Path("/healthcheck") 8 | public class Healthcheck { 9 | 10 | @GET 11 | public Response healthcheck() { 12 | return Response.noContent().build(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /service/native-java-quarkus/src/main/java/com/ecobenchmark/controllers/addaccount/AccountRequest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers.addaccount; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import io.quarkus.runtime.annotations.RegisterForReflection; 5 | 6 | import java.time.Instant; 7 | import java.util.UUID; 8 | 9 | @RegisterForReflection 10 | public class AccountRequest { 11 | 12 | private UUID id; 13 | 14 | private String login; 15 | 16 | @JsonProperty("creation_date") 17 | private Instant creationDate; 18 | 19 | public UUID getId() { 20 | return id; 21 | } 22 | 23 | public void setId(UUID id) { 24 | this.id = id; 25 | } 26 | 27 | public String getLogin() { 28 | return login; 29 | } 30 | 31 | public void setLogin(String login) { 32 | this.login = login; 33 | } 34 | 35 | public Instant getCreationDate() { 36 | return creationDate; 37 | } 38 | 39 | public void setCreationDate(Instant creationDate) { 40 | this.creationDate = creationDate; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /service/native-java-quarkus/src/main/java/com/ecobenchmark/controllers/addlist/ListRequest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers.addlist; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | @RegisterForReflection 6 | public class ListRequest { 7 | 8 | private String name; 9 | 10 | public String getName() { 11 | return name; 12 | } 13 | 14 | public void setName(String name) { 15 | this.name = name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /service/native-java-quarkus/src/main/java/com/ecobenchmark/controllers/addtask/TaskRequest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.controllers.addtask; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | @RegisterForReflection 6 | public class TaskRequest { 7 | 8 | private String name; 9 | 10 | private String description; 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public void setName(String name) { 17 | this.name = name; 18 | } 19 | 20 | public String getDescription() { 21 | return description; 22 | } 23 | 24 | public void setDescription(String description) { 25 | this.description = description; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /service/native-java-quarkus/src/main/java/com/ecobenchmark/repositories/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.repositories; 2 | 3 | import com.ecobenchmark.entities.Account; 4 | import io.quarkus.hibernate.orm.panache.PanacheRepository; 5 | 6 | import javax.enterprise.context.ApplicationScoped; 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | @ApplicationScoped 11 | public class AccountRepository implements PanacheRepository { 12 | 13 | public Optional findByIdOptional(UUID accountId) { 14 | return find("id", accountId).firstResultOptional(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /service/native-java-quarkus/src/main/java/com/ecobenchmark/repositories/ListEntityRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.repositories; 2 | 3 | import com.ecobenchmark.entities.ListEntity; 4 | import io.quarkus.hibernate.orm.panache.PanacheRepository; 5 | 6 | import javax.enterprise.context.ApplicationScoped; 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | @ApplicationScoped 11 | public class ListEntityRepository implements PanacheRepository { 12 | public Optional findByIdOptional(UUID listId) { 13 | return find("id", listId).firstResultOptional(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /service/native-java-quarkus/src/main/java/com/ecobenchmark/repositories/TaskRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark.repositories; 2 | 3 | import com.ecobenchmark.entities.Task; 4 | import io.quarkus.hibernate.orm.panache.PanacheRepository; 5 | 6 | import javax.enterprise.context.ApplicationScoped; 7 | 8 | @ApplicationScoped 9 | public class TaskRepository implements PanacheRepository { 10 | } 11 | -------------------------------------------------------------------------------- /service/native-java-quarkus/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Boavizta/ecobenchmark-applicationweb-backend/aeea2451de2e4fb83333d9cc8b7090e27b5ba022/service/native-java-quarkus/src/main/resources/application.properties -------------------------------------------------------------------------------- /service/native-java-quarkus/src/test/java/com/ecobenchmark/FullScenarioTest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import com.ecobenchmark.controllers.addaccount.AccountRequest; 4 | import io.quarkus.test.junit.QuarkusTest; 5 | import io.restassured.http.ContentType; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static io.restassured.RestAssured.given; 9 | 10 | @QuarkusTest 11 | public class FullScenarioTest { 12 | 13 | @Test 14 | public void testScenarioTodo() { 15 | AccountRequest accountRequest= new AccountRequest(); 16 | accountRequest.setLogin("user1"); 17 | given() 18 | .when() 19 | .body(accountRequest) 20 | .contentType(ContentType.JSON) 21 | .post("/api/accounts") 22 | .then() 23 | .statusCode(200); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /service/native-java-quarkus/src/test/java/com/ecobenchmark/GreetingResourceIT.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import io.quarkus.test.junit.QuarkusIntegrationTest; 4 | 5 | @QuarkusIntegrationTest 6 | public class GreetingResourceIT extends GreetingResourceTest { 7 | // Execute the same tests but in packaged mode. 8 | } 9 | -------------------------------------------------------------------------------- /service/native-java-quarkus/src/test/java/com/ecobenchmark/GreetingResourceTest.java: -------------------------------------------------------------------------------- 1 | package com.ecobenchmark; 2 | 3 | import io.quarkus.test.junit.QuarkusTest; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static io.restassured.RestAssured.given; 7 | import static org.hamcrest.CoreMatchers.is; 8 | 9 | @QuarkusTest 10 | public class GreetingResourceTest { 11 | 12 | @Test 13 | public void testHelloEndpoint() { 14 | given() 15 | .when().get("/hello") 16 | .then() 17 | .statusCode(200) 18 | .body(is("Hello from RESTEasy Reactive")); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /service/node-express-sequelize/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /service/node-express-sequelize/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:current-bullseye 2 | 3 | ENV PORT=8080 4 | 5 | WORKDIR /code 6 | COPY package.json package-lock.json /code/ 7 | RUN npm clean-install 8 | 9 | COPY src /code/src 10 | 11 | ENTRYPOINT ["/usr/local/bin/node", "/code/src/main.js"] -------------------------------------------------------------------------------- /service/node-express-sequelize/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | runner: 3 | image: jdrouet/eco-benchmark:runner-default 4 | build: ../../runner 5 | command: 6 | - run 7 | - "--vus" 8 | - "2" 9 | - "--duration" 10 | - "1m" 11 | - "--env" 12 | - "SERVER_HOST=service:8080" 13 | - "/config/scenario.js" 14 | depends_on: 15 | - service 16 | 17 | service: 18 | image: jdrouet/eco-benchmark:service-node-express-sequelize-default 19 | build: . 20 | depends_on: 21 | - database 22 | environment: 23 | - DATABASE_URL=postgres://postgres:mysecretpassword@database:5432/postgres 24 | - DATABASE_POOL_MAX=20 25 | restart: unless-stopped 26 | 27 | database: 28 | image: jdrouet/eco-benchmark:database-default 29 | build: ../../migrations 30 | environment: 31 | - POSTGRES_PASSWORD=mysecretpassword 32 | healthcheck: 33 | test: "/usr/bin/psql -U postgres postgres -c \"\\d\"" 34 | interval: 3s 35 | timeout: 1s 36 | retries: 20 37 | restart: unless-stopped 38 | -------------------------------------------------------------------------------- /service/node-express-sequelize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-express-sequelize", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "src/main.js", 6 | "scripts": { 7 | "serve": "node src/main.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.20.0", 14 | "express": "^4.17.3", 15 | "joi": "^17.6.0", 16 | "morgan": "^1.10.0", 17 | "pg": "^8.7.3", 18 | "pg-hstore": "^2.3.4", 19 | "sequelize": "^6.20.1" 20 | }, 21 | "devDependencies": { 22 | "sequelize-cli": "^6.4.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /service/node-express-sequelize/src/handler/create-account.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const models = require('../model'); 3 | 4 | const schema = Joi.object().keys({ 5 | login: Joi.string().required(), 6 | }).required(); 7 | 8 | module.exports = (req, res, next) => { 9 | const body = schema.validate(req.body, {stripUnknown: true}); 10 | if (body.error) return next(body.error); 11 | return models.account 12 | .create(body.value) 13 | .then((account) => res.send(account)) 14 | .catch(next); 15 | } -------------------------------------------------------------------------------- /service/node-express-sequelize/src/handler/create-list.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const models = require('../model'); 3 | 4 | const schema = Joi.object().keys({ 5 | name: Joi.string().required(), 6 | }).required(); 7 | 8 | module.exports = (req, res, next) => { 9 | const body = schema.validate(req.body, {stripUnknown: true}); 10 | if (body.error) return next(body.error); 11 | return models.list 12 | .create({ 13 | ...body.value, 14 | accountId: req.params.account, 15 | }) 16 | .then((list) => res.send(list)) 17 | .catch(next); 18 | } -------------------------------------------------------------------------------- /service/node-express-sequelize/src/handler/create-task.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const models = require('../model'); 3 | 4 | const schema = Joi.object().keys({ 5 | name: Joi.string().required(), 6 | description: Joi.string().required(), 7 | }).required(); 8 | 9 | module.exports = (req, res, next) => { 10 | const body = schema.validate(req.body, {stripUnknown: true}); 11 | if (body.error) return next(body.error); 12 | return models.task 13 | .create({ 14 | ...body.value, 15 | listId: req.params.list, 16 | }) 17 | .then((task) => res.send(task)) 18 | .catch(next); 19 | } -------------------------------------------------------------------------------- /service/node-express-sequelize/src/handler/healthcheck.js: -------------------------------------------------------------------------------- 1 | module.exports = (_, res) => res.status(204).end(); 2 | -------------------------------------------------------------------------------- /service/node-express-sequelize/src/handler/index.js: -------------------------------------------------------------------------------- 1 | const {Router} = require('express'); 2 | 3 | const router = new Router(); 4 | 5 | router.head('/healthcheck', require('./healthcheck')); 6 | router.post('/api/accounts', require('./create-account')); 7 | router.post('/api/accounts/:account/lists', require('./create-list')); 8 | router.get('/api/accounts/:account/lists', require('./list-list')); 9 | router.post('/api/lists/:list/tasks', require('./create-task')); 10 | router.get('/api/stats', require('./stats')); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /service/node-express-sequelize/src/handler/list-list.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const models = require('../model'); 3 | 4 | const schema = Joi.object().keys({ 5 | page: Joi.number(), 6 | }); 7 | 8 | module.exports = (req, res, next) => { 9 | const query = schema.validate(req.query, {stripUnknown: true}); 10 | if (query.error) return next(query.error); 11 | return models.list.findByAccount(req.params.account, query.value?.page ?? 0) 12 | .then((lists) => res.send(lists)) 13 | .catch(next); 14 | } 15 | -------------------------------------------------------------------------------- /service/node-express-sequelize/src/handler/stats.js: -------------------------------------------------------------------------------- 1 | const models = require('../model'); 2 | 3 | module.exports = (req, res, next) => { 4 | return models.account.statistics() 5 | .then((lists) => res.send(lists)) 6 | .catch(next); 7 | } 8 | -------------------------------------------------------------------------------- /service/node-express-sequelize/src/main.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const morgan = require('morgan'); 4 | 5 | const PORT = process.env.PORT || 3000; 6 | 7 | const app = express(); 8 | 9 | app.use(morgan('tiny')); 10 | app.use(bodyParser.json()); 11 | app.use(require('./handler')); 12 | app.use(require('./middleware/error-handler')); 13 | 14 | app.listen(PORT, (err) => { 15 | if (err) { 16 | console.error(err); 17 | process.exit(1); 18 | } 19 | console.log(`server running on port ${PORT}`); 20 | }); -------------------------------------------------------------------------------- /service/node-express-sequelize/src/middleware/error-handler.js: -------------------------------------------------------------------------------- 1 | module.exports = (err, req, res, next) => { 2 | if (err.isJoi) { 3 | return res.status(422).json(err); 4 | } 5 | if (err.status) { 6 | res.status(err.status); 7 | } else { 8 | console.log(err); 9 | res.status(500); 10 | } 11 | return res.send(err); 12 | }; 13 | -------------------------------------------------------------------------------- /service/node-express-sequelize/src/model/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const Sequelize = require('sequelize'); 4 | const sequelize = require('../service/database'); 5 | 6 | const basename = path.basename(__filename); 7 | const db = {}; 8 | 9 | fs 10 | .readdirSync(__dirname) 11 | .filter(file => { 12 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); 13 | }) 14 | .forEach(file => { 15 | const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); 16 | db[model.name] = model; 17 | }); 18 | 19 | Object.keys(db).forEach(modelName => { 20 | if (db[modelName].associate) { 21 | db[modelName].associate(db); 22 | } 23 | }); 24 | 25 | db.sequelize = sequelize; 26 | db.Sequelize = Sequelize; 27 | 28 | module.exports = db; 29 | -------------------------------------------------------------------------------- /service/node-express-sequelize/src/model/task.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('sequelize'); 2 | 3 | module.exports = (sequelize, DataTypes) => { 4 | class Task extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | Task.belongsTo(models.list, { 13 | foreignKey: 'listId', 14 | }); 15 | } 16 | } 17 | 18 | Task.init({ 19 | id: { 20 | primaryKey: true, 21 | allowNull: false, 22 | type: DataTypes.UUID, 23 | defaultValue: DataTypes.UUIDV4, 24 | }, 25 | listId: { 26 | type: DataTypes.UUID, 27 | allowNull: false, 28 | }, 29 | name: { 30 | type: DataTypes.TEXT, 31 | }, 32 | description: { 33 | type: DataTypes.TEXT, 34 | }, 35 | }, { 36 | sequelize, 37 | tableName: 'task', 38 | createdAt: 'creationDate', 39 | updatedAt: false, 40 | underscored: true, 41 | modelName: 'task', 42 | }); 43 | 44 | return Task; 45 | }; -------------------------------------------------------------------------------- /service/node-express-sequelize/src/service/database.js: -------------------------------------------------------------------------------- 1 | const { Sequelize } = require('sequelize'); 2 | 3 | module.exports = new Sequelize(process.env.DATABASE_URL, { 4 | pool: { 5 | max: process.env.DATABASE_MAX_POOL ? +process.env.DATABASE_MAX_POOL : 10, 6 | }, 7 | }); -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ###> symfony/framework-bundle ### 3 | /.env.local 4 | /.env.local.php 5 | /.env.*.local 6 | /config/secrets/prod/prod.decrypt.private.php 7 | /public/bundles/ 8 | /var/ 9 | /vendor/ 10 | ###< symfony/framework-bundle ### 11 | 12 | .idea -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.1-apache-buster 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y libpq-dev unzip && \ 5 | rm -rf /var/cache/apt/archives /var/lib/apt/lists/* && \ 6 | docker-php-ext-install pdo_pgsql && \ 7 | a2enmod rewrite && \ 8 | rm /etc/apache2/sites-available/000-default.conf && \ 9 | rm /etc/apache2/sites-available/default-ssl.conf && \ 10 | echo 'Listen 8080' > /etc/apache2/ports.conf && \ 11 | curl https://getcomposer.org/installer -o composer-setup.php && \ 12 | php composer-setup.php --install-dir=/usr/local/bin --filename=composer 13 | COPY apache.conf /etc/apache2/sites-enabled/symfony.conf 14 | COPY . /var/www/project 15 | WORKDIR /var/www/project 16 | RUN chown -R www-data /var/www/project 17 | USER www-data 18 | RUN composer install 19 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/README.md: -------------------------------------------------------------------------------- 1 | # How to run 2 | 3 | ## Build the image 4 | 5 | ```bash 6 | docker build -t benchmark-php-symfony-apache2-dev . 7 | ``` 8 | 9 | ## Run the container 10 | 11 | ```bash 12 | docker run --name=benchmark-php-symfony-apache2-dev -d -p 8080:8080 -e DATABASE_URL='postgresql://postgres:mysecretpassword@172.17.0.2:5432/postgres' benchmark-php-symfony-apache2-dev 13 | ``` 14 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | ['all' => true], 5 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], 6 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 7 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 8 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 9 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 10 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 11 | ]; 12 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | 'DoctrineMigrations': '%kernel.project_dir%/migrations' 6 | enable_profiler: '%kernel.debug%' 7 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | uid: 4 | default_uuid_version: 4 5 | secret: '%env(APP_SECRET)%' 6 | #csrf_protection: true 7 | http_method_override: false 8 | 9 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 10 | # Remove or comment this section to explicitly disable session support. 11 | session: 12 | handler_id: null 13 | cookie_secure: auto 14 | cookie_samesite: lax 15 | storage_factory_id: session.storage.factory.native 16 | 17 | #esi: true 18 | #fragments: true 19 | php_errors: 20 | log: true 21 | 22 | when@test: 23 | framework: 24 | test: true 25 | session: 26 | storage_factory_id: session.storage.factory.mock_file 27 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | 5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 7 | #default_uri: http://localhost 8 | 9 | when@prod: 10 | framework: 11 | router: 12 | strict_requirements: null 13 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/config/packages/sensio_framework_extra.yaml: -------------------------------------------------------------------------------- 1 | sensio_framework_extra: 2 | router: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/config/packages/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | when@dev: 2 | web_profiler: 3 | toolbar: true 4 | intercept_redirects: false 5 | 6 | framework: 7 | profiler: { only_exceptions: false } 8 | 9 | when@test: 10 | web_profiler: 11 | toolbar: false 12 | intercept_redirects: false 13 | 14 | framework: 15 | profiler: { collect: false } 16 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/config/preload.php: -------------------------------------------------------------------------------- 1 | setLogin($this->login); 14 | $account->setCreationDate(new \DateTime()); 15 | 16 | return $account; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/src/Controller/AddAccount/AccountResponse.php: -------------------------------------------------------------------------------- 1 | id = $account->getId(); 16 | $accountResponse->login = $account->getLogin(); 17 | $accountResponse->creation_date = $account->getCreationDate()->format('c'); 18 | 19 | return $accountResponse; 20 | } 21 | } -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/src/Controller/AddAccount/AddAccount.php: -------------------------------------------------------------------------------- 1 | deserialize($request->getContent(), AccountRequest::class, 'json'); 19 | $account = $accountRequest->toAccountEntity(); 20 | $entityManager = $doctrine->getManager(); 21 | $entityManager->persist($account); 22 | $entityManager->flush(); 23 | 24 | return $this->json(AccountResponse::fromAccountEntity($account), 201); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/src/Controller/AddList/ListRequest.php: -------------------------------------------------------------------------------- 1 | setName($this->name); 16 | $list->setAccount($account); 17 | $list->setCreationDate(new \DateTime()); 18 | 19 | return $list; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/src/Controller/AddList/ListResponse.php: -------------------------------------------------------------------------------- 1 | id = $listEntity->getId(); 17 | $listResponse->name = $listEntity->getName(); 18 | $listResponse->creation_date = $listEntity->getCreationDate()->format('c'); 19 | 20 | return $listResponse; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/src/Controller/AddTask/TaskRequest.php: -------------------------------------------------------------------------------- 1 | setList($listEntity); 17 | $task->setName($this->name); 18 | $task->setDescription($this->description); 19 | $task->setCreationDate(new \DateTime()); 20 | 21 | return $task; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/src/Controller/AddTask/TaskResponse.php: -------------------------------------------------------------------------------- 1 | id = $task->getId(); 19 | $taskResponse->list_id = $task->getList()->getId(); 20 | $taskResponse->name = $task->getName(); 21 | $taskResponse->description = $task->getDescription(); 22 | $taskResponse->creation_date = $task->getCreationDate()->format('c'); 23 | 24 | return $taskResponse; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/src/Controller/GetLists/GetLists.php: -------------------------------------------------------------------------------- 1 | getManager()->getRepository(ListEntity::class); 19 | $lists = $listRepository->findAllByAccountJoinTasks($account_id, $request->query->getInt('page')); 20 | 21 | return $this->json(ListResponse::fromListEntities($lists)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/src/Controller/GetLists/TaskResponse.php: -------------------------------------------------------------------------------- 1 | id = $task->getId(); 18 | $taskResponse->name = $task->getName(); 19 | $taskResponse->description = $task->getDescription(); 20 | $taskResponse->creation_date = $task->getCreationDate()->format('c'); 21 | 22 | return $taskResponse; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/src/Controller/GetStats/GetStats.php: -------------------------------------------------------------------------------- 1 | getManager()->getRepository(Account::class); 17 | $stats = $accountRepository->getStats(); 18 | 19 | return $this->json(StatResponse::fromStats($stats)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/src/Controller/GetStats/StatResponse.php: -------------------------------------------------------------------------------- 1 | account_id = $stat['id']; 21 | $statResponse->account_login = $stat['login']; 22 | $statResponse->list_count = $stat['nb_list']; 23 | $statResponse->task_avg = $stat['avg_tasks']; 24 | $statsResponse[] = $statResponse; 25 | } 26 | return $statsResponse; 27 | } 28 | } -------------------------------------------------------------------------------- /service/php-symfony-apache2-dev/src/Controller/Healthcheck.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Welcome!{% endblock %} 6 | 7 | {# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #} 8 | {% block stylesheets %} 9 | {{ encore_entry_link_tags('app') }} 10 | {% endblock %} 11 | 12 | {% block javascripts %} 13 | {{ encore_entry_script_tags('app') }} 14 | {% endblock %} 15 | 16 | 17 | {% block body %}{% endblock %} 18 | 19 | 20 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ###> symfony/framework-bundle ### 3 | /.env.local 4 | /.env.local.php 5 | /.env.*.local 6 | /config/secrets/prod/prod.decrypt.private.php 7 | /public/bundles/ 8 | /var/ 9 | /vendor/ 10 | ###< symfony/framework-bundle ### 11 | 12 | .idea -------------------------------------------------------------------------------- /service/php-symfony-apache2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.1-apache-buster 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y libpq-dev unzip && \ 5 | rm -rf /var/cache/apt/archives /var/lib/apt/lists/* && \ 6 | docker-php-ext-install pdo_pgsql opcache && \ 7 | a2enmod rewrite && \ 8 | rm /etc/apache2/sites-available/000-default.conf && \ 9 | rm /etc/apache2/sites-available/default-ssl.conf && \ 10 | echo 'Listen 8080' > /etc/apache2/ports.conf && \ 11 | curl https://getcomposer.org/installer -o composer-setup.php && \ 12 | php composer-setup.php --install-dir=/usr/local/bin --filename=composer 13 | COPY apache.conf /etc/apache2/sites-enabled/symfony.conf 14 | COPY docker/php.ini /usr/local/etc/php/conf.d/symfony.ini 15 | RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/conf.d/php.ini 16 | ENV APP_ENV prod 17 | COPY . /var/www/project 18 | WORKDIR /var/www/project 19 | RUN chown -R www-data /var/www/project 20 | USER www-data 21 | RUN composer install --prefer-dist --no-dev --no-interaction && \ 22 | composer dump-autoload --no-dev --classmap-authoritative && \ 23 | composer dump-env prod && \ 24 | php bin/console cache:clear --env prod 25 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/README.md: -------------------------------------------------------------------------------- 1 | # How to run 2 | 3 | ## Build the image 4 | 5 | ```bash 6 | docker build -t benchmark-php-symfony-apache2 . 7 | ``` 8 | 9 | ## Run the container 10 | 11 | ```bash 12 | docker run --name=benchmark-php-symfony-apache2 -d -p 8080:8080 -e DATABASE_URL='postgresql://postgres:mysecretpassword@172.17.0.2:5432/postgres' benchmark-php-symfony-apache2 13 | ``` 14 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | ['all' => true], 5 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], 6 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 7 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 8 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 9 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['dev' => true], 10 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 11 | ]; 12 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | 'DoctrineMigrations': '%kernel.project_dir%/migrations' 6 | enable_profiler: '%kernel.debug%' 7 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | uid: 4 | default_uuid_version: 4 5 | secret: '%env(APP_SECRET)%' 6 | #csrf_protection: true 7 | http_method_override: false 8 | 9 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 10 | # Remove or comment this section to explicitly disable session support. 11 | session: 12 | handler_id: null 13 | cookie_secure: auto 14 | cookie_samesite: lax 15 | storage_factory_id: session.storage.factory.native 16 | 17 | #esi: true 18 | #fragments: true 19 | php_errors: 20 | log: true 21 | 22 | when@test: 23 | framework: 24 | test: true 25 | session: 26 | storage_factory_id: session.storage.factory.mock_file 27 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | 5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 7 | #default_uri: http://localhost 8 | 9 | when@prod: 10 | framework: 11 | router: 12 | strict_requirements: null 13 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/config/packages/sensio_framework_extra.yaml: -------------------------------------------------------------------------------- 1 | sensio_framework_extra: 2 | router: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/config/packages/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | when@dev: 2 | web_profiler: 3 | toolbar: true 4 | intercept_redirects: false 5 | 6 | framework: 7 | profiler: { only_exceptions: false } 8 | 9 | when@test: 10 | web_profiler: 11 | toolbar: false 12 | intercept_redirects: false 13 | 14 | framework: 15 | profiler: { collect: false } 16 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/config/preload.php: -------------------------------------------------------------------------------- 1 | setLogin($this->login); 14 | $account->setCreationDate(new \DateTime()); 15 | 16 | return $account; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/AddAccount/AccountResponse.php: -------------------------------------------------------------------------------- 1 | id = $account->getId(); 16 | $accountResponse->login = $account->getLogin(); 17 | $accountResponse->creation_date = $account->getCreationDate()->format('c'); 18 | 19 | return $accountResponse; 20 | } 21 | } -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/AddAccount/AddAccount.php: -------------------------------------------------------------------------------- 1 | deserialize($request->getContent(), AccountRequest::class, 'json'); 19 | $account = $accountRequest->toAccountEntity(); 20 | $entityManager = $doctrine->getManager(); 21 | $entityManager->persist($account); 22 | $entityManager->flush(); 23 | 24 | return $this->json(AccountResponse::fromAccountEntity($account), 201); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/AddList/AddList.php: -------------------------------------------------------------------------------- 1 | getManager(); 19 | 20 | /** @var ListRequest $listRequest */ 21 | $listRequest = $serializer->deserialize($request->getContent(), ListRequest::class, 'json'); 22 | $list = $listRequest->toListEntity($entityManager->getReference('App\Entity\Account', new UuidV4($id))); 23 | $entityManager->persist($list); 24 | $entityManager->flush(); 25 | 26 | return $this->json(ListResponse::fromListEntity($list), 201); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/AddList/ListRequest.php: -------------------------------------------------------------------------------- 1 | setName($this->name); 16 | $list->setAccount($account); 17 | $list->setCreationDate(new \DateTime()); 18 | 19 | return $list; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/AddList/ListResponse.php: -------------------------------------------------------------------------------- 1 | id = $listEntity->getId(); 17 | $listResponse->name = $listEntity->getName(); 18 | $listResponse->creation_date = $listEntity->getCreationDate()->format('c'); 19 | 20 | return $listResponse; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/AddTask/AddTask.php: -------------------------------------------------------------------------------- 1 | getManager(); 19 | 20 | /** @var TaskRequest $taskRequest */ 21 | $taskRequest = $serializer->deserialize($request->getContent(), TaskRequest::class, 'json'); 22 | $task = $taskRequest->toTaskEntity($entityManager->getReference('App\Entity\ListEntity', new UuidV4($id))); 23 | $entityManager->persist($task); 24 | $entityManager->flush(); 25 | 26 | return $this->json(TaskResponse::fromTaskEntity($task), 201); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/AddTask/TaskRequest.php: -------------------------------------------------------------------------------- 1 | setList($listEntity); 17 | $task->setName($this->name); 18 | $task->setDescription($this->description); 19 | $task->setCreationDate(new \DateTime()); 20 | 21 | return $task; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/AddTask/TaskResponse.php: -------------------------------------------------------------------------------- 1 | id = $task->getId(); 19 | $taskResponse->list_id = $task->getList()->getId(); 20 | $taskResponse->name = $task->getName(); 21 | $taskResponse->description = $task->getDescription(); 22 | $taskResponse->creation_date = $task->getCreationDate()->format('c'); 23 | 24 | return $taskResponse; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/GetLists/GetLists.php: -------------------------------------------------------------------------------- 1 | getManager()->getRepository(ListEntity::class); 19 | $lists = $listRepository->findAllByAccountJoinTasks($account_id, $request->query->getInt('page')); 20 | 21 | return $this->json(ListResponse::fromListEntities($lists)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/GetLists/TaskResponse.php: -------------------------------------------------------------------------------- 1 | id = $task->getId(); 18 | $taskResponse->name = $task->getName(); 19 | $taskResponse->description = $task->getDescription(); 20 | $taskResponse->creation_date = $task->getCreationDate()->format('c'); 21 | 22 | return $taskResponse; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/GetStats/GetStats.php: -------------------------------------------------------------------------------- 1 | getManager()->getRepository(Account::class); 17 | $stats = $accountRepository->getStats(); 18 | 19 | return $this->json(StatResponse::fromStats($stats)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/GetStats/StatResponse.php: -------------------------------------------------------------------------------- 1 | account_id = $stat['id']; 21 | $statResponse->account_login = $stat['login']; 22 | $statResponse->list_count = $stat['nb_list']; 23 | $statResponse->task_avg = $stat['avg_tasks']; 24 | $statsResponse[] = $statResponse; 25 | } 26 | return $statsResponse; 27 | } 28 | } -------------------------------------------------------------------------------- /service/php-symfony-apache2/src/Controller/Healthcheck.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Welcome!{% endblock %} 6 | 7 | {# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #} 8 | {% block stylesheets %} 9 | {{ encore_entry_link_tags('app') }} 10 | {% endblock %} 11 | 12 | {% block javascripts %} 13 | {{ encore_entry_script_tags('app') }} 14 | {% endblock %} 15 | 16 | 17 | {% block body %}{% endblock %} 18 | 19 | 20 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ###> symfony/framework-bundle ### 3 | /.env.local 4 | /.env.local.php 5 | /.env.*.local 6 | /config/secrets/prod/prod.decrypt.private.php 7 | /public/bundles/ 8 | /var/ 9 | /vendor/ 10 | ###< symfony/framework-bundle ### 11 | 12 | .idea -------------------------------------------------------------------------------- /service/php-symfony-nginx/README.md: -------------------------------------------------------------------------------- 1 | # How to run 2 | 3 | ## Build the image 4 | For Mac with M1, you need to add --platform linux/amd64 5 | 6 | ```bash 7 | docker build -t benchmark-php-symfony-nginx . 8 | ``` 9 | 10 | ## Run the container 11 | ### Get database IP addresses 12 | ```bash 13 | docker inspect \ 14 | -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' eco-benchmark-database 15 | ``` 16 | 17 | Be careful to replace the IP address of the database (172.17.0.2) with the previously obtained result. 18 | 19 | ```bash 20 | docker run --name=benchmark-php-symfony-nginx -d -p 8080:8080 -e DATABASE_URL='postgresql://postgres:mysecretpassword@172.17.0.2:5432/postgres' benchmark-php-symfony-nginx 21 | ``` 22 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | ['all' => true], 5 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], 6 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 7 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 8 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 9 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['dev' => true], 10 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 11 | ]; 12 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | 'DoctrineMigrations': '%kernel.project_dir%/migrations' 6 | enable_profiler: '%kernel.debug%' 7 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | uid: 4 | default_uuid_version: 4 5 | secret: '%env(APP_SECRET)%' 6 | #csrf_protection: true 7 | http_method_override: false 8 | 9 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 10 | # Remove or comment this section to explicitly disable session support. 11 | session: 12 | handler_id: null 13 | cookie_secure: auto 14 | cookie_samesite: lax 15 | storage_factory_id: session.storage.factory.native 16 | 17 | #esi: true 18 | #fragments: true 19 | php_errors: 20 | log: true 21 | 22 | when@test: 23 | framework: 24 | test: true 25 | session: 26 | storage_factory_id: session.storage.factory.mock_file 27 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | 5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 7 | #default_uri: http://localhost 8 | 9 | when@prod: 10 | framework: 11 | router: 12 | strict_requirements: null 13 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/config/packages/sensio_framework_extra.yaml: -------------------------------------------------------------------------------- 1 | sensio_framework_extra: 2 | router: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/config/packages/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | when@dev: 2 | web_profiler: 3 | toolbar: true 4 | intercept_redirects: false 5 | 6 | framework: 7 | profiler: { only_exceptions: false } 8 | 9 | when@test: 10 | web_profiler: 11 | toolbar: false 12 | intercept_redirects: false 13 | 14 | framework: 15 | profiler: { collect: false } 16 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/config/preload.php: -------------------------------------------------------------------------------- 1 | setLogin($this->login); 14 | $account->setCreationDate(new \DateTime()); 15 | 16 | return $account; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/AddAccount/AccountResponse.php: -------------------------------------------------------------------------------- 1 | id = $account->getId(); 16 | $accountResponse->login = $account->getLogin(); 17 | $accountResponse->creation_date = $account->getCreationDate()->format('c'); 18 | 19 | return $accountResponse; 20 | } 21 | } -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/AddAccount/AddAccount.php: -------------------------------------------------------------------------------- 1 | deserialize($request->getContent(), AccountRequest::class, 'json'); 19 | $account = $accountRequest->toAccountEntity(); 20 | $entityManager = $doctrine->getManager(); 21 | $entityManager->persist($account); 22 | $entityManager->flush(); 23 | 24 | return $this->json(AccountResponse::fromAccountEntity($account), 201); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/AddList/AddList.php: -------------------------------------------------------------------------------- 1 | getManager(); 19 | 20 | /** @var ListRequest $listRequest */ 21 | $listRequest = $serializer->deserialize($request->getContent(), ListRequest::class, 'json'); 22 | $list = $listRequest->toListEntity($entityManager->getReference('App\Entity\Account', new UuidV4($id))); 23 | $entityManager->persist($list); 24 | $entityManager->flush(); 25 | 26 | return $this->json(ListResponse::fromListEntity($list), 201); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/AddList/ListRequest.php: -------------------------------------------------------------------------------- 1 | setName($this->name); 16 | $list->setAccount($account); 17 | $list->setCreationDate(new \DateTime()); 18 | 19 | return $list; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/AddList/ListResponse.php: -------------------------------------------------------------------------------- 1 | id = $listEntity->getId(); 17 | $listResponse->name = $listEntity->getName(); 18 | $listResponse->creation_date = $listEntity->getCreationDate()->format('c'); 19 | 20 | return $listResponse; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/AddTask/AddTask.php: -------------------------------------------------------------------------------- 1 | getManager(); 19 | 20 | /** @var TaskRequest $taskRequest */ 21 | $taskRequest = $serializer->deserialize($request->getContent(), TaskRequest::class, 'json'); 22 | $task = $taskRequest->toTaskEntity($entityManager->getReference('App\Entity\ListEntity', new UuidV4($id))); 23 | $entityManager->persist($task); 24 | $entityManager->flush(); 25 | 26 | return $this->json(TaskResponse::fromTaskEntity($task), 201); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/AddTask/TaskRequest.php: -------------------------------------------------------------------------------- 1 | setList($listEntity); 17 | $task->setName($this->name); 18 | $task->setDescription($this->description); 19 | $task->setCreationDate(new \DateTime()); 20 | 21 | return $task; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/AddTask/TaskResponse.php: -------------------------------------------------------------------------------- 1 | id = $task->getId(); 19 | $taskResponse->list_id = $task->getList()->getId(); 20 | $taskResponse->name = $task->getName(); 21 | $taskResponse->description = $task->getDescription(); 22 | $taskResponse->creation_date = $task->getCreationDate()->format('c'); 23 | 24 | return $taskResponse; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/GetLists/GetLists.php: -------------------------------------------------------------------------------- 1 | getManager()->getRepository(ListEntity::class); 19 | $lists = $listRepository->findAllByAccountJoinTasks($account_id, $request->query->getInt('page')); 20 | 21 | return $this->json(ListResponse::fromListEntities($lists)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/GetLists/TaskResponse.php: -------------------------------------------------------------------------------- 1 | id = $task->getId(); 18 | $taskResponse->name = $task->getName(); 19 | $taskResponse->description = $task->getDescription(); 20 | $taskResponse->creation_date = $task->getCreationDate()->format('c'); 21 | 22 | return $taskResponse; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/GetStats/GetStats.php: -------------------------------------------------------------------------------- 1 | getManager()->getRepository(Account::class); 17 | $stats = $accountRepository->getStats(); 18 | 19 | return $this->json(StatResponse::fromStats($stats)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/GetStats/StatResponse.php: -------------------------------------------------------------------------------- 1 | account_id = $stat['id']; 21 | $statResponse->account_login = $stat['login']; 22 | $statResponse->list_count = $stat['nb_list']; 23 | $statResponse->task_avg = $stat['avg_tasks']; 24 | $statsResponse[] = $statResponse; 25 | } 26 | return $statsResponse; 27 | } 28 | } -------------------------------------------------------------------------------- /service/php-symfony-nginx/src/Controller/Healthcheck.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Welcome!{% endblock %} 6 | 7 | {# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #} 8 | {% block stylesheets %} 9 | {{ encore_entry_link_tags('app') }} 10 | {% endblock %} 11 | 12 | {% block javascripts %} 13 | {{ encore_entry_script_tags('app') }} 14 | {% endblock %} 15 | 16 | 17 | {% block body %}{% endblock %} 18 | 19 | 20 | -------------------------------------------------------------------------------- /service/python-fastapi/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10 2 | WORKDIR /code 3 | COPY ./requirements.txt /code/requirements.txt 4 | RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt 5 | COPY ./app /code/app 6 | CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] -------------------------------------------------------------------------------- /service/python-fastapi/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | runner: 3 | image: jdrouet/eco-benchmark:runner-default 4 | build: ../../runner 5 | command: 6 | - run 7 | - "--vus" 8 | - "2" 9 | - "--duration" 10 | - "1m" 11 | - "--env" 12 | - "SERVER_HOST=service:80" 13 | - "/config/scenario.js" 14 | depends_on: 15 | - service 16 | 17 | service: 18 | image: jdrouet/eco-benchmark:service-python-fastapi-default 19 | build: . 20 | depends_on: 21 | - database 22 | environment: 23 | - DATABASE_URL=postgresql://postgres:mysecretpassword@database:5432/postgres 24 | - DATABASE_POOL_SIZE=20 25 | restart: unless-stopped 26 | 27 | database: 28 | image: jdrouet/eco-benchmark:database-default 29 | build: ../../migrations 30 | environment: 31 | - POSTGRES_PASSWORD=mysecretpassword 32 | healthcheck: 33 | test: "/usr/bin/psql -U postgres postgres -c \"\\d\"" 34 | interval: 3s 35 | timeout: 1s 36 | retries: 20 37 | restart: unless-stopped 38 | -------------------------------------------------------------------------------- /service/python-fastapi/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.89.1 2 | psycopg2-binary==2.9.5 3 | SQLAlchemy==2.0.0 4 | uvicorn==0.20.0 5 | -------------------------------------------------------------------------------- /service/python-flask/.envrc: -------------------------------------------------------------------------------- 1 | layout python3 2 | -------------------------------------------------------------------------------- /service/python-flask/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10 2 | WORKDIR /code 3 | COPY ./requirements.txt /code/requirements.txt 4 | RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt 5 | COPY ./app /code/app 6 | CMD ["gunicorn", "--bind=0.0.0.0:80", "app.main:app"] 7 | -------------------------------------------------------------------------------- /service/python-flask/docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | services: 3 | runner: 4 | image: jdrouet/eco-benchmark:runner-default 5 | build: ../../runner 6 | command: 7 | - run 8 | - "--vus" 9 | - "2" 10 | - "--duration" 11 | - "1m" 12 | - "--env" 13 | - "SERVER_HOST=service:80" 14 | - "/config/scenario.js" 15 | depends_on: 16 | - service 17 | 18 | service: 19 | image: jdrouet/eco-benchmark:service-python-flask-default 20 | build: . 21 | depends_on: 22 | - database 23 | environment: 24 | - DATABASE_URL=postgresql://postgres:mysecretpassword@database:5432/postgres 25 | - DATABASE_POOL_SIZE=20 26 | restart: unless-stopped 27 | 28 | database: 29 | image: jdrouet/eco-benchmark:database-default 30 | build: ../../migrations 31 | environment: 32 | - POSTGRES_PASSWORD=mysecretpassword 33 | healthcheck: 34 | test: "/usr/bin/psql -U postgres postgres -c \"\\d\"" 35 | interval: 3s 36 | timeout: 1s 37 | retries: 20 38 | restart: unless-stopped -------------------------------------------------------------------------------- /service/python-flask/requirements.txt: -------------------------------------------------------------------------------- 1 | click==8.1.3 2 | Flask==2.2.2 3 | Flask-Pydantic==0.11.0 4 | greenlet==2.0.2 5 | gunicorn==20.1.0 6 | h11==0.14.0 7 | itsdangerous==2.1.2 8 | Jinja2==3.1.2 9 | MarkupSafe==2.1.2 10 | psycopg2-binary==2.9.5 11 | pydantic==1.10.4 12 | SQLAlchemy==2.0.1 13 | typing_extensions==4.4.0 14 | Werkzeug==2.2.2 15 | -------------------------------------------------------------------------------- /service/rust-actix-native/.dockerignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /service/rust-actix-native/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /service/rust-actix-native/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-actix-sqlx" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | actix-web = { version = "4.0" } 10 | chrono = { version = "0.4", features = ["serde"] } 11 | deadpool-postgres = { version = "0.10", features = ["serde"] } 12 | num-traits = { version = "0.2.14" } 13 | serde = { version = "1.0", features = ["derive"] } 14 | serde_json = { version = "1.0" } 15 | tokio-postgres = { version = "0.7", features = ["with-serde_json-1", "with-uuid-1", "with-chrono-0_4"] } 16 | url = { version = "2.3" } 17 | uuid = { version = "1.1", features = ["serde", "v4"] } 18 | -------------------------------------------------------------------------------- /service/rust-actix-native/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG RUST_VERSION=1.61.0 2 | FROM rust:${RUST_VERSION}-bullseye AS builder 3 | 4 | ENV USER=root 5 | 6 | WORKDIR /code 7 | RUN cargo init 8 | COPY Cargo.lock Cargo.toml /code/ 9 | RUN mkdir -p /code/.cargo 10 | RUN cargo vendor >> /code/.cargo/config.toml 11 | 12 | COPY src /code/src 13 | RUN cargo build --release --offline 14 | 15 | FROM gcr.io/distroless/cc-debian11 16 | 17 | ENV ADDRESS=0.0.0.0 18 | ENV PORT=8080 19 | 20 | COPY --from=builder /code/target/release/rust-actix-sqlx /usr/bin/rust-actix-sqlx 21 | 22 | ENTRYPOINT ["/usr/bin/rust-actix-sqlx"] 23 | -------------------------------------------------------------------------------- /service/rust-actix-native/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | runner: 3 | image: jdrouet/eco-benchmark:runner-default 4 | build: ../../runner 5 | command: 6 | - run 7 | - "--vus" 8 | - "2" 9 | - "--duration" 10 | - "1m" 11 | - "--env" 12 | - "SERVER_HOST=service:8080" 13 | - "/config/scenario.js" 14 | depends_on: 15 | - service 16 | 17 | service: 18 | image: jdrouet/eco-benchmark:service-rust-actix-native-default 19 | build: . 20 | depends_on: 21 | - database 22 | environment: 23 | - DATABASE_URL=postgres://postgres:mysecretpassword@database:5432/postgres 24 | - DATABASE_POOL_MAX=20 25 | restart: unless-stopped 26 | 27 | database: 28 | image: jdrouet/eco-benchmark:database-default 29 | build: ../../migrations 30 | environment: 31 | - POSTGRES_PASSWORD=mysecretpassword 32 | healthcheck: 33 | test: "/usr/bin/psql -U postgres postgres -c \"\\d\"" 34 | interval: 3s 35 | timeout: 1s 36 | retries: 20 37 | restart: unless-stopped 38 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/handler/accounts_create.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::account::create::AccountCreate; 3 | use crate::service::database::Pool; 4 | use actix_web::{post, web, HttpResponse}; 5 | 6 | #[post("/api/accounts")] 7 | async fn handler( 8 | pool: web::Data, 9 | payload: web::Json, 10 | ) -> Result { 11 | let mut conn = pool.get().await?; 12 | let created = payload.execute(&mut conn).await?; 13 | Ok(HttpResponse::Created().json(created)) 14 | } 15 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/handler/health.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{head, HttpResponse}; 2 | 3 | #[head("/healthcheck")] 4 | async fn handler() -> HttpResponse { 5 | HttpResponse::NoContent().finish() 6 | } 7 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/handler/lists_create.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::list::create::ListCreate; 3 | use crate::service::database::Pool; 4 | use actix_web::{post, web, HttpResponse}; 5 | use uuid::Uuid; 6 | 7 | #[post("/api/accounts/{account_id}/lists")] 8 | async fn handler( 9 | pool: web::Data, 10 | account_id: web::Path, 11 | payload: web::Json, 12 | ) -> Result { 13 | let mut conn = pool.get().await?; 14 | let created = payload.execute(&mut conn, account_id.into_inner()).await?; 15 | Ok(HttpResponse::Created().json(created)) 16 | } 17 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/handler/lists_list.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::list::list::ListList; 3 | use crate::service::database::Pool; 4 | use actix_web::{get, web, HttpResponse}; 5 | use uuid::Uuid; 6 | 7 | #[get("/api/accounts/{account_id}/lists")] 8 | async fn handler( 9 | pool: web::Data, 10 | account_id: web::Path, 11 | payload: web::Query, 12 | ) -> Result { 13 | let mut conn = pool.get().await?; 14 | let result = payload.execute(&mut conn, account_id.into_inner()).await?; 15 | Ok(HttpResponse::Ok().json(result)) 16 | } 17 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/handler/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod accounts_create; 2 | pub(crate) mod health; 3 | pub(crate) mod lists_create; 4 | pub(crate) mod lists_list; 5 | pub(crate) mod stats; 6 | pub(crate) mod tasks_create; 7 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/handler/stats.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::stats::AccountStat; 3 | use crate::service::database::Pool; 4 | use actix_web::{get, web, HttpResponse}; 5 | 6 | #[get("/api/stats")] 7 | async fn handler(pool: web::Data) -> Result { 8 | let mut conn = pool.get().await?; 9 | let result = AccountStat::list(&mut conn).await?; 10 | Ok(HttpResponse::Ok().json(result)) 11 | } 12 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/handler/tasks_create.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::task::create::TaskCreate; 3 | use crate::service::database::Pool; 4 | use actix_web::{post, web, HttpResponse}; 5 | use uuid::Uuid; 6 | 7 | #[post("/api/lists/{list_id}/tasks")] 8 | async fn handler( 9 | pool: web::Data, 10 | list_id: web::Path, 11 | payload: web::Json, 12 | ) -> Result { 13 | let mut conn = pool.get().await?; 14 | let created = payload.execute(&mut conn, list_id.into_inner()).await?; 15 | Ok(HttpResponse::Created().json(created)) 16 | } 17 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, HttpServer}; 2 | 3 | mod error; 4 | mod handler; 5 | mod model; 6 | mod service; 7 | 8 | fn binding() -> (String, u16) { 9 | let host = std::env::var("ADDRESS").unwrap_or_else(|_| "localhost".into()); 10 | let port = std::env::var("PORT") 11 | .ok() 12 | .and_then(|port| port.parse::().ok()) 13 | .unwrap_or(3000); 14 | (host, port) 15 | } 16 | 17 | #[actix_web::main] 18 | async fn main() -> std::io::Result<()> { 19 | let pool = web::Data::new(service::database::create_pool().await); 20 | 21 | HttpServer::new(move || { 22 | App::new() 23 | .app_data(pool.clone()) 24 | .service(crate::handler::health::handler) 25 | .service(crate::handler::accounts_create::handler) 26 | .service(crate::handler::lists_create::handler) 27 | .service(crate::handler::lists_list::handler) 28 | .service(crate::handler::tasks_create::handler) 29 | .service(crate::handler::stats::handler) 30 | }) 31 | .bind(binding())? 32 | .run() 33 | .await 34 | } 35 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/model/account/create.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use crate::service::database::{Client, Error}; 4 | 5 | #[derive(serde::Deserialize)] 6 | pub struct AccountCreate { 7 | login: String, 8 | } 9 | 10 | impl AccountCreate { 11 | pub async fn execute(&self, client: &mut Client) -> Result { 12 | let stmt = client 13 | .prepare("INSERT INTO account(login) VALUES ($1) RETURNING id, login, creation_date") 14 | .await?; 15 | let row = client.query_one(&stmt, &[&self.login]).await?; 16 | Ok(row.try_into()?) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/model/account/mod.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use std::convert::TryFrom; 3 | use uuid::Uuid; 4 | 5 | pub mod create; 6 | 7 | #[derive(serde::Deserialize, serde::Serialize)] 8 | pub struct Account { 9 | id: Uuid, 10 | login: String, 11 | creation_date: DateTime, 12 | } 13 | 14 | impl TryFrom for Account { 15 | type Error = tokio_postgres::Error; 16 | 17 | fn try_from(value: tokio_postgres::Row) -> Result { 18 | Ok(Self { 19 | id: value.try_get(0)?, 20 | login: value.try_get(1)?, 21 | creation_date: value.try_get(2)?, 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/model/list/create.rs: -------------------------------------------------------------------------------- 1 | use crate::service::database::{Client, Error}; 2 | use uuid::Uuid; 3 | 4 | #[derive(serde::Deserialize)] 5 | pub struct ListCreate { 6 | name: String, 7 | } 8 | 9 | impl ListCreate { 10 | pub async fn execute( 11 | &self, 12 | client: &mut Client, 13 | account_id: Uuid, 14 | ) -> Result { 15 | let stmt = client.prepare("INSERT INTO list(account_id, name) VALUES ($1,$2) RETURNING id, account_id, name, creation_date").await?; 16 | let row = client.query_one(&stmt, &[&account_id, &self.name]).await?; 17 | Ok(row.try_into()?) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/model/list/mod.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use uuid::Uuid; 3 | 4 | pub mod create; 5 | pub mod list; 6 | 7 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 8 | pub struct List { 9 | pub id: Uuid, 10 | pub account_id: Uuid, 11 | pub name: String, 12 | pub creation_date: DateTime, 13 | } 14 | 15 | impl TryFrom for List { 16 | type Error = tokio_postgres::Error; 17 | 18 | fn try_from(value: tokio_postgres::Row) -> Result { 19 | Ok(Self { 20 | id: value.try_get(0)?, 21 | account_id: value.try_get(1)?, 22 | name: value.try_get(2)?, 23 | creation_date: value.try_get(3)?, 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/model/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod account; 2 | pub mod list; 3 | pub mod stats; 4 | pub mod task; 5 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/model/task/create.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use crate::service::database::{Client, Error}; 4 | use uuid::Uuid; 5 | 6 | #[derive(serde::Deserialize)] 7 | pub struct TaskCreate { 8 | name: String, 9 | description: String, 10 | } 11 | 12 | impl TaskCreate { 13 | pub async fn execute(&self, client: &mut Client, list_id: Uuid) -> Result { 14 | let stmt = client.prepare("INSERT INTO task(list_id, name, description) VALUES ($1,$2,$3) RETURNING id, list_id, name, description, creation_date").await?; 15 | let row = client 16 | .query_one(&stmt, &[&list_id, &self.name, &self.description]) 17 | .await?; 18 | Ok(row.try_into()?) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/model/task/mod.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use uuid::Uuid; 3 | 4 | pub mod create; 5 | 6 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 7 | pub struct Task { 8 | pub id: Uuid, 9 | pub list_id: Uuid, 10 | pub name: String, 11 | pub description: String, 12 | pub creation_date: DateTime, 13 | } 14 | 15 | impl TryFrom for Task { 16 | type Error = tokio_postgres::Error; 17 | 18 | fn try_from(value: tokio_postgres::Row) -> Result { 19 | Ok(Self { 20 | id: value.try_get(0)?, 21 | list_id: value.try_get(1)?, 22 | name: value.try_get(2)?, 23 | description: value.try_get(3)?, 24 | creation_date: value.try_get(4)?, 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /service/rust-actix-native/src/service/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod database; 2 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/.dockerignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /service/rust-actix-sqlx/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-actix-sqlx" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | actix-web = { version = "4.0" } 10 | chrono = { version = "0.4", features = ["serde"] } 11 | num-traits = { version = "0.2.14" } 12 | serde = { version = "1.0", features = ["derive"] } 13 | serde_json = { version = "1.0" } 14 | sqlx = { version = "0.5", features = ["chrono", "decimal", "postgres", "runtime-actix-rustls", "uuid"] } 15 | uuid = { version = "0.8.2", features = ["serde", "v4"] } # version blocked by sqlx 16 | 17 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG RUST_VERSION=1.61.0 2 | FROM rust:${RUST_VERSION}-bullseye AS builder 3 | 4 | ENV USER=root 5 | 6 | WORKDIR /code 7 | RUN cargo init 8 | COPY Cargo.lock Cargo.toml /code/ 9 | RUN mkdir -p /code/.cargo 10 | RUN cargo vendor >> /code/.cargo/config.toml 11 | 12 | COPY src /code/src 13 | RUN cargo build --release --offline 14 | 15 | FROM gcr.io/distroless/cc-debian11 16 | 17 | ENV ADDRESS=0.0.0.0 18 | ENV PORT=8080 19 | 20 | COPY --from=builder /code/target/release/rust-actix-sqlx /usr/bin/rust-actix-sqlx 21 | 22 | ENTRYPOINT ["/usr/bin/rust-actix-sqlx"] 23 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/handler/accounts_create.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::account::create::AccountCreate; 3 | use crate::service::database::Pool; 4 | use actix_web::{post, web, HttpResponse}; 5 | 6 | #[post("/api/accounts")] 7 | async fn handler( 8 | pool: web::Data, 9 | payload: web::Json, 10 | ) -> Result { 11 | let mut conn = pool.acquire().await?; 12 | let created = payload.execute(&mut conn).await?; 13 | Ok(HttpResponse::Created().json(created)) 14 | } 15 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/handler/health.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{head, HttpResponse}; 2 | 3 | #[head("/healthcheck")] 4 | async fn handler() -> HttpResponse { 5 | HttpResponse::NoContent().finish() 6 | } 7 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/handler/lists_create.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::list::create::ListCreate; 3 | use crate::service::database::Pool; 4 | use actix_web::{post, web, HttpResponse}; 5 | use uuid::Uuid; 6 | 7 | #[post("/api/accounts/{account_id}/lists")] 8 | async fn handler( 9 | pool: web::Data, 10 | account_id: web::Path, 11 | payload: web::Json, 12 | ) -> Result { 13 | let mut conn = pool.acquire().await?; 14 | let created = payload.execute(&mut conn, account_id.into_inner()).await?; 15 | Ok(HttpResponse::Created().json(created)) 16 | } 17 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/handler/lists_list.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::list::list::ListList; 3 | use crate::service::database::Pool; 4 | use actix_web::{get, web, HttpResponse}; 5 | use uuid::Uuid; 6 | 7 | #[get("/api/accounts/{account_id}/lists")] 8 | async fn handler( 9 | pool: web::Data, 10 | account_id: web::Path, 11 | payload: web::Query, 12 | ) -> Result { 13 | let mut conn = pool.acquire().await?; 14 | let result = payload.execute(&mut conn, account_id.into_inner()).await?; 15 | Ok(HttpResponse::Ok().json(result)) 16 | } 17 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/handler/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod accounts_create; 2 | pub(crate) mod health; 3 | pub(crate) mod lists_create; 4 | pub(crate) mod lists_list; 5 | pub(crate) mod stats; 6 | pub(crate) mod tasks_create; 7 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/handler/stats.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::stats::AccountStat; 3 | use crate::service::database::Pool; 4 | use actix_web::{get, web, HttpResponse}; 5 | 6 | #[get("/api/stats")] 7 | async fn handler(pool: web::Data) -> Result { 8 | let mut conn = pool.acquire().await?; 9 | let result = AccountStat::list(&mut conn).await?; 10 | Ok(HttpResponse::Ok().json(result)) 11 | } 12 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/handler/tasks_create.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::task::create::TaskCreate; 3 | use crate::service::database::Pool; 4 | use actix_web::{post, web, HttpResponse}; 5 | use uuid::Uuid; 6 | 7 | #[post("/api/lists/{list_id}/tasks")] 8 | async fn handler( 9 | pool: web::Data, 10 | list_id: web::Path, 11 | payload: web::Json, 12 | ) -> Result { 13 | let mut conn = pool.acquire().await?; 14 | let created = payload.execute(&mut conn, list_id.into_inner()).await?; 15 | Ok(HttpResponse::Created().json(created)) 16 | } 17 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{web, App, HttpServer}; 2 | 3 | mod error; 4 | mod handler; 5 | mod model; 6 | mod service; 7 | 8 | fn binding() -> (String, u16) { 9 | let host = std::env::var("ADDRESS").unwrap_or_else(|_| "localhost".into()); 10 | let port = std::env::var("PORT") 11 | .ok() 12 | .and_then(|port| port.parse::().ok()) 13 | .unwrap_or(3000); 14 | (host, port) 15 | } 16 | 17 | #[actix_web::main] 18 | async fn main() -> std::io::Result<()> { 19 | let pool = web::Data::new(service::database::create_pool().await); 20 | 21 | HttpServer::new(move || { 22 | App::new() 23 | .app_data(pool.clone()) 24 | .service(crate::handler::health::handler) 25 | .service(crate::handler::accounts_create::handler) 26 | .service(crate::handler::lists_create::handler) 27 | .service(crate::handler::lists_list::handler) 28 | .service(crate::handler::tasks_create::handler) 29 | .service(crate::handler::stats::handler) 30 | }) 31 | .bind(binding())? 32 | .run() 33 | .await 34 | } 35 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/model/account/create.rs: -------------------------------------------------------------------------------- 1 | #[derive(serde::Deserialize)] 2 | #[serde(deny_unknown_fields)] 3 | pub struct AccountCreate { 4 | login: String, 5 | } 6 | 7 | impl AccountCreate { 8 | pub async fn execute<'e, E: sqlx::Executor<'e, Database = sqlx::Postgres>>( 9 | &self, 10 | executor: E, 11 | ) -> Result { 12 | sqlx::query_as("INSERT INTO account(login) VALUES ($1) RETURNING id, login, creation_date") 13 | .bind(self.login.as_str()) 14 | .fetch_one(executor) 15 | .await 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/model/account/mod.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use sqlx::postgres::PgRow; 3 | use sqlx::Row; 4 | use uuid::Uuid; 5 | 6 | pub mod create; 7 | 8 | #[derive(serde::Deserialize, serde::Serialize)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct Account { 11 | id: Uuid, 12 | login: String, 13 | creation_date: DateTime, 14 | } 15 | 16 | impl<'r> sqlx::FromRow<'r, PgRow> for Account { 17 | fn from_row(row: &'r PgRow) -> Result { 18 | Ok(Self { 19 | id: row.get(0), 20 | login: row.get(1), 21 | creation_date: row.get(2), 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/model/list/create.rs: -------------------------------------------------------------------------------- 1 | use uuid::Uuid; 2 | 3 | #[derive(serde::Deserialize)] 4 | #[serde(deny_unknown_fields)] 5 | pub struct ListCreate { 6 | name: String, 7 | } 8 | 9 | impl ListCreate { 10 | pub async fn execute<'e, E: sqlx::Executor<'e, Database = sqlx::Postgres>>( 11 | &self, 12 | executor: E, 13 | account_id: Uuid, 14 | ) -> Result { 15 | sqlx::query_as("INSERT INTO list(account_id, name) VALUES ($1,$2) RETURNING id, account_id, name, creation_date") 16 | .bind(account_id) 17 | .bind(self.name.as_str()) 18 | .fetch_one(executor) 19 | .await 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/model/list/mod.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use sqlx::postgres::PgRow; 3 | use sqlx::Row; 4 | use uuid::Uuid; 5 | 6 | pub mod create; 7 | pub mod list; 8 | 9 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 10 | #[serde(rename_all = "camelCase")] 11 | pub struct List { 12 | pub id: Uuid, 13 | pub account_id: Uuid, 14 | pub name: String, 15 | pub creation_date: DateTime, 16 | } 17 | 18 | impl<'r> sqlx::FromRow<'r, PgRow> for List { 19 | fn from_row(row: &'r PgRow) -> Result { 20 | Ok(Self { 21 | id: row.get(0), 22 | account_id: row.get(1), 23 | name: row.get(2), 24 | creation_date: row.get(3), 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/model/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod account; 2 | pub mod list; 3 | pub mod stats; 4 | pub mod task; 5 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/model/task/create.rs: -------------------------------------------------------------------------------- 1 | use uuid::Uuid; 2 | 3 | #[derive(serde::Deserialize)] 4 | #[serde(deny_unknown_fields)] 5 | pub struct TaskCreate { 6 | name: String, 7 | description: String, 8 | } 9 | 10 | impl TaskCreate { 11 | pub async fn execute<'e, E: sqlx::Executor<'e, Database = sqlx::Postgres>>( 12 | &self, 13 | executor: E, 14 | list_id: Uuid, 15 | ) -> Result { 16 | sqlx::query_as("INSERT INTO task(list_id, name, description) VALUES ($1,$2,$3) RETURNING id, list_id, name, description, creation_date") 17 | .bind(&list_id) 18 | .bind(self.name.as_str()) 19 | .bind(self.description.as_str()) 20 | .fetch_one(executor) 21 | .await 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/model/task/mod.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use sqlx::postgres::PgRow; 3 | use sqlx::Row; 4 | use uuid::Uuid; 5 | 6 | pub mod create; 7 | 8 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 9 | #[serde(rename_all = "camelCase")] 10 | pub struct Task { 11 | pub id: Uuid, 12 | pub list_id: Uuid, 13 | pub name: String, 14 | pub description: String, 15 | pub creation_date: DateTime, 16 | } 17 | 18 | impl<'r> sqlx::FromRow<'r, PgRow> for Task { 19 | fn from_row(row: &'r PgRow) -> Result { 20 | Ok(Self { 21 | id: row.get(0), 22 | list_id: row.get(1), 23 | name: row.get(2), 24 | description: row.get(3), 25 | creation_date: row.get(4), 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/service/database.rs: -------------------------------------------------------------------------------- 1 | use sqlx::postgres::PgPoolOptions; 2 | 3 | pub type Pool = sqlx::PgPool; 4 | 5 | fn database_url() -> String { 6 | std::env::var("DATABASE_URL").unwrap_or_else(|_| "postgres://localhost/postgres".into()) 7 | } 8 | 9 | fn pool_max() -> u32 { 10 | std::env::var("DATABASE_POOL_MAX") 11 | .ok() 12 | .and_then(|value| value.parse::().ok()) 13 | .unwrap_or(5) 14 | } 15 | 16 | pub async fn create_pool() -> sqlx::PgPool { 17 | let url = database_url(); 18 | PgPoolOptions::new() 19 | .max_connections(pool_max()) 20 | .connect(url.as_str()) 21 | .await 22 | .expect("unable to create postgres pool") 23 | } 24 | -------------------------------------------------------------------------------- /service/rust-actix-sqlx/src/service/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod database; 2 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /service/rust-axum-sqlx/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-axum-sqlx" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | axum = { version = "0.5" } 10 | chrono = { version = "0.4", features = ["serde"] } 11 | num-traits = { version = "0.2.14" } 12 | serde = { version = "1.0", features = ["derive"] } 13 | serde_json = { version = "1.0" } 14 | sqlx = { version = "0.5", features = ["chrono", "decimal", "postgres", "runtime-tokio-rustls", "uuid"] } 15 | tokio = { version = "1.0", features = ["full"] } 16 | uuid = { version = "0.8.2", features = ["serde", "v4"] } # version blocked by sqlx 17 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1-slim AS builder 2 | 3 | ENV USER=root 4 | 5 | WORKDIR /code 6 | RUN cargo init 7 | COPY Cargo.lock Cargo.toml /code/ 8 | RUN mkdir -p /code/.cargo 9 | RUN cargo vendor >> /code/.cargo/config.toml 10 | 11 | COPY src /code/src 12 | RUN cargo build --release --offline 13 | 14 | FROM gcr.io/distroless/cc-debian11 15 | 16 | ENV ADDRESS=0.0.0.0 17 | ENV PORT=8080 18 | 19 | COPY --from=builder /code/target/release/rust-axum-sqlx /usr/bin/rust-axum-sqlx 20 | 21 | ENTRYPOINT ["/usr/bin/rust-axum-sqlx"] 22 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | runner: 3 | image: jdrouet/eco-benchmark:runner-default 4 | build: ../../runner 5 | command: 6 | - run 7 | - "--vus" 8 | - "2" 9 | - "--duration" 10 | - "1m" 11 | - "--env" 12 | - "SERVER_HOST=service:8080" 13 | - "/config/scenario.js" 14 | depends_on: 15 | - service 16 | 17 | service: 18 | image: jdrouet/eco-benchmark:service-rust-axum-sqlx-default 19 | build: . 20 | depends_on: 21 | - database 22 | environment: 23 | - DATABASE_URL=postgres://postgres:mysecretpassword@database:5432/postgres 24 | - DATABASE_POOL_MAX=20 25 | restart: unless-stopped 26 | 27 | database: 28 | image: jdrouet/eco-benchmark:database-default 29 | build: ../../migrations 30 | environment: 31 | - POSTGRES_PASSWORD=mysecretpassword 32 | healthcheck: 33 | test: "/usr/bin/psql -U postgres postgres -c \"\\d\"" 34 | interval: 3s 35 | timeout: 1s 36 | retries: 20 37 | restart: unless-stopped 38 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/error.rs: -------------------------------------------------------------------------------- 1 | use axum::http::StatusCode; 2 | use axum::response::{IntoResponse, Response}; 3 | 4 | #[derive(Debug)] 5 | pub struct Error { 6 | code: StatusCode, 7 | message: String, 8 | } 9 | 10 | impl std::fmt::Display for Error { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | write!(f, "Error {}: {:?}", self.code.as_u16(), self.message) 13 | } 14 | } 15 | 16 | impl From for Error { 17 | fn from(err: sqlx::Error) -> Self { 18 | match err { 19 | sqlx::Error::RowNotFound => Self { 20 | code: StatusCode::NOT_FOUND, 21 | message: "Entity not found.".into(), 22 | }, 23 | _ => Self { 24 | code: StatusCode::INTERNAL_SERVER_ERROR, 25 | message: "Internal server error.".into(), 26 | }, 27 | } 28 | } 29 | } 30 | 31 | impl IntoResponse for Error { 32 | fn into_response(self) -> Response { 33 | (self.code, self.message).into_response() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/handler/accounts_create.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::account::{create::AccountCreate, Account}; 3 | use crate::service::database::Pool; 4 | use axum::extract::{Extension, Json}; 5 | use axum::http::StatusCode; 6 | 7 | pub async fn handle( 8 | Extension(pool): Extension, 9 | Json(payload): Json, 10 | ) -> Result<(StatusCode, Json), Error> { 11 | let mut conn = pool.acquire().await?; 12 | let created = payload.execute(&mut conn).await?; 13 | Ok((StatusCode::CREATED, Json(created))) 14 | } 15 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/handler/healthcheck.rs: -------------------------------------------------------------------------------- 1 | use axum::http::StatusCode; 2 | 3 | pub async fn handle() -> StatusCode { 4 | StatusCode::NO_CONTENT 5 | } -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/handler/lists_create.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::list::{create::ListCreate, List}; 3 | use crate::service::database::Pool; 4 | use axum::extract::{Extension, Path, Json}; 5 | use axum::http::StatusCode; 6 | use uuid::Uuid; 7 | 8 | pub async fn handle( 9 | Path(account_id): Path, 10 | Extension(pool): Extension, 11 | Json(payload): Json, 12 | ) -> Result<(StatusCode, Json), Error> { 13 | let mut conn = pool.acquire().await?; 14 | let created = payload.execute(&mut conn, account_id).await?; 15 | Ok((StatusCode::CREATED, Json(created))) 16 | } 17 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/handler/lists_list.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::list::list::{ListList, ListWithTasks}; 3 | use crate::service::database::Pool; 4 | use axum::extract::{Extension, Json, Path, Query}; 5 | use uuid::Uuid; 6 | 7 | pub async fn handle( 8 | Path(account_id): Path, 9 | Extension(pool): Extension, 10 | Query(payload): Query, 11 | ) -> Result>, Error> { 12 | let mut conn = pool.acquire().await?; 13 | let result = payload.execute(&mut conn, account_id).await?; 14 | Ok(Json(result)) 15 | } 16 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/handler/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::service::database::Pool; 2 | use axum::extract::Extension; 3 | use axum::routing::{get, head, post}; 4 | use axum::Router; 5 | 6 | mod accounts_create; 7 | mod healthcheck; 8 | mod lists_create; 9 | mod lists_list; 10 | mod stats; 11 | mod tasks_create; 12 | 13 | pub fn router(db: Pool) -> Router { 14 | Router::new() 15 | .route("/healthcheck", head(healthcheck::handle)) 16 | .route("/api/accounts", post(accounts_create::handle)) 17 | .route( 18 | "/api/accounts/:account_id/lists", 19 | post(lists_create::handle), 20 | ) 21 | .route("/api/accounts/:account_id/lists", get(lists_list::handle)) 22 | .route("/api/lists/:list_id/tasks", post(tasks_create::handle)) 23 | .route("/api/stats", get(stats::handle)) 24 | .layer(Extension(db)) 25 | } 26 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/handler/stats.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::stats::AccountStat; 3 | use crate::service::database::Pool; 4 | use axum::extract::{Extension, Json}; 5 | 6 | pub async fn handle(Extension(pool): Extension) -> Result>, Error> { 7 | let mut conn = pool.acquire().await?; 8 | let result = AccountStat::list(&mut conn).await?; 9 | Ok(Json(result)) 10 | } 11 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/handler/tasks_create.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::model::task::{create::TaskCreate, Task}; 3 | use crate::service::database::Pool; 4 | use axum::extract::{Extension, Json, Path}; 5 | use axum::http::StatusCode; 6 | use uuid::Uuid; 7 | 8 | pub async fn handle( 9 | Extension(pool): Extension, 10 | Path(list_id): Path, 11 | Json(payload): Json, 12 | ) -> Result<(StatusCode, Json), Error> { 13 | let mut conn = pool.acquire().await?; 14 | let created = payload.execute(&mut conn, list_id).await?; 15 | Ok((StatusCode::CREATED, Json(created))) 16 | } 17 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::net::{SocketAddr, IpAddr}; 2 | use axum::Server; 3 | 4 | mod handler; 5 | mod model; 6 | mod error; 7 | mod service; 8 | 9 | fn binding() -> SocketAddr { 10 | let host = std::env::var("ADDRESS").unwrap_or_else(|_| "127.0.0.1".into()); 11 | let host = host.parse::().unwrap(); 12 | let port = std::env::var("PORT") 13 | .ok() 14 | .and_then(|port| port.parse::().ok()) 15 | .unwrap_or(3000); 16 | SocketAddr::new(host, port) 17 | } 18 | 19 | #[tokio::main] 20 | async fn main() { 21 | let addr = binding(); 22 | let db = service::database::create_pool().await; 23 | 24 | Server::bind(&addr) 25 | .serve(handler::router(db).into_make_service()) 26 | .await 27 | .unwrap(); 28 | } 29 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/model/account/create.rs: -------------------------------------------------------------------------------- 1 | #[derive(serde::Deserialize)] 2 | pub struct AccountCreate { 3 | login: String, 4 | } 5 | 6 | impl AccountCreate { 7 | pub async fn execute<'e, E: sqlx::Executor<'e, Database = sqlx::Postgres>>( 8 | &self, 9 | executor: E, 10 | ) -> Result { 11 | sqlx::query_as("INSERT INTO account(login) VALUES ($1) RETURNING id, login, creation_date") 12 | .bind(self.login.as_str()) 13 | .fetch_one(executor) 14 | .await 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/model/account/mod.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use sqlx::postgres::PgRow; 3 | use sqlx::Row; 4 | use uuid::Uuid; 5 | 6 | pub mod create; 7 | 8 | #[derive(serde::Deserialize, serde::Serialize)] 9 | pub struct Account { 10 | id: Uuid, 11 | login: String, 12 | creation_date: DateTime, 13 | } 14 | 15 | impl<'r> sqlx::FromRow<'r, PgRow> for Account { 16 | fn from_row(row: &'r PgRow) -> Result { 17 | Ok(Self { 18 | id: row.get(0), 19 | login: row.get(1), 20 | creation_date: row.get(2), 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/model/list/create.rs: -------------------------------------------------------------------------------- 1 | use uuid::Uuid; 2 | 3 | #[derive(serde::Deserialize)] 4 | pub struct ListCreate { 5 | name: String, 6 | } 7 | 8 | impl ListCreate { 9 | pub async fn execute<'e, E: sqlx::Executor<'e, Database = sqlx::Postgres>>( 10 | &self, 11 | executor: E, 12 | account_id: Uuid, 13 | ) -> Result { 14 | sqlx::query_as("INSERT INTO list(account_id, name) VALUES ($1,$2) RETURNING id, account_id, name, creation_date") 15 | .bind(account_id) 16 | .bind(self.name.as_str()) 17 | .fetch_one(executor) 18 | .await 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/model/list/mod.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use sqlx::postgres::PgRow; 3 | use sqlx::Row; 4 | use uuid::Uuid; 5 | 6 | pub mod create; 7 | pub mod list; 8 | 9 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 10 | pub struct List { 11 | pub id: Uuid, 12 | pub account_id: Uuid, 13 | pub name: String, 14 | pub creation_date: DateTime, 15 | } 16 | 17 | impl<'r> sqlx::FromRow<'r, PgRow> for List { 18 | fn from_row(row: &'r PgRow) -> Result { 19 | Ok(Self { 20 | id: row.get(0), 21 | account_id: row.get(1), 22 | name: row.get(2), 23 | creation_date: row.get(3), 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/model/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod account; 2 | pub mod list; 3 | pub mod stats; 4 | pub mod task; 5 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/model/task/create.rs: -------------------------------------------------------------------------------- 1 | use uuid::Uuid; 2 | 3 | #[derive(serde::Deserialize)] 4 | pub struct TaskCreate { 5 | name: String, 6 | description: String, 7 | } 8 | 9 | impl TaskCreate { 10 | pub async fn execute<'e, E: sqlx::Executor<'e, Database = sqlx::Postgres>>( 11 | &self, 12 | executor: E, 13 | list_id: Uuid, 14 | ) -> Result { 15 | sqlx::query_as("INSERT INTO task(list_id, name, description) VALUES ($1,$2,$3) RETURNING id, list_id, name, description, creation_date") 16 | .bind(&list_id) 17 | .bind(self.name.as_str()) 18 | .bind(self.description.as_str()) 19 | .fetch_one(executor) 20 | .await 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/model/task/mod.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use sqlx::postgres::PgRow; 3 | use sqlx::Row; 4 | use uuid::Uuid; 5 | 6 | pub mod create; 7 | 8 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 9 | pub struct Task { 10 | pub id: Uuid, 11 | pub list_id: Uuid, 12 | pub name: String, 13 | pub description: String, 14 | pub creation_date: DateTime, 15 | } 16 | 17 | impl<'r> sqlx::FromRow<'r, PgRow> for Task { 18 | fn from_row(row: &'r PgRow) -> Result { 19 | Ok(Self { 20 | id: row.get(0), 21 | list_id: row.get(1), 22 | name: row.get(2), 23 | description: row.get(3), 24 | creation_date: row.get(4), 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/service/database.rs: -------------------------------------------------------------------------------- 1 | use sqlx::postgres::PgPoolOptions; 2 | 3 | pub type Pool = sqlx::PgPool; 4 | 5 | fn database_url() -> String { 6 | std::env::var("DATABASE_URL").unwrap_or_else(|_| "postgres://localhost/postgres".into()) 7 | } 8 | 9 | fn pool_max() -> u32 { 10 | std::env::var("DATABASE_POOL_MAX") 11 | .ok() 12 | .and_then(|value| value.parse::().ok()) 13 | .unwrap_or(5) 14 | } 15 | 16 | pub async fn create_pool() -> sqlx::PgPool { 17 | let url = database_url(); 18 | PgPoolOptions::new() 19 | .max_connections(pool_max()) 20 | .connect(url.as_str()) 21 | .await 22 | .expect("unable to create postgres pool") 23 | } 24 | -------------------------------------------------------------------------------- /service/rust-axum-sqlx/src/service/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod database; 2 | -------------------------------------------------------------------------------- /service/specs.md: -------------------------------------------------------------------------------- 1 | # Specifications of the service under tests 2 | 3 | Each subfolder corresponding to an API implementation should conform to the following specifications about the data model & the API endpoints to be included in the benchmark. 4 | 5 | ## Model 6 | 7 | 8 | Entity Diagram created using [Mermaid](https://mermaid-js.github.io/mermaid/): 9 | 10 | ```mermaid 11 | erDiagram 12 | ACCOUNT ||--|{ LIST : has 13 | LIST ||--|{ TASK : "contains" 14 | ``` 15 | 16 | Note: You can have a look on the [SQL initialization script](../migrations/20220408080000_init.sql) for more details. 17 | 18 | ## API spec 19 | 20 | Common characteristics: 21 | 22 | - [x] Default error handler 23 | - [x] Data validation 24 | 25 | An OpenAPI 3.0.0 specification is available in YAML in this repo [EcobenchmarkBackEnd-0.0.1-swagger.yaml](./EcobenchmarkBackEnd-0.0.1-swagger.yaml) or online on [SwaggerHub](https://app.swaggerhub.com/apis/agraignic/EcobenchmarkBackEnd/0.0.1). 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | ## Test Script 2 | 3 | 4 | ### Test Script to check the good behavior of each service implementation. 5 | 6 | Prequisites : 7 | - Java version 11 or above is required. 8 | 9 | From the test folder, launch like this : 10 | 11 | `wget https://github.com/karatelabs/karate/releases/download/v1.4.1/karate-1.4.1.jar` 12 | `java -jar karate-1.4.1.jar check-service-conformity.feature ` 13 | 14 | ### More way to launch test 15 | 16 | Get help here on how to launch the test : https://github.com/karatelabs/karate/wiki/Get-Started 17 | 18 | You have different flavour to launch test. 19 | 20 | You can use a plugin on your favorite IDE in order to launch test locally for dev purpose : 21 | 22 | - https://github.com/karatelabs/karate/wiki/Get-Started:-Visual-Studio-Code (payed subscription needed) 23 | - https://github.com/karatelabs/karate/wiki/Get-Started:-JetBrains-IntelliJ (payed subscription needed) 24 | 25 | Or use command line style for CI integration : 26 | 27 | - https://github.com/karatelabs/karate/wiki/Get-Started:-Maven-and-Gradle 28 | - https://github.com/karatelabs/karate/wiki/Get-Started:-Other-Runtime-Options -------------------------------------------------------------------------------- /validator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM grafana/k6 2 | 3 | COPY scenario.js /config/scenario.js 4 | --------------------------------------------------------------------------------