diff --git a/.github/workflows/checkBuild.yml b/.github/workflows/checkBuild.yml index 6cf3139..800f842 100644 --- a/.github/workflows/checkBuild.yml +++ b/.github/workflows/checkBuild.yml @@ -18,9 +18,10 @@ jobs: - uses: actions/checkout@v2 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 11.x + distribution: 'adopt' - name: Cache local Maven repository uses: actions/cache@v2 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 989840b..1bb4a0a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -13,9 +13,10 @@ jobs: - uses: actions/checkout@v2 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 11.x + distribution: 'adopt' - name: Cache local Maven repository uses: actions/cache@v2 diff --git a/.github/workflows/deployDockerHubDevelop.yml b/.github/workflows/deployDockerHubDevelop.yml new file mode 100644 index 0000000..b484618 --- /dev/null +++ b/.github/workflows/deployDockerHubDevelop.yml @@ -0,0 +1,29 @@ +name: Deploy develop to DockerHub + +on: + workflow_dispatch: + push: + branches: [ develop ] + paths-ignore: + - '**.md' + +jobs: + build_publish_docker: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Generate tag vars + id: tagvars + run: | + echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" + echo "::set-output name=build_datetime::$(date -u +%Y%m%d-%H%M)" + + - name: Builder Dockerimage and publish to Registry + uses: elgohr/Publish-Docker-Github-Action@master + with: + name: alb2k/fuel-filling-service + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + tags: "develop" diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 27c2add..8fb859e 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -14,9 +14,10 @@ jobs: - uses: actions/checkout@v2 - name: Setup - Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 11.0.x + distribution: 'adopt' - name: Restore - Maven Cache uses: actions/cache@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 68d5404..85241b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,9 +12,10 @@ jobs: - uses: actions/checkout@v2 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: java-version: 11.x + distribution: 'adopt' - name: Cache local Maven repository uses: actions/cache@v2 @@ -66,6 +67,26 @@ jobs: asset_name: release.zip asset_content_type: application/octet-stream + build_publish_docker: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Generate tag vars + id: tagvars + run: | + echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" + echo "::set-output name=build_datetime::$(date -u +%Y%m%d-%H%M)" + + - name: Builder Dockerimage and publish to Registry + uses: elgohr/Publish-Docker-Github-Action@master + with: + name: alb2k/fuel-filling-service + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + tags: "latest,master-${{ steps.tagvars.outputs.sha_short }}-${{ steps.tagvars.outputs.build_datetime }}" + after_release: runs-on: ubuntu-latest needs: [build_release_jar] diff --git a/README.md b/README.md index 68ffa4a..c9ff042 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,10 @@ A helidon (microprofile) RESTful webservice with microstream. The project represents a basic CRUD webservice where you can manage fuel fillings (of a car).
It is also shipped with a nice UI (openapi-ui) so that no external REST/HTTP client is required. -This project was created for the [Microstream hackathon](https://hackathon.microstream.one/) - ### Used technologies * [Microstream](https://microstream.one/platforms/microstream-for-java/) +* [Helidon MP](https://helidon.io/#getting-started) * [Microprofile (config)](https://github.com/eclipse/microprofile-config) -* [Helidon MP](https://helidon.io/#getting-started) * [MP Health](https://github.com/eclipse/microprofile-health) * Logging via [SLF4J](http://www.slf4j.org/) and [Apache Log4j 2](https://logging.apache.org/log4) * [OpenApi](https://www.openapis.org/) @@ -17,18 +15,26 @@ This project was created for the [Microstream hackathon](https://hackathon.micro * [GitHub Actions](https://github.com/features/actions) for CI/CD * [Heroku](https://www.heroku.com/) for hosting the demo +### [Documentation in detail](docs/README.md) +Documentation about this project is [available here](docs/README.md) + ## [Demo](https://hackathon-ms-fuel-filling.herokuapp.com) [![Deployment Status](https://img.shields.io/github/workflow/status/alb2k/fuel-filling-service/Deploy%20CI?label=deployment)](https://github.com/alb2k/fuel-filling-service/actions/workflows/deploy.yml) -The demo is hosted on heroku. +The demo is hosted on heroku.
+It may take some seconds to start. ![openapi-ui screenshot](assets/OpenApiUI.png) +* [OpenAPI-UI (redirection)](https://hackathon-ms-fuel-filling.herokuapp.com) +* [OpenAPI](https://hackathon-ms-fuel-filling.herokuapp.com/openapi) +* [Health](https://hackathon-ms-fuel-filling.herokuapp.com/health) + ## Download [![Release Status](https://img.shields.io/github/workflow/status/alb2k/fuel-filling-service/Release%20CI?label=release)](https://github.com/alb2k/fuel-filling-service/actions/workflows/release.yml) There are prebuilt executables, which save you from building the executable locally. ### JAR * Check if you have Java 11 installed, if not [install it](https://adoptopenjdk.net/?variant=openjdk11&jvmVariant=hotspot) -* Download the [latest zip from the releases](https://github.com/alb2k/fuel-filling-service/releases/latest) -* Unzip it and run it locally with ``java -jar fuel-filling-service.jar`` +* Download the [latest zip from the releases](https://github.com/alb2k/fuel-filling-service/releases/latest) and unzip it +* Run ``java -jar fuel-filling-service.jar`` * Open http://localhost:8080 → you should get redirected to the OpenAPI UI @@ -60,9 +66,17 @@ Requirements: Requirements: * Docker -#### "Normal" Dockerfile +#### Running the prebuilt image from DockerHub [![Latest docker version](https://img.shields.io/badge/docker-latest-%232684ff)](https://hub.docker.com/r/alb2k/fuel-filling-service/tags?name=latest&page=1) [![Develop docker version](https://img.shields.io/badge/docker-develop-%232684ff)](https://hub.docker.com/r/alb2k/fuel-filling-service/tags?name=develop&page=1) +* Run the latest release using ``docker run --rm -p 8080:8080 --name fuel-filling alb2k/fuel-filling-service`` +* Stop/Remove it with ``docker stop fuel-filling`` + +#### Building and running it * Build the image with ``docker build -t fuel-filling .`` * Execute it with ``docker run --rm -p 8080:8080 --name fuel-filling fuel-filling`` * Stop/Remove it with ``docker stop fuel-filling`` -### Dependencies and Licenses [![dependency overview](https://img.shields.io/badge/dependency--overview-online-success?logo=apache-maven)](https://alb2k.github.io/fuel-filling-service/dependencies/) [![Apache License 2.0](https://img.shields.io/github/license/alb2k/fuel-filling-service?color=informational)](https://choosealicense.com/licenses/apache-2.0/) +## Dependencies and Licenses [![dependency overview](https://img.shields.io/badge/dependency--overview-online-success?logo=apache-maven)](https://alb2k.github.io/fuel-filling-service/dependencies/) [![Apache License 2.0](https://img.shields.io/github/license/alb2k/fuel-filling-service?color=informational)](https://choosealicense.com/licenses/apache-2.0/) +For the license of this project, check the [LICENSE file](LICENSE)
+A summary of all dependencies and their licenses is also available [online](https://alb2k.github.io/fuel-filling-service/dependencies/) + +This project was created for the [Microstream hackathon](https://hackathon.microstream.one/) diff --git a/docs/GHActions.md b/docs/GHActions.md new file mode 100644 index 0000000..63d2499 --- /dev/null +++ b/docs/GHActions.md @@ -0,0 +1,11 @@ +# GitHub Actions + +The following plans are used: +| Plan | Runs when | Description | +| --- | --- | --- | +| [checkBuild.yml](../.github/workflows/checkBuild.yml) | Push or PullRequest on develop | Checks if the project is buildable (using ``mvn -B clean package``) | +| [release.yml](../.github/workflows/release.yml) | Push on master | Builds a release jar and creates a new release draft on GitHub, where the jar is uploaded | +| [deploy.yml](../.github/workflows/deploy.yml) | Push on master | Deploys the current code to [Heroku](Heroku.md) | +| [gh-pages.yml](../.github/workflows/gh-pages.yml) | Push on master | Creates a [project-info-report about the dependencies](https://maven.apache.org/plugins/maven-project-info-reports-plugin/dependencies-mojo.html) and publishes it with [GitHub pages](https://pages.github.com/) | + +Results of the executed workflows occur in the [``Actions`` tab](https://github.com/alb2k/fuel-filling-service/actions) diff --git a/docs/Helidon.md b/docs/Helidon.md new file mode 100644 index 0000000..ea35a1c --- /dev/null +++ b/docs/Helidon.md @@ -0,0 +1,41 @@ +# Helidon (Microprofile) + +[Helidon MP](https://helidon.io/docs/latest/#/about/02_introduction) is a [collection of Java Libaries](https://alb2k.github.io/fuel-filling-service/dependencies/#Dependency_Tree) mainly used for Java/Jakarte EE conform web/microservices.
+This project is based on [helidon-quickstart-mp](https://github.com/oracle/helidon/tree/master/examples/quickstarts/helidon-quickstart-mp). + +## Webservice +![Heldion_Overview.png](https://user-images.githubusercontent.com/80211953/113520281-34e32f80-9592-11eb-8ea8-6d5d118864b2.png) + +The HTTP Webservice was done with [JAX-RS](https://en.wikipedia.org/wiki/Jakarta_RESTful_Web_Services) and other [Java/Jakarta EE technologies](https://en.wikipedia.org/wiki/Jakarta_EE) (see below).
+The "rest resources" (which represent the HTTP interface) are located in [src/main/java/hackathon/microstream/service/rest/resource](../src/main/java/hackathon/microstream/service/rest/resource). + +The integrated webserver is the by default shipped [Helidon-Webserver](https://github.com/oracle/helidon/tree/master/webserver/webserver). + +#### Validation +If entities are transmitted to a method they are validated (via the ``@Valid`` annotation) using [jersey-bean-validation](https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/bean-validation.html).
+When the validation fails, a ``ConstraintViolationException`` is thrown which is automatically processed by the [ConstraintViolationExceptionMapper](../src/main/java/hackathon/microstream/service/system/ConstraintViolationExceptionMapper.java) so that it results in a better readable exception: +``` +Unrecognized token 'trues': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false') + at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 7, column: 30] +``` + +#### Serialization +For the serialization from JSON to Java objects and back [com.fasterxml.jackson](https://github.com/FasterXML/jackson) is used. +This worked pretty good, however some tweaks had to be [applied to the defaults](../src/main/java/hackathon/microstream/service/system/ObjectMapperContextResolver.java): +* By default LocalDate is written as array, which is not [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601) conform
+ → https://stackoverflow.com/a/28803634 +* By default ObjectMapper throws an exception, if unknown properties are supplied.
+ To be a bit less restrictive, unknown properties are allowed (easier handling of objects with / without UUID) + +To also automatically serialize Java objects (to JSON) in a response without the need of doing it manually with an ObjectMapper [org.glassfish.jersey.media:jersey-media-json-jackson](https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-json-jackson) is used.
+More information [available here](https://stackoverflow.com/questions/26207252/messagebodywriter-not-found-for-media-type-application-json). + +#### CDI +The corresponding [service](../src/main/java/hackathon/microstream/service/provider) is injected into the rest resource via CDI. + +#### Microprofile +Furthermore the integrated webserver can be configured with microprofile.
+For example, the [``server.port`` can be set](../src/main/resources/META-INF/microprofile-config.properties#L4-L6). + +#### Other tweaks +* When a entity is not found, a [``NotFoundException``](../src/main/java/hackathon/microstream/dal/util/NotFoundException.java) is thrown, which is automatically catched by the [``NotFoundExceptionHandler``](../src/main/java/hackathon/microstream/service/rest/errorhandling/NotFoundExceptionHandler.java) diff --git a/docs/Heroku.md b/docs/Heroku.md new file mode 100644 index 0000000..76d97a0 --- /dev/null +++ b/docs/Heroku.md @@ -0,0 +1,74 @@ +# Heroku deployment +The goal here was to deploy a demo online, so that you don't have to run the code locally and also have a showcase available. + +I used [Heroku](https://www.heroku.com/home) once before with GitHub so I choose that as my cloud platform again. + +## How to set it up +The setup is based on [this guide](https://dev.to/heroku/deploying-to-heroku-from-github-actions-29ej) + +### Preparing Heroku +At first: You need to [setup an account](https://signup.heroku.com/) + +TL;DR
+Web applications on Heroku are called [web dynos](https://www.heroku.com/dynos).
+A [free tier account on Heroku](https://www.heroku.com/pricing) grants you 550 dyno hours (and additional 450 hours if you add your credit card).
+Your dyno will automatically sleep/shut down if there is no activity in the last 30 minutes. + +After the account is set up, create a new app: +![Pic](https://user-images.githubusercontent.com/80211953/111872102-e4fb4a80-898d-11eb-8151-ab41ca7a23b7.png) +Choose your region, add a name (the app will be available under that ``https://.herokuapp.com``) and then click create app. + +### Deploying the project using GitHub +Great, now we have a new app on heroku... but there isn't actually anything running, because nothing was deployed. + +So how can we deploy code (from GitHub)?
+There are 2 ways: +* Setup a deployment pipeline directly in Heroku +* Write a [GitHub actions](https://github.com/features/actions) workflow that builds and deploys the code + +I wanted to stay independent from Heroku and - through the fact that I already had experience with GitHub Actions - I ultimately choose those. + +#### Setting up secrets +There have to be some [secrets stored for the coming GitHub Actions workflow](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository): +* ``HEROKU_APP_NAME``
+The app-name that is used on heroku e.g. ``hackathon-ms-fuel-filling`` +* ``HEROKU_API_KEY``
+The API-Key so that the app can be deployed to heroku.
+The key can be generated via multiple methods.
+I recommend adding a new "authorization" (API-Key) using https://dashboard.heroku.com/account/applications#authorizations + +#### Writing the workflow +Setup a new workflow and name it e.g. [deploy.yml](../.github/workflows/deploy.yml) + +The workflow has to meet the following requirements: +* Executed the workflow when a new release is created or manually +* Build the app (as jar) +* Deploy the app to Heroku + +The first two parts are pretty easy doable if you know a bit about GitHub Actions.
+The last part is a little bit more tricky: +* GitHub Actions own virtual (linux) machines are by default equipped with the Heroku CLI.
More details are available [here](https://github.com/actions/virtual-environments#available-environments) +* At first install [Heroku's Java plugin](https://github.com/heroku/plugin-java): ``heroku plugins:install java`` +* Deploy the jar with the deploy command ``heroku deploy:jar`` + * Select the jar using ``target/fuel-filling-service.jar`` + * Set the JDK to Java 11 using ``--jdk 11``.
Heroku trys to deploy by default with JDK 8. + * The libs folder must be included: ``--includes target/libs/`` + * Select the app that you want to deploy to with ``--app ${{ secrets.HEROKU_APP_NAME }}`` + * Of course Heroku also needs some type of authentication. This is done using ``HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}`` + +In the last 2 steps you noted the use of ``${{ secret.XXX }}``.
+This is the usage of the GitHub secrets we created before. + +#### Using a Procfile +There is one special case when you want to use Heroku: You have to either expose your app on Port 80 or you have to bind to Herokus ``PORT`` environment variable. + +This can be done easily using a [Procfile](/Procfile) in the repository root which contains ``-Dserver.port=$PORT``. + +#### Deploying it +Great - now when everything was done correctly - you should be able to deploy it.
+Go to the ``Actions`` tab of your repository and run the Deployment workflow: +![Pic](https://user-images.githubusercontent.com/80211953/111875065-137e2300-8998-11eb-9819-f83c0e04cdbc.png) + +After some time you should see that your app was deployed on Heroku.
+It is also useful to check the logs for problems and have a quick look at your app. +![Pic](https://user-images.githubusercontent.com/80211953/111875837-1418b880-899c-11eb-895e-81c62ba2e5d4.png) diff --git a/docs/Logging.md b/docs/Logging.md new file mode 100644 index 0000000..ddbb5c9 --- /dev/null +++ b/docs/Logging.md @@ -0,0 +1,49 @@ +# Logging +To protocol information about events in the app, some kind of logging is required.
+For Java therefore multiple logging frameworks exist. Some popular ones include SLF4J, LOG4J, JUL, Logback, etc. + +Helidon uses [JUL](https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html) as default logger. + +### Using another logging framework +However I prefer the more satisfying [SLF4J](http://www.slf4j.org/) (→ https://stackoverflow.com/a/11360517) in combination with [Log4j 2](https://logging.apache.org/log4j/2.x/).
+So an adapter was needed to redirect JUL to SLF4J... + +Luckily I found [this great guide about how to use Heldion with other logging frameworks](https://medium.com/helidon/helidon-logging-and-mdc-5de272cf085d) which basically tells you how to achieve that. + +#### TL;DR / What was done to add it to this project? +* Added the following dependencies: + * [io.helidon.logging:helidon-logging-slf4j](https://mvnrepository.com/artifact/io.helidon.logging/helidon-logging-slf4j) (Helidon SLF4J integration) + * [org.slf4j:slf4j-api](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) (logging facade) + * [org.slf4j:jul-to-slf4j](https://mvnrepository.com/artifact/org.slf4j/jul-to-slf4j) (JUL → SLF4J) + * [org.apache.logging.log4j:log4j-slf4j-impl](https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl) (SLF4J → LOG4J) + * [org.apache.logging.log4j:log4j-core](https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core) ("real" logging framework) +* Added [log4j2.xml](../src/main/resources/log4j2.xml) which [configures the logging framework](https://logging.apache.org/log4j/2.x/manual/configuration.html) +* Tweaked the [logging.properties](../src/main/resources/logging.properties) a bit so that it works correctly +* Due to no available main entry class the SLF4JBridgeHandler for JUL is installed in the [Startup class](../src/main/java/hackathon/microstream/Startup.java#L26-L30) + +After everything was done the output looks like this: +``` +"...java.exe" ... io.helidon.microprofile.cdi.Main +[INFORMATION] 2021-04-03 20:31:47 [Thread[main,5,main]] Logging at initialization configured using classpath: /logging.properties +[INFO ] 2021-04-03 20:31:48.476 [main] org.jboss.weld.Version - WELD-000900: 3.1.6 (Final) +[INFO ] 2021-04-03 20:31:49.309 [main] org.jboss.weld.Bootstrap - WELD-ENV-000020: Using jandex for bean discovery +[INFO ] 2021-04-03 20:31:49.531 [main] org.jboss.weld.Bootstrap - WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously. +[INFO ] 2021-04-03 20:31:49.679 [main] org.jboss.weld.Event - WELD-000411: Observer method [BackedAnnotatedMethod] public org.glassfish.jersey.ext.cdi1x.internal.ProcessAllAnnotatedTypes.processAnnotatedType(@Observes ProcessAnnotatedType, BeanManager) receives events for all annotated types. Consider restricting events using @WithAnnotations or a generic type with bounds. +[INFO ] 2021-04-03 20:31:49.694 [main] org.jboss.weld.Event - WELD-000411: Observer method [BackedAnnotatedMethod] private io.helidon.microprofile.openapi.OpenApiCdiExtension.processAnnotatedType(@Observes ProcessAnnotatedType) receives events for all annotated types. Consider restricting events using @WithAnnotations or a generic type with bounds. +[INFO ] 2021-04-03 20:31:50.465 [main] hackathon.microstream.Startup - The application is starting... +[INFO ] 2021-04-03 20:31:50.465 [main] hackathon.microstream.Startup - Installing org.slf4j.bridge.SLF4JBridgeHandler +[INFO ] 2021-04-03 20:31:50.465 [main] hackathon.microstream.Startup - Installed org.slf4j.bridge.SLF4JBridgeHandler successfully +[INFO ] 2021-04-03 20:31:50.465 [main] hackathon.microstream.Startup - Initializing DB +[INFO ] 2021-04-03 20:31:50.465 [main] hackathon.microstream.storage.DBManager - Loading configuration +[INFO ] 2021-04-03 20:31:51.020 [main] hackathon.microstream.storage.DBManager - BaseDirectory is 'data' +[INFO ] 2021-04-03 20:31:51.020 [main] hackathon.microstream.storage.DBManager - Initializing storageManager +[INFO ] 2021-04-03 20:31:51.553 [main] hackathon.microstream.storage.DBManager - Demo mode; Using demo data +[INFO ] 2021-04-03 20:31:51.553 [main] hackathon.microstream.Startup - Initialized DB +[INFO ] 2021-04-03 20:31:51.553 [main] hackathon.microstream.Startup - The application is started +[INFO ] 2021-04-03 20:31:51.553 [main] io.helidon.microprofile.server.ServerCdiExtension - Registering JAX-RS Application: App +[INFO ] 2021-04-03 20:31:52.217 [main] org.hibernate.validator.internal.util.Version - HV000001: Hibernate Validator 6.1.2.Final +[INFO ] 2021-04-03 20:31:53.253 [nioEventLoopGroup-2-1] io.helidon.webserver.NettyWebServer - Channel '@default' started: [id: 0x245155bc, L:/0:0:0:0:0:0:0:0:8080] +[INFO ] 2021-04-03 20:31:53.253 [main] io.helidon.microprofile.server.ServerCdiExtension - Server started on http://localhost:8080 (and all other host addresses) in 6257 milliseconds (since JVM startup). +[INFO ] 2021-04-03 20:31:53.268 [features-thread] io.helidon.common.HelidonFeatures - Helidon MP 2.2.1 features: [CDI, Config, Health, JAX-RS, Open API, Server] +... +``` diff --git a/docs/Microstream.md b/docs/Microstream.md new file mode 100644 index 0000000..14dac6e --- /dev/null +++ b/docs/Microstream.md @@ -0,0 +1,21 @@ +# Microstream + +[Microstream](https://microstream.one/) is a storage/persistence solution which uses Graphs and binary files for it's storage. + +The project was mainly written based on the [Microstream Getting Started Guide](https://manual.docs.microstream.one/data-store/getting-started) + +#### TL;DR / What was done to add it to this project? +* Added [the dependencies](https://manual.docs.microstream.one/data-store/getting-started#prerequisites) to the [pom.xml](../pom.xml) +* Added a [root instance / DBContext class](../src/main/java/hackathon/microstream/storage/DBContext.java), where all objects that should be persisted are attached to (more information available [here](https://manual.docs.microstream.one/data-store/getting-started#the-root-instance)) +* Added a [DB Manager](../src/main/java/hackathon/microstream/storage/DBManager.java), which manages (cares about initializing, reading, saving, shutdown ...) and holds the DBContext + * There also exist a demo mode → if enabled all data is overriden by the defaults on init
+ The demo mode is managed by [microprofile-config.properties](../src/main/resources/META-INF/microprofile-config.properties#L2) +* Added [microstream-storage.properties](../src/main/resources/microstream-storage.properties) +* Added a [DAL Layer](../src/main/java/hackathon/microstream/dal) which uses the DB Manager + + +The app by default writes the storage / binary files to the ``data`` folder. + + +### Notes +* Microstream doesn't have any built in ID management. I used [UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier) for that. diff --git a/docs/OpenAPI.md b/docs/OpenAPI.md new file mode 100644 index 0000000..6aca878 --- /dev/null +++ b/docs/OpenAPI.md @@ -0,0 +1,25 @@ +# OpenAPI + +[OpenAPI](https://www.openapis.org/) is a standardized specification to describe REST-conform APIs. + +Helidon supports OpenAPI.
+For more information read [this guide](https://medium.com/helidon/project-helidon-and-openapi-54a1fadc75b1). + +## OpenAPI UI +To test the app without the requirement of an external HTTP Client, you can simply add [OpenAPI UI](https://github.com/microprofile-extensions/openapi-ext/blob/main/openapi-ui/README.md) (derived from [Swagger UI](https://swagger.io/tools/swagger-ui/)). + +#### What was done to add it to this project? +* Added the [dependency](https://mvnrepository.com/artifact/org.microprofile-ext.openapi-ext/openapi-ui) to the pom.xml +* Annotated the Rest-Endpoints (e.g. ``@Operation`` or ``@APIResponse``) +* Added a custom application class for more information → [App.java](../src/main/java/hackathon/microstream/service/system/App.java) +* Customized the UI further with [microprofile-config.properties](../src/main/resources/META-INF/microprofile-config.properties#L9-L11) +* Added a [RootResource](../src/main/java/hackathon/microstream/service/rest/resource/RootResource.java) which redirects to the OpenAPI UI endpoint when trying to get '/' + +I also highly recommend reading the ["Getting Started" page of OpenAPI-UI](https://github.com/microprofile-extensions/openapi-ext/blob/main/openapi-ui/README.md#getting-started), because it explains everything that was done in detail. + + +Note: Using the app locally sometimes throws `` +[WARN ] 2021-04-03 20:00:00.787 [helidon-2] io.helidon.webserver.RequestRouting - Default error handler: Response wasn't successfully sent. +java.util.concurrent.CompletionException: io.helidon.webserver.SocketClosedException: Response channel is closed! +`` when calling the root endpoint ('/').
+This problem does not occur when it is deployed on Heroku. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..01a5a58 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,21 @@ +# Docs +Documentation about this project + +## Details +Each part / Used technology of the project has it's own documentation: +1. [Helidon](Helidon.md)
+Webservice, CDI, Configuration with Microprofile and more +2. [Microstream](Microstream.md)
+Graph based binary data storage +3. [OpenAPI (+UI)](OpenAPI.md)
+Standardized specification to describe REST-conform APIs + UI +4. [Logging](Logging.md) +5. [GitHub Actions](GHActions.md)
+Continuous Integration (CI) / Continuous Delivery (CD) +6. [Heroku](Heroku.md)
+How to host a demo online + + +## Overview +A quick overview how the project is designed to work:

+![Overview](https://user-images.githubusercontent.com/80211953/112724270-d7583e80-8f12-11eb-9506-5e62c647f98d.png) diff --git a/pom.xml b/pom.xml index 10598b7..e8eadaf 100644 --- a/pom.xml +++ b/pom.xml @@ -18,9 +18,9 @@ UTF-8 - 2.2.1 + 2.3.1 - 1.7.30 + 1.7.31 2.14.1 @@ -84,18 +84,23 @@ jakarta.activation jakarta.activation-api + + org.glassfish.jersey.media jersey-media-json-jackson + org.glassfish.jersey.ext jersey-bean-validation + com.fasterxml.jackson.datatype jackson-datatype-jsr310 + org.jboss jandex @@ -103,11 +108,12 @@ true - + org.microprofile-ext.openapi-ext openapi-ui 1.1.4 + runtime @@ -144,12 +150,13 @@ ${org.apache.logging.log4j.version} - + jakarta.annotation jakarta.annotation-api 1.3.5 + commons-beanutils commons-beanutils @@ -178,7 +185,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.1.2 + 3.2.0 org.apache.maven.plugins @@ -203,7 +210,7 @@ org.jboss.jandex jandex-maven-plugin - 1.0.8 + 1.1.0 org.codehaus.mojo @@ -216,12 +223,12 @@ io.helidon.build-tools helidon-maven-plugin - 2.1.3 + 2.2.1 io.helidon.build-tools helidon-cli-maven-plugin - 2.1.3 + 2.2.1 @@ -243,7 +250,6 @@ true true runtime - test @@ -292,7 +298,7 @@ org.jmdns jmdns - 3.5.6 + 3.5.7 diff --git a/src/main/java/hackathon/microstream/Startup.java b/src/main/java/hackathon/microstream/Startup.java index 23e1a58..8e52151 100644 --- a/src/main/java/hackathon/microstream/Startup.java +++ b/src/main/java/hackathon/microstream/Startup.java @@ -11,6 +11,11 @@ import javax.enterprise.context.Initialized; import javax.enterprise.event.Observes; +/** + * Handles the startup and shutdown + * + * @see https://stackoverflow.com/a/11476587 + */ @ApplicationScoped public class Startup { private final static Logger LOG = LoggerFactory.getLogger(Startup.class); diff --git a/src/main/java/hackathon/microstream/dal/FillingRepository.java b/src/main/java/hackathon/microstream/dal/FillingRepository.java index 3c87664..eb1e602 100644 --- a/src/main/java/hackathon/microstream/dal/FillingRepository.java +++ b/src/main/java/hackathon/microstream/dal/FillingRepository.java @@ -27,7 +27,7 @@ public List getAll() { * @return * @throws NotFoundException */ - public Filling getById(UUID id) { + public DBFilling getById(UUID id) { if(id == null) throw new IllegalArgumentException("id can't be null"); @@ -48,7 +48,7 @@ public Filling getById(UUID id) { * @param filling * @return */ - public Filling add(Filling filling) { + public DBFilling add(Filling filling) { if(filling == null) throw new IllegalArgumentException("filling can't be null"); @@ -72,13 +72,13 @@ public Filling add(Filling filling) { * @return * @throws NotFoundException */ - public Filling update(UUID id, Filling filling) { + public DBFilling update(UUID id, Filling filling) { if(id == null) throw new IllegalArgumentException("id can't be null"); if(filling == null) throw new IllegalArgumentException("filling can't be null"); - Filling dbFilling = getById(id); + var dbFilling = getById(id); // Update the properties in the bean try { diff --git a/src/main/java/hackathon/microstream/service/provider/FillingService.java b/src/main/java/hackathon/microstream/service/provider/FillingService.java index e3776e8..c856f30 100644 --- a/src/main/java/hackathon/microstream/service/provider/FillingService.java +++ b/src/main/java/hackathon/microstream/service/provider/FillingService.java @@ -26,7 +26,7 @@ public List getAll() { * @return * @throws NotFoundException */ - public Filling getById(UUID id) { + public DBFilling getById(UUID id) { return this.fillingRepository.getById(id); } @@ -35,7 +35,7 @@ public Filling getById(UUID id) { * @param filling * @return */ - public Filling add(Filling filling) { + public DBFilling add(Filling filling) { return this.fillingRepository.add(filling); } @@ -45,7 +45,7 @@ public Filling add(Filling filling) { * @return * @throws NotFoundException */ - public Filling update(UUID id, Filling filling) { + public DBFilling update(UUID id, Filling filling) { return this.fillingRepository.update(id, filling); } diff --git a/src/main/java/hackathon/microstream/service/system/ConstraintViolationExceptionMapper.java b/src/main/java/hackathon/microstream/service/system/ConstraintViolationExceptionMapper.java index 9d43bda..b3a73fe 100644 --- a/src/main/java/hackathon/microstream/service/system/ConstraintViolationExceptionMapper.java +++ b/src/main/java/hackathon/microstream/service/system/ConstraintViolationExceptionMapper.java @@ -1,12 +1,14 @@ package hackathon.microstream.service.system; -import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import java.util.stream.Collectors; +/** + * Makes ConstraintViolationExceptions pretty + */ @Provider public class ConstraintViolationExceptionMapper implements ExceptionMapper { @Override diff --git a/src/main/java/hackathon/microstream/service/system/ObjectMapperContextResolver.java b/src/main/java/hackathon/microstream/service/system/ObjectMapperContextResolver.java index 4e08ba8..2f4f0ec 100644 --- a/src/main/java/hackathon/microstream/service/system/ObjectMapperContextResolver.java +++ b/src/main/java/hackathon/microstream/service/system/ObjectMapperContextResolver.java @@ -8,6 +8,23 @@ import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; +/** + * The default provider for a configured {@link ObjectMapper} + * + * Configuration: + *
    + *
  • + * By default LocalDate is written as array, which is not ISO-8601 conform
    + * https://stackoverflow.com/a/28803634 + *
  • + *
  • + * By default ObjectMapper throws an exception, if unknown properties are supplied.
    + * To be a bit less restrictive, unknown properties are allowed (easier handling of objects with / without UUID) + *
  • + *
+ * + * + */ @Provider public class ObjectMapperContextResolver implements ContextResolver { private final ObjectMapper MAPPER; @@ -17,6 +34,7 @@ public ObjectMapperContextResolver() { // Make LocalDate & Co to Strings (and vice versa) MAPPER.registerModule(new JavaTimeModule()); MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } diff --git a/src/main/java/hackathon/microstream/storage/DBManager.java b/src/main/java/hackathon/microstream/storage/DBManager.java index 1dc0993..e2a6e57 100644 --- a/src/main/java/hackathon/microstream/storage/DBManager.java +++ b/src/main/java/hackathon/microstream/storage/DBManager.java @@ -9,6 +9,9 @@ import java.time.LocalDate; +/** + * Manages DB access (Microstream) + */ public class DBManager { private static final Logger LOG = LoggerFactory.getLogger(DBManager.class); diff --git a/src/main/resources/META-INF/microprofile-config.properties b/src/main/resources/META-INF/microprofile-config.properties index a56e089..7f845df 100644 --- a/src/main/resources/META-INF/microprofile-config.properties +++ b/src/main/resources/META-INF/microprofile-config.properties @@ -1,5 +1,4 @@ # Application properties. -#app.greeting=Hello db.demoMode=true # Microprofile server properties @@ -8,4 +7,4 @@ server.host=0.0.0.0 # OpenAPI-UI openapi.ui.title=Fuel filling webservice -openapi.ui.swaggerHeaderVisibility=hidden \ No newline at end of file +openapi.ui.swaggerHeaderVisibility=hidden