Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the coroutine example with vertx-mysql-client #380

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions kotlin-examples/coroutines/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
# Vert.x Kotlin Coroutines Example

A movie getRating REST application written in [Kotlin](https://kotlinlang.org/) to demonstrate how it can Kotlin
coroutines can be used with Vert.x.
A movie rating REST application written in [Kotlin](https://kotlinlang.org/) to demonstrate how Kotlin coroutines can be used with Vert.x.

Coroutines enables to write asynchronous code with sequential statements, the function `rateMovie` is the best example
showing two SQL client operations wrapped in a `use` block managing the SQL connection.
Kotlin Coroutines enables to write asynchronous code with sequential statements, the function `rateMovie` is the best example
showing two asynchronous SQL client operations could be executed sequentially just like writing synchronous code.

## Setup the database

Setup a MySQL database server with Docker

```
docker run -it --rm=true --name vertx-coroutine-example-test -e MYSQL_USER=vertx -e MYSQL_PASSWORD=vertx -e MYSQL_ROOT_PASSWORD=my-secret-pw -e MYSQL_DATABASE=vertx_example -p 3306:3306 mysql:8
```

## Running from the IDE

Expand All @@ -19,10 +26,7 @@ Run the main function from the IDE

## Running from the CLI

Copy hsqldb jar to $VERTX_HOME/lib

```
> cp $HOME/.m2/repository/org/hsqldb/hsqldb/2.4.0/hsqldb-2.4.0.jar $VERTX_HOME/lib/
> vertx run src/main/kotlin/movierating/App.kt
```

Expand All @@ -37,14 +41,14 @@ You can know more about a movie
{"id":"starwars","title":"Star Wars"}
```

You can get the current getRating of a movie:
You can get the current rating of a movie:

```
> curl http://localhost:8080/getRating/indianajones
{"id":"indianajones","getRating":5}
```

Finally you can rateMovie a movie
Finally you can rate a movie

```
> curl -X POST http://localhost:8080/rateMovie/starwars?getRating=4
Expand Down
7 changes: 1 addition & 6 deletions kotlin-examples/coroutines/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,9 @@
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-jdbc-client</artifactId>
<artifactId>vertx-mysql-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.4</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
Expand Down
97 changes: 49 additions & 48 deletions kotlin-examples/coroutines/src/main/kotlin/movierating/App.kt
Original file line number Diff line number Diff line change
@@ -1,54 +1,54 @@
package movierating

import io.vertx.ext.jdbc.JDBCClient
import io.vertx.ext.web.Route
import io.vertx.ext.web.Router
import io.vertx.ext.web.RoutingContext
import io.vertx.kotlin.core.http.listenAwait
import io.vertx.kotlin.core.json.array
import io.vertx.kotlin.core.json.get
import io.vertx.kotlin.core.json.json
import io.vertx.kotlin.core.json.obj
import io.vertx.kotlin.coroutines.CoroutineVerticle
import io.vertx.kotlin.coroutines.dispatcher
import io.vertx.kotlin.ext.sql.executeAwait
import io.vertx.kotlin.ext.sql.getConnectionAwait
import io.vertx.kotlin.ext.sql.queryWithParamsAwait
import io.vertx.kotlin.ext.sql.updateWithParamsAwait
import io.vertx.kotlin.sqlclient.executeAwait
import io.vertx.mysqlclient.MySQLConnectOptions
import io.vertx.mysqlclient.MySQLPool
import io.vertx.sqlclient.*
import kotlinx.coroutines.launch


class App : CoroutineVerticle() {

private lateinit var client: JDBCClient
private lateinit var client: MySQLPool
private lateinit var getTitleByMovieId : PreparedQuery<RowSet<Row>>
private lateinit var insertRatingWithMovieId : PreparedQuery<RowSet<Row>>
private lateinit var getAverageRatingByMovieId : PreparedQuery<RowSet<Row>>

override suspend fun start() {

client = JDBCClient.createShared(vertx, json {
obj(
"url" to "jdbc:hsqldb:mem:test?shutdown=true",
"driver_class" to "org.hsqldb.jdbcDriver",
"max_pool_size-loop" to 30
)
})
client = MySQLPool.pool(vertx, MySQLConnectOptions.fromUri("mysql://vertx:vertx@localhost:3306/vertx_example")
.setCachePreparedStatements(true)
.setPreparedStatementCacheMaxSize(1024), PoolOptions().setMaxSize(50))

// Populate database
val statements = listOf(
"CREATE TABLE MOVIE (ID VARCHAR(16) PRIMARY KEY, TITLE VARCHAR(256) NOT NULL)",
"CREATE TABLE RATING (ID INTEGER IDENTITY PRIMARY KEY, value INTEGER, MOVIE_ID VARCHAR(16))",
"INSERT INTO MOVIE (ID, TITLE) VALUES 'starwars', 'Star Wars'",
"INSERT INTO MOVIE (ID, TITLE) VALUES 'indianajones', 'Indiana Jones'",
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 1, 'starwars'",
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 5, 'starwars'",
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 9, 'starwars'",
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 10, 'starwars'",
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 4, 'indianajones'",
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 7, 'indianajones'",
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 3, 'indianajones'",
"INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 9, 'indianajones'"
)
client.getConnectionAwait()
.use { connection -> statements.forEach { connection.executeAwait(it) } }
client.query("""
DROP TABLE IF EXISTS movie;
DROP TABLE IF EXISTS rating;
CREATE TABLE movie (id VARCHAR(16) PRIMARY KEY, title VARCHAR(256) NOT NULL);
CREATE TABLE rating (id INTEGER AUTO_INCREMENT PRIMARY KEY, `value` INTEGER, movie_id VARCHAR(16));
INSERT INTO movie (id, title) VALUES ('starwars', 'Star Wars');
INSERT INTO movie (id, title) VALUES ('indianajones', 'Indiana Jones');
INSERT INTO rating (`value`, movie_id) VALUES (1, 'starwars');
INSERT INTO rating (`value`, movie_id) VALUES (5, 'starwars');
INSERT INTO rating (`value`, movie_id) VALUES (9, 'starwars');
INSERT INTO rating (`value`, movie_id) VALUES (10, 'starwars');
INSERT INTO rating (`value`, movie_id) VALUES (4, 'indianajones');
INSERT INTO rating (`value`, movie_id) VALUES (7, 'indianajones');
INSERT INTO rating (`value`, movie_id) VALUES (3, 'indianajones');
INSERT INTO rating (`value`, movie_id) VALUES (9, 'indianajones');
""".trimIndent()).executeAwait()

getTitleByMovieId = client.preparedQuery("SELECT title FROM movie WHERE id = ?")
insertRatingWithMovieId = client.preparedQuery("INSERT INTO rating (`value`, movie_id) VALUES (?, ?)")
getAverageRatingByMovieId = client.preparedQuery("SELECT AVG(`value`) AS avg_rating FROM rating WHERE movie_id = ? ")

// Build Vert.x Web router
val router = Router.router(vertx)
Expand All @@ -64,38 +64,39 @@ class App : CoroutineVerticle() {

// Send info about a movie
suspend fun getMovie(ctx: RoutingContext) {
val id = ctx.pathParam("id")
val result = client.queryWithParamsAwait("SELECT TITLE FROM MOVIE WHERE ID=?", json { array(id) })
if (result.rows.size == 1) {
val movieId = ctx.pathParam("id")

val rowSet = getTitleByMovieId.executeAwait(Tuple.of(movieId))
if (rowSet.size() == 1) {
val row = rowSet.iterator().next()
ctx.response().end(json {
obj("id" to id, "title" to result.rows[0]["TITLE"]).encode()
obj("id" to movieId, "title" to row.getString("title")).encode()
})
} else {
} else{
ctx.response().setStatusCode(404).end()
}
}

// Rate a movie
suspend fun rateMovie(ctx: RoutingContext) {
val movie = ctx.pathParam("id")
val movieId = ctx.pathParam("id")
val rating = Integer.parseInt(ctx.queryParam("getRating")[0])
client.getConnectionAwait().use { connection ->
val result = connection.queryWithParamsAwait("SELECT TITLE FROM MOVIE WHERE ID=?", json { array(movie) })
if (result.rows.size == 1) {
connection.updateWithParamsAwait("INSERT INTO RATING (VALUE, MOVIE_ID) VALUES ?, ?", json { array(rating, movie) })
ctx.response().setStatusCode(200).end()
} else {
ctx.response().setStatusCode(404).end()
}
val titleRowSet = getTitleByMovieId.executeAwait(Tuple.of(movieId))
if (titleRowSet.size() == 1) {
insertRatingWithMovieId.executeAwait(Tuple.of(rating, movieId))
ctx.response().setStatusCode(200).end()
} else {
ctx.response().setStatusCode(404).end()
}
}

// Get the current rating of a movie
suspend fun getRating(ctx: RoutingContext) {
val id = ctx.pathParam("id")
val result = client.queryWithParamsAwait("SELECT AVG(VALUE) AS VALUE FROM RATING WHERE MOVIE_ID=?", json { array(id) })
val movieId = ctx.pathParam("id")
val result = getAverageRatingByMovieId.executeAwait(Tuple.of(movieId))
val averageRating = result.iterator().next().getInteger("avg_rating")
ctx.response().end(json {
obj("id" to id, "getRating" to result.rows[0]["VALUE"]).encode()
obj("id" to movieId, "getRating" to averageRating).encode()
})
}

Expand Down