{"id":13756805,"url":"https://github.com/OpenLiberty/liberty-bikes","last_synced_at":"2025-05-10T04:30:59.541Z","repository":{"id":39577582,"uuid":"113689042","full_name":"OpenLiberty/liberty-bikes","owner":"OpenLiberty","description":"Real-time web based multiplayer game running on OpenLiberty","archived":false,"fork":false,"pushed_at":"2025-01-10T16:45:01.000Z","size":4765,"stargazers_count":36,"open_issues_count":38,"forks_count":38,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-09T13:11:22.733Z","etag":null,"topics":["game","hacktoberfest","jakartaee","microprofile","microservices","microservices-demo","open-liberty"],"latest_commit_sha":null,"homepage":"http://libertybikes.mybluemix.net","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/OpenLiberty.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-12-09T17:47:38.000Z","updated_at":"2025-02-11T21:57:45.000Z","dependencies_parsed_at":"2024-05-21T11:38:47.412Z","dependency_job_id":"eab4e004-004e-4d08-a8ea-3a380f93e31f","html_url":"https://github.com/OpenLiberty/liberty-bikes","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenLiberty%2Fliberty-bikes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenLiberty%2Fliberty-bikes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenLiberty%2Fliberty-bikes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenLiberty%2Fliberty-bikes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/OpenLiberty","download_url":"https://codeload.github.com/OpenLiberty/liberty-bikes/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253365227,"owners_count":21897180,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["game","hacktoberfest","jakartaee","microprofile","microservices","microservices-demo","open-liberty"],"created_at":"2024-08-03T11:00:54.332Z","updated_at":"2025-05-10T04:30:58.178Z","avatar_url":"https://github.com/OpenLiberty.png","language":"Java","funding_links":[],"categories":["More/Unorganised"],"sub_categories":["Sock Shop"],"readme":"# Liberty Bikes\n[![Build Status](https://travis-ci.org/OpenLiberty/liberty-bikes.svg?branch=master)](https://travis-ci.org/OpenLiberty/liberty-bikes)\n\n\n![Image of Liberty Bikes game](https://user-images.githubusercontent.com/1577201/47185063-0d307c00-d2f2-11e8-87f5-997ecf22c3d4.png)\n\nPublicly hosted on IBM Cloud here: [http://libertybikes.mybluemix.net/](http://libertybikes.mybluemix.net/)\n\nBluemix toolchain automatically deploys the current `liberty-bikes/liberty-bikes:master` branch\n\n## How to setup locally\n\n### Prereqs:\n\n- [Java 8 or newer](https://adoptopenjdk.net/index.html?variant=openjdk8\u0026jvmVariant=openj9). Java must also be on the `$PATH`. If you can run `java -version` from a terminal window, then Java is on your `$PATH`.\n- Have [Git installed](https://git-scm.com/downloads)\n- [*Optional*] Have [Docker installed](https://hub.docker.com/?overlay=onboarding) if you want to use the real database or Grafana dashboard.\n\n### Clone and run\n\nFirst, clone this github repo with the following commands:\n\n```\ngit clone git@github.com:OpenLiberty/liberty-bikes.git\ncd liberty-bikes\n```\n\nIf you have a Github account, press the \"Fork\" button in the top right corner of this web page to fork the repository.\n\nNext, build and deploy all microservice applications on locally running liberty servers, then open the game in a web browser. If you are on Windows, you may need to manually open the game in a web browser at http://localhost:12000\n\n```\n./gradlew start frontend:open\n```\n\nAny 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.\n\n### Optional Docker steps\n\nBy 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:\n\n```\n./startDB.sh\n```\n\nTo start the monitoring services, you must have Docker installed. They can be started with:\n\n```\n./startMonitoring.sh\n```\n\n### How to shut everything down cleanly\n\nTo stop all liberty servers, issue the command:\n\n```\n./gradlew stop\n```\n\n## Run it locally in containers\n\n(Requires docker and docker-compose to be installed. The Docker daemon must be running.)\n\nNormally you get better performance running services outside of containers (aka bare metal), but if you want to build and run all of the containers locally, run the command: \n\n```\n./gradlew dockerStart\n```\n\nTo stop and remove the containers, use:\n\n```\n./gradlew dockerStop\n```\n\n# Technologies used\n\n- Java EE 8\n  - CDI 2.0 (auth-service, game-service, player-service)\n  - [EE Concurrency](#ee-concurrency) (game-service, player-service)\n  - JAX-RS 2.1 (auth-service, game-service, player-service)\n  - JNDI (auth-service, game-service, player-service)\n  - [JSON-B](#json-b) (game-service, player-service)\n  - WebSocket 1.1 (game-service)\n- MicroProfile 2.2 \n  - Config (auth-service, game-service, player-service)\n  - JWT (auth-service, game-service, player-service)\n  - [Rest Client](#microprofile-rest-client) (game-service)\n  - [OpenAPI](#microprofile-openapi) (auth-service, game-service, player-service)\n  - [Metrics](#monitoring) (auth-service, game-service, player-service, frontend)\n- Angular 7 (frontend)\n- Prometheus for metric collection\n- Grafana for metric visualization\n- Gradle build\n  - [Liberty Gradle Plugin](#liberty-gradle-plugin)\n- [IBM Cloud Continuous Delivery Pipeline](#continuous-delivery)\n\n\n## JSON-B \n\nSeveral of the backend entities need to be represtented as JSON data so they can be sent to the frontend via websocket, these include objects like `GameBoard`, `Obstacle`, and `Player`.  Using POJOs and the occasional `@JsonbTransient` annotation, we used JSON-B to transform Java objects to JSON data.\n\n```java\npublic class GameBoard {\n\n    @JsonbTransient\n    public final short[][] board = new short[BOARD_SIZE][BOARD_SIZE];\n\n    public final Set\u003cObstacle\u003e obstacles = new HashSet\u003c\u003e();\n    public final Set\u003cMovingObstacle\u003e movingObstacles = new HashSet\u003c\u003e();\n    public final Set\u003cPlayer\u003e players = new HashSet\u003c\u003e();\n\n    // ...\n}\n```\n\nBy default, JSON-B will expose any `public` members as well as public `getXXX()`, this includes other objects such as the `Set\u003cPlayer\u003e players` field.  The resulting class gets serialized into something like this:\n\n```json\n{\n  \"movingObstacles\" : [ \n    { \"height\":12, \"width\":11, \"x\":13, \"y\":14 }\n  ],\n  \"obstacles\" : [\n    { \"height\":2, \"width\":1, \"x\":3, \"y\":4 }\n  ],\n  \"players\" : [    \n    { \"id\":\"1234\", \"name\":\"Bob\", \"color\":\"#f28415\", \"status\":\"Connected\", \"alive\":true, \"x\":9, \"y\":9, \"width\":3, \"height\":3, \"direction\":\"RIGHT\" }\n  ]\n}\n```\n\n## MicroProfile Rest Client\n\nEach of the 3 backend microservices in Liberty Bikes (auth, game, and player) exposed a REST API.  In most cases the frontend would call the backend REST services, but sometimes the backend services had to call each other.  \n\nFor example, when a game is over, the game service makes REST calls to the player service to update the player statistics.  To accomplish this, the game-service simply defines a POJI (plain old Java Interface) that represents the player-service API it cares about, including the data model:\n\n```java\nimport javax.ws.rs.*;\nimport org.eclipse.microprofile.rest.client.inject.RegisterRestClient;\n\n@RegisterRestClient\n@Path(\"/\")\npublic interface PlayerService {\n\n    @GET\n    @Path(\"/player/{playerId}\")\n    @Produces(MediaType.APPLICATION_JSON)\n    public Player getPlayerById(@PathParam(\"playerId\") String id);\n\n    @POST\n    @Path(\"/rank/{playerId}/recordGame\")\n    public void recordGame(@PathParam(\"playerId\") String id, @QueryParam(\"place\") int place);\n\n}\n\npublic class Player {\n    public String id;\n    public String name;\n}\n```\n\nThen, to use the Rest Client in the game service, we simply inject the interface and an implementation is proxied for us:\n\n```java\n@ServerEndpoint(\"/round/ws/{roundId}\")\npublic class GameRoundWebsocket {\n\n    @Inject\n    @RestClient\n    PlayerService playerSvc;\n\n    @Inject\n    GameRoundService gameSvc;\n    \n    private final static Jsonb jsonb = JsonbBuilder.create();\n    \n    @OnMessage\n    public void onMessage(@PathParam(\"roundId\") final String roundId, String message, Session session) {\n        InboundMessage msg = jsonb.fromJson(message, InboundMessage.class);\n        GameRound round = gameSvc.getRound(roundId);\n        // ...\n        Player playerResponse = playerSvc.getPlayerById(msg.playerJoinedId);\n        round.addPlayer(session, msg.playerJoinedId, playerResponse.name, msg.hasGameBoard);\n        // ...\n    }\n}      \n```\n\nThe only non-Java part about MP Rest Client is the need to specify the base path to the service via JVM option.  This is easy enough to do in the build scripting, and easily overridable for cloud environments:\n\n```groovy\nliberty {\n  server {\n    name = 'game-service'\n    jvmOptions = ['-Dorg.libertybikes.restclient.PlayerService/mp-rest/url=http://localhost:8081/']\n  }\n}\n```\n\n## Microprofile OpenAPI\n\nEspecially while developing new Rest APIs locally, it is useful to inspect the exposed APIs and test them out manually. Simply by enabling the `mpOpenAPI-1.0` feature in server.xml (no application changes needed), all JAX-RS endpoints will be exposed in an interactive web UI.\n\nHere is a snapshot of what the player-service view looks like:\n\n![Image of MP OpenAPI web ui](https://user-images.githubusercontent.com/5427967/47033512-a87ef100-d13a-11e8-827d-375e0f1c4cae.png)\n\n## EE Concurrency\n\nExecutors from Java SE are very easy to use, and the \"Managed\" equivalent Executors in EE Concurrency lets you use all of the SE functionality with the added benefit of running the work on threads that are A) managed by the application server and B) have the proper thread context metadata to perform \"EE type\" operations such as CDI injections and JNDI lookups.\n\n```java\nSystem.out.println(\"Scheduling round id=\" + roundId + \" for deletion in 5 minutes\");\nexec.schedule(() -\u003e {\n    allRounds.remove(roundId);\n    System.out.println(\"Deleted round id=\" + roundId);\n}, 5, TimeUnit.MINUTES);\n```\n\n## Liberty Gradle Plugin\n\nLiberty Bikes can be built and run with a single command and no prereqs thanks to Gradle and the Liberty Gradle Plugin! With these build tools we can easily control a bunch of things:\n- Downloading and \"installing\" Liberty\n- Managing build and runtime dependencies (i.e. compile-time classpath and jars that get packaged inside the WAR applications)\n- Starting and stopping one or more Liberty servers\n\nTo get the Liberty gradle plugin, we add this dependency:\n\n```groovy\nbuildscript {\n  repositories {\n    mavenCentral()\n  }\n  dependencies {\n    classpath 'net.wasdev.wlp.gradle.plugins:liberty-gradle-plugin:2.6.5'\n  }\n}\n```\n\nTo control the Liberty distribution, we simply specify a dependency:\n\n```groovy\ndependencies {\n    libertyRuntime group: 'io.openliberty', name: 'openliberty-runtime', version: '[19.0.0.5,)'\n}\n```\n\nOr, if we want to use a Beta image instead of an official GA'd image, we specify a URL in the `liberty.install` task instead of as a runtime dependency:\n\n```groovy\nliberty {\n  install {\n    runtimeUrl = \"https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/wasdev/downloads/wlp/beta/wlp-beta-2018.5.0.0.zip\"\n  }\n}\n```\n\n## Monitoring\n\nIf you run Liberty Bikes in a container environment using `./gradlew dockerStart`, a Prometheus and Grafana instance will be started and preconfigured for monitoring the 4 Liberty Bikes microservices.\n\nIf you are running locally, you can open a browser to http://localhost:3000 and login with the username/password of `admin/admin` (respectively). The dashboard looks something like this:\n\n![Image of Grafana dashboard](https://user-images.githubusercontent.com/5427967/59791807-807ef900-9298-11e9-96fc-6071c85cf865.png)\n\nThe above shapshot shows basic data such as:\n- Service Health: Green/Red boxes for up/down respectively\n- System info: CPU load and memory usage\n- Current stats:\n  - Number of players in queue\n  - Number of players playing a game\n  - Total actions/sec of players\n- Overall stats:\n  - Total number of logins\n  - Total number of games played\n\nAny application-specific stats can be collected using MicroProfile Metrics. For example, to collect number of player logins, we added the following code to our `createPlayer` method:\n\n```java\n    @Inject\n    private MetricRegistry registry;\n\n     private static final Metadata numLoginsCounter = new Metadata(\"num_player_logins\", // name\n                    \"Number of Total Logins\", // display name\n                    \"How many times a user has logged in.\", // description\n                    MetricType.COUNTER, // type\n                    MetricUnits.NONE); // units\n\n    @POST\n    @Produces(MediaType.TEXT_HTML)\n    public String createPlayer(@QueryParam(\"name\") String name, @QueryParam(\"id\") String id) {\n      // ...\n      registry.counter(numLoginsCounter).inc();\n      // ...\n    }\n```\n\n\n## Continuous Delivery\n\nEarly on we set up a build pipeline on IBM Cloud that we pointed at this GitHub repository.  Every time a new commit is merged into the `master` branch, the pipeline kicks off a new build and redeploys all of the services.  The average time from pressing merge on a PR to having the changes live on libertybikes.mybluemix.net is around 20 minutes.\n\nThe pipeline UI looks like this in our dashboard:\n\n![Image of build pipeline](https://user-images.githubusercontent.com/5427967/40152561-41fa1c46-594b-11e8-98b1-3f9f0f0c6472.PNG)\n\nThe pipeline consists of 2 stages: Build and Deploy.\n\nThe build stage simply points at the GitHub repository URL, and has a little bit of shell scripting where we define how to build the repo:\n\n```bash\n#!/bin/bash\nexport JAVA_HOME=~/java8\n./gradlew clean build libertyPackage -Denv_mode=prod\n```\n\nFor the deployment stage, each microservice gets its own step in the stage.  We could also split the microservices into separate stages (or even different pipelines) if we didn't always want to redeploy all microservices.  Like the build stage, the deploy stage has a little bit of shell scripting at each step:\n\n```bash\n#!/bin/bash\n\n# Unzip the archive we receive as build input\ncd game-service/build/libs\nunzip game-service.zip -d game-service\n\n# Set some Cloud Foundry env vars (use the latest WAS Liberty beta)\ncf set-env \"${CF_APP}\" IBM_LIBERTY_BETA true\ncf set-env \"${CF_APP}\" JBP_CONFIG_LIBERTY \"version: +\"\n\n# Override the player-service URL for MP Rest Client on game-service\necho \"-Dorg.libertybikes.restclient.PlayerService/mp-rest/url=\\\nhttp://player-service.mybluemix.net/\" \u003e game-service/wlp/usr/servers/game-service/jvm.options\n\n# Push the entire server directory into Cloud Foundry\ncf push \"${CF_APP}\" -p \"game-service/wlp/usr/servers/game-service\"\n```\n\nOriginally cloned from https://github.com/aguibert/coms319-project4\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FOpenLiberty%2Fliberty-bikes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FOpenLiberty%2Fliberty-bikes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FOpenLiberty%2Fliberty-bikes/lists"}