Merge pull request #231 from aguibert/player-service-jdbc

Add real database to player-service
This commit is contained in:
Andrew Guibert 2019-10-03 13:28:31 -05:00 committed by GitHub
commit 86fd314fc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 381 additions and 66 deletions

View File

@ -14,7 +14,13 @@ Builds all microservice applications and deploys them to locally running liberty
./gradlew start frontend:open
```
Any code changes that are made in an eclipse environment with auto-build enabled will automatically publish content to the loose application, meaning no server restarts should be required between code changes.
Any code changes that are made in an IDE with auto-build enabled will automatically publish content to the loose application, meaning no server restarts should be required between code changes.
By default, the player-service stores player registration and stats in-memory. To use a real database, you can start a PostgreSQL docker container with this script:
```
./startDB.sh
```
To start the monitoring services, you must have Docker installed. They can be started with:

View File

@ -1,4 +1,4 @@
FROM open-liberty:microProfile3-java11
FROM open-liberty:19.0.0.9-microProfile3-java11
ADD --chown=1001:0 build/libs/auth-service.war /config/dropins
COPY --chown=1001:0 src/main/liberty/config /config/
RUN printf 'frontend_url=http://lb-frontend:12000/login\n\

View File

@ -70,7 +70,6 @@ subprojects {
providedCompile group: 'jakarta.platform', name: 'jakarta.jakartaee-api', version: '8.0.0'
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.eclipse', name: 'yasson', version: '1.0.5'
//testCompile group: 'org.glassfish', name: 'jakarta.json', version: '1.1.5'
libertyRuntime group: 'io.openliberty', name: 'openliberty-runtime', version: '19.0.0.9'
}
@ -79,6 +78,8 @@ subprojects {
defaultOutputDir = file('build/classes/java/main')
file {
whenMerged {
entries.findAll { it.path.startsWith('src/main') }
.each { it.output = "build/classes/java/main" }
entries.findAll { it.path.startsWith('src/test') }
.each { it.output = "build/classes/java/test" }
}

View File

@ -26,6 +26,16 @@ services:
image: libertybikes-player
ports:
- "8081:8081"
environment:
- DB_HOST=postgres
postgres:
image: postgres:11-alpine
ports:
- 5432:5432
environment:
- POSTGRES_DB=playerdb
- POSTGRES_USER=lb_user
- POSTGRES_PASSWORD=lb_password
prometheus:
image: prom/prometheus:v2.4.0
ports:

View File

@ -1,4 +1,4 @@
FROM open-liberty:microProfile3-java11
FROM open-liberty:19.0.0.9-microProfile3-java11
ADD --chown=1001:0 build/libs/frontend.war /config/apps
COPY --chown=1001:0 src/main/liberty/config /config/
RUN printf 'httpPort=12000\n\

View File

@ -1,4 +1,4 @@
FROM open-liberty:microProfile3-java11
FROM open-liberty:19.0.0.9-microProfile3-java11
ADD --chown=1001:0 build/libs/game-service.war /config/dropins
COPY --chown=1001:0 src/main/liberty/config /config/
RUN printf 'httpPort=8080\n\

View File

@ -96,14 +96,12 @@ public class GameMetrics {
public static void counterInc(Metadata metricMetadata) {
if (registry != null || (getRegistry() != null)) {
registry.concurrentGauge(metricMetadata).inc();
//registry.counter(metricMetadata).inc();
}
}
public static void counterDec(Metadata metricMetadata) {
if (registry != null || (getRegistry() != null)) {
registry.concurrentGauge(metricMetadata).dec();
//registry.counter(metricMetadata).dec();
}
}

View File

@ -1,5 +1,6 @@
FROM open-liberty:microProfile3-java11
FROM open-liberty:19.0.0.9-microProfile3-java11
ADD --chown=1001:0 build/libs/player-service.war /config/dropins
ADD --chown=1001:0 build/libs/postgresql-*.jar /config/postgresql
COPY --chown=1001:0 src/main/liberty/config /config/
RUN printf 'httpPort=8081\n\
httpsPort=8444' > /config/bootstrap.properties

View File

@ -13,9 +13,28 @@ liberty {
}
}
configurations {
postgresql
}
dependencies {
compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
postgresql group: 'org.postgresql', name: 'postgresql', version: '42.2.8'
}
task addPostgresqlToServer(type: Copy) {
shouldRunAfter libertyCreate
from configurations.postgresql
into "${rootProject.buildDir}/wlp/usr/servers/${liberty.server.name}/postgresql"
}
task addPostgresqlToBuild(type: Copy) {
from configurations.postgresql
into "${buildDir}/libs"
}
assemble.dependsOn 'addPostgresqlToBuild'
installApps.dependsOn 'addPostgresqlToServer'
// The installLiberty task doesn't work when run in parallel with other installLiberty tasks
installLiberty.mustRunAfter ':frontend:installLiberty'

View File

@ -0,0 +1,53 @@
package org.libertybikes.player.data;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.libertybikes.player.service.Player;
public class InMemPlayerDB implements PlayerDB {
private final Map<String, Player> allPlayers = new HashMap<>();
/**
* Inserts a new player into the database.
*
* @return Returns true if the player was created. False if a player with the same ID already existed
*/
@Override
public boolean create(Player p) {
return allPlayers.putIfAbsent(p.id, p) == null;
}
@Override
public void update(Player p) {
allPlayers.put(p.id, p);
}
@Override
public Player get(String id) {
return allPlayers.get(id);
}
@Override
public Collection<Player> getAll() {
return allPlayers.values();
}
@Override
public Collection<Player> topPlayers(int numPlayers) {
return allPlayers.values()
.stream()
.sorted(Player::compareOverall)
.limit(numPlayers)
.collect(Collectors.toList());
}
@Override
public boolean exists(String id) {
return allPlayers.containsKey(id);
}
}

View File

@ -0,0 +1,179 @@
package org.libertybikes.player.data;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.libertybikes.player.service.Player;
import org.libertybikes.player.service.PlayerStats;
public class PersistentPlayerDB implements PlayerDB {
private static final String TABLE_NAME = "PLAYERS";
private static final String COL_ID = "id";
private static final String COL_NAME = "name";
private static final String COL_NUM_GAMES = "totalGames";
private static final String COL_NUM_WINS = "totalWins";
private static final String COL_RATING = "rating";
private final DataSource ds;
public PersistentPlayerDB() throws NamingException {
ds = InitialContext.doLookup("java:comp/DefaultDataSource");
}
public boolean isAvailable() {
try (Connection con = ds.getConnection()) {
String CREATE_TABLE = new StringBuilder("CREATE TABLE IF NOT EXISTS ")
.append(TABLE_NAME)
.append(" (")
.append(COL_ID)
.append(" varchar(30) not null, ")
.append(COL_NAME)
.append(" varchar(20) not null, ")
.append(COL_NUM_GAMES)
.append(" integer, ")
.append(COL_NUM_WINS)
.append(" integer, ")
.append(COL_RATING)
.append(" integer, ")
.append("PRIMARY KEY (")
.append(COL_ID)
.append(") )")
.toString();
System.out.println("Creating table with SQL:");
System.out.println(CREATE_TABLE);
con.createStatement().execute(CREATE_TABLE);
return true;
} catch (SQLException notConfigured) {
System.err.println("Unable to initialize database because of: " + notConfigured.getMessage());
return false;
}
}
/**
* Inserts a new player into the database.
*
* @return Returns true if the player was created. False if a player with the same ID already existed
*/
@Override
public boolean create(Player p) {
if (exists(p.id))
return false;
try (Connection con = ds.getConnection()) {
PreparedStatement ps = con.prepareStatement("INSERT INTO " + TABLE_NAME + " VALUES (?,?,?,?,?)");
ps.setString(1, p.id);
ps.setString(2, p.name);
ps.setInt(3, p.stats.totalGames);
ps.setInt(4, p.stats.numWins);
ps.setInt(5, p.stats.rating);
return ps.executeUpdate() == 1;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
@Override
public void update(Player p) {
try (Connection con = ds.getConnection()) {
PreparedStatement ps = con.prepareStatement("UPDATE " + TABLE_NAME +
" SET " +
COL_NAME + " = ?, " +
COL_NUM_GAMES + " = ?, " +
COL_NUM_WINS + " = ?, " +
COL_RATING + " = ? " +
" WHERE " + COL_ID + " = ?");
ps.setString(1, p.name);
ps.setInt(2, p.stats.totalGames);
ps.setInt(3, p.stats.numWins);
ps.setInt(4, p.stats.rating);
ps.setString(5, p.id);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public Player get(String id) {
try (Connection con = ds.getConnection()) {
PreparedStatement ps = con.prepareStatement("SELECT * FROM " + TABLE_NAME + " WHERE " + COL_ID + " = ?");
ps.setString(1, id);
ResultSet rs = ps.executeQuery();
return rs.next() ? inflate(rs) : null;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
@Override
public Collection<Player> getAll() {
Set<Player> allPlayers = new HashSet<>();
try (Connection con = ds.getConnection()) {
PreparedStatement ps = con.prepareStatement("SELECT * FROM " + TABLE_NAME);
ResultSet rs = ps.executeQuery();
while (rs.next())
allPlayers.add(inflate(rs));
} catch (SQLException e) {
e.printStackTrace();
return Collections.emptySet();
}
return allPlayers;
}
@Override
public Collection<Player> topPlayers(int numPlayers) {
List<Player> allPlayers = new ArrayList<>();
try (Connection con = ds.getConnection()) {
PreparedStatement ps = con.prepareStatement("SELECT * FROM " + TABLE_NAME +
" ORDER BY " + COL_RATING + " DESC, " + COL_NUM_WINS + " DESC" +
" LIMIT ?");
ps.setInt(1, numPlayers);
ResultSet rs = ps.executeQuery();
while (rs.next())
allPlayers.add(inflate(rs));
} catch (SQLException e) {
e.printStackTrace();
return Collections.emptySet();
}
return allPlayers;
}
@Override
public boolean exists(String id) {
try (Connection con = ds.getConnection()) {
PreparedStatement ps = con.prepareStatement("SELECT EXISTS (" +
"SELECT 1 FROM " + TABLE_NAME + " WHERE " + COL_ID + " = ? )");
ps.setString(1, id);
ResultSet rs = ps.executeQuery();
return rs.next() && rs.getBoolean(1);
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
private static Player inflate(ResultSet rs) throws SQLException {
PlayerStats stats = new PlayerStats();
stats.totalGames = rs.getInt(COL_NUM_GAMES);
stats.numWins = rs.getInt(COL_NUM_WINS);
stats.rating = rs.getInt(COL_RATING);
Player p = new Player(rs.getString(COL_NAME), rs.getString(COL_ID), stats);
return p;
}
}

View File

@ -1,63 +1,29 @@
/**
*
*/
package org.libertybikes.player.data;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import javax.enterprise.context.ApplicationScoped;
import org.libertybikes.player.service.Player;
@ApplicationScoped
public class PlayerDB {
// TODO back this by a DB instead of in-mem
private final Map<String, Player> allPlayers = new HashMap<>();
public interface PlayerDB {
/**
* Inserts a new player into the database.
*
* @return Returns true if the player was created. False if a player with the same ID already existed
*/
public boolean create(Player p) {
return allPlayers.putIfAbsent(p.id, p) == null;
}
public boolean create(Player p);
public void update(Player p) {
allPlayers.put(p.id, p);
}
public void update(Player p);
public Player get(String id) {
return allPlayers.get(id);
}
public Player get(String id);
public Collection<Player> getAll() {
return allPlayers.values();
}
public Collection<Player> getAll();
public Collection<Player> topPlayers(int numPlayers) {
return allPlayers.values()
.stream()
.sorted(Player::compareOverall)
.limit(numPlayers)
.collect(Collectors.toList());
}
public Collection<Player> topPlayers(int numPlayers);
public long getRank(String id) {
Player p = get(id);
if (p == null)
return -1;
int wins = p.stats.numWins;
long numPlayersAhead = allPlayers.values()
.stream()
.filter(otherPlayer -> otherPlayer.stats.numWins > wins)
.count();
return numPlayersAhead + 1;
}
public boolean exists(String id) {
return allPlayers.containsKey(id);
}
public boolean exists(String id);
}

View File

@ -0,0 +1,28 @@
/**
*
*/
package org.libertybikes.player.data;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
@ApplicationScoped
public class PlayerDBProducer {
@Produces
@ApplicationScoped
public PlayerDB createDB() {
try {
PersistentPlayerDB db = new PersistentPlayerDB();
if (db.isAvailable()) {
System.out.println("Using persistent player DB.");
return db;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Unable to create persistent player DB. Falling back to in-memory storage");
return new InMemPlayerDB();
}
}

View File

@ -26,7 +26,7 @@ public class Player {
public final String name;
public final PlayerStats stats = new PlayerStats();
public final PlayerStats stats;
public Player(String name) {
this(name, null);
@ -35,8 +35,13 @@ public class Player {
@JsonbCreator
public Player(@JsonbProperty("name") String name,
@JsonbProperty("id") String id) {
this.id = (id == null || id.equals("null")) ? DOMAIN.BASIC + name : id;
this(name, id, new PlayerStats());
}
public Player(String name, String id, PlayerStats stats) {
this.id = (id == null || id.equals("null")) ? createDefaultId(name) : id;
this.name = name;
this.stats = stats;
}
public static int compareByWins(Player a, Player b) {
@ -61,6 +66,10 @@ public class Player {
return compareByWinRatio(a, b);
}
public static String createDefaultId(String name) {
return DOMAIN.BASIC + name;
}
@JsonbTransient
public DOMAIN getDomain() {
for (DOMAIN d : DOMAIN.values()) {

View File

@ -35,6 +35,11 @@ public class RankingService {
@PostConstruct
public void initPlayers() {
if (db.exists(Player.createDefaultId("SamplePlayer-0"))) {
System.out.println("Sample players already exist in database.");
return;
}
Random r = new Random();
for (int i = 0; i < 10; i++) {
String id = playerSvc.createPlayer("SamplePlayer-" + i, null);
@ -55,13 +60,6 @@ public class RankingService {
return db.topPlayers(numPlayers);
}
@GET
@Path("/{playerId}")
@Produces(MediaType.APPLICATION_JSON)
public long getRank(@PathParam("playerId") String id) {
return db.getRank(id);
}
@POST
@RolesAllowed({ "admin" })
@Path("/{playerId}")

View File

@ -4,15 +4,39 @@
<feature>cdi-2.0</feature>
<feature>concurrent-1.0</feature>
<feature>jaxrs-2.1</feature>
<feature>jdbc-4.2</feature>
<feature>jndi-1.0</feature>
<feature>jsonb-1.0</feature>
<feature>mpConfig-1.3</feature>
<feature>mpJwt-1.1</feature>
<feature>mpOpenAPI-1.1</feature>
<feature>mpMetrics-2.0</feature>
<feature>restConnector-2.0</feature>
</featureManager>
<mpMetrics authentication="false"/>
<quickStartSecurity userName="admin" userPassword="admin"/>
<!-- Auth filter so MP JWT is only used for the app and not built-in server API -->
<authFilter id="appFilter">
<requestUrl urlPattern="/ibm/api" matchType="notContain"/>
</authFilter>
<variable name="DB_HOST" defaultValue="localhost"/>
<variable name="DB_PORT" defaultValue="5432"/>
<variable name="DB_NAME" defaultValue="playerdb"/>
<variable name="DB_USER" defaultValue="lb_user"/>
<variable name="DB_PASS" defaultValue="lb_password"/>
<library id="postgresql">
<fileset dir="postgresql"/>
</library>
<dataSource id="DefaultDataSource">
<jdbcDriver libraryRef="postgresql"/>
<properties.postgresql serverName="${DB_HOST}" portNumber="${DB_PORT}" databaseName="${DB_NAME}"
user="${DB_USER}" password="${DB_PASS}"/>
</dataSource>
<applicationManager autoExpand="true"/>
@ -27,12 +51,14 @@
<jndiEntry jndiName="jwtKeyStore" value="${server.config.dir}resources/security/validationKeystore.jks"/>
<mpJwt id="myMpJwt"
<mpJwt id="myMpJwt"
keyName="rebike"
issuer="https://libertybikes.mybluemix.net"
audiences="client"/>
audiences="client"
authFilterRef="appFilter"/>
<httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="${httpPort}" httpsPort="${httpsPort}" />
<applicationManager autoExpand="true"/>
</server>

View File

@ -5,7 +5,7 @@ import static org.junit.Assert.assertNotNull;
import org.junit.Before;
import org.junit.Test;
import org.libertybikes.player.data.PlayerDB;
import org.libertybikes.player.data.InMemPlayerDB;
public class PlayerServiceTest {
@ -14,7 +14,7 @@ public class PlayerServiceTest {
@Before
public void beforeEach() {
svc = new PlayerService();
svc.db = new PlayerDB();
svc.db = new InMemPlayerDB();
}
@Test

View File

@ -6,6 +6,7 @@ import static org.libertybikes.player.service.RankingService.ratingChange;
import org.junit.Before;
import org.junit.Test;
import org.libertybikes.player.data.InMemPlayerDB;
import org.libertybikes.player.data.PlayerDB;
public class RankingServiceTest {
@ -18,7 +19,7 @@ public class RankingServiceTest {
@Before
public void beforeEach() {
PlayerDB db = new PlayerDB();
PlayerDB db = new InMemPlayerDB();
players = new PlayerService();
players.db = db;
ranks = new RankingService();

20
startDB.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
echo "Starting postgresql database"
docker stop lb-postgresql 2> /dev/null
docker run \
--name lb-postgresql \
--rm \
-d \
-p 5432:5432 \
-e POSTGRES_DB=playerdb \
-e POSTGRES_USER=lb_user \
-e POSTGRES_PASSWORD=lb_password \
postgres:11-alpine
echo "########################################################"
echo "PostgreSQL database for player-service has been started"
echo "To test Liberty's connection to PostgreSQL, ping the URL:"
echo " https://localhost:8444/ibm/api/validation/dataSource/DefaultDataSource"
echo "Using username/password credentials of admin/admin"
echo "########################################################"