Step-By-Step Guide On How To Build a Project With DApps Part 1: Java
Step-By-Step Guide On How To Build a Project With DApps Part 1: Java
Take a look at this article, the first part of a series on creating an application using DApps and Java. Click here to learn more!
Join the DZone community and get the full member experience.Join For Free
Container Monitoring and Management eBook: Read about the new realities of containerization.
This article is the beginning of a series of assembling DApp applications in different languages, platforms, and process stacks. In connection with the recent release of DApp 0.26.2, I will show at the same time how to describe the assembly in a YAML file.
I will describe the assembly using the example application from the Docker samples repository, the atsea-sample-shop-app. This is a prototype of a small store built on React (front) and Java Spring Boot (backend). As a database, PostgreSQL is used. For greater similarity to the working project, a reverse proxy on NGINX and a payment gateway in the form of a simple script were added.
In the article I will describe the assembly of only the application — images with NGINX, PostgreSQL, and the gateway can be found in our work in the DApp file.
Building of the Application
After cloning the repository, the ready Dockerfile for Java and React applications can be found on the path /app/Dockerfile. This file defines two image-stages (in DApp it's an artifact) and one final image. In stages, a Java application is built into the jar and the React-application into the/static directory.
FROM node:latest AS storefront WORKDIR /usr/src/atsea/app/react-app COPY react-app . RUN npm install RUN npm run build FROM maven:latest AS appserver WORKDIR /usr/src/atsea COPY pom.xml . RUN mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:resolve COPY . . RUN mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests FROM java:8-jdk-alpine RUN adduser -Dh /home/gordon gordon WORKDIR /static COPY --from=storefront /usr/src/atsea/app/react-app/build/ . WORKDIR /app COPY --from=appserver /usr/src/atsea/target/AtSea-0.0.1-SNAPSHOT.jar . ENTRYPOINT ["java", "-jar", "/app/AtSea-0.0.1-SNAPSHOT.jar"] CMD ["--spring.profiles.active=postgres"]
To begin with, I will redesign this file from "as is" into "classic" Dappfile, and then, in dappfile.yml.
DApp file turns out more verbose at the expense of Ruby-blocks:
dimg_group do artifact do # artifact for building a Java application docker.from 'maven: latest' git do add '/ app' do to '/ usr / src / atsea' end end shell do install do run 'cd / usr / src / atsea' run 'mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency: resolve' run 'mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests' end end export '/usr/src/atsea/target/AtSea-0.0.1-SNAPSHOT.jar' do to '/app/AtSea-0.0.1-SNAPSHOT.jar' after: install end end artifact do # artifact for building React-applications docker.from 'node: latest' git do add '/ app / react-app' do to '/ usr / src / atsea / app / react-app' end end shell do install do run 'cd / usr / src / atsea / app / react-app' run 'npm install' run 'npm run build' end end export '/ usr / src / atsea / app / react-app / build' do to '/ static' after: install end end dimg 'app' do docker.from 'java: 8-jdk-alpine' shell do before_install do run 'mkdir / app' run 'adduser -Dh / home / gordon gordon' end end docker do entrypoint "java", "-jar", "/app/AtSea-0.0.1-SNAPSHOT.jar" cmd "--spring.profiles.active = postgres" end end end
"Classic" DApp file has an option with export to the artifact, which was available in DApp before the February releases. It differs from the COPY directive from in Dockerfile in that it is in the artifact that it indicates what and where to copy, and not in the description of the final image. So it's easier to describe roughly the same images, in which you need to copy one result of an assembly or something. Now, with version 0.26.2, DApp supports the import mechanism, which is even more preferable to use (see below for an example of its use).
And one more comment to the file. When assembling through a Docker build, the context is sent to the Docker engine. Usually, this is the directory where the Dockerfile is located and the source code of the application. In the case of DApp, the context is the Git repository, the history of which DApp calculates the changes that occurred during the last assembly, and only changes what has changed in the final image. That is, the analog of the COPY directive without—from in Dockerfile is the git directive, which describes which directories or files from the repository need to be copied into the final image, where to put, which owner to assign. Also here it is described, from what changes the re-assembly depends, but more on this later. For now, let's see how the same assembly looks in the new YAML syntax:
artifact: appserver from: maven: latest git: - add: '/ app' to: '/ usr / src / atsea' shell: install: - cd / usr / src / atsea - mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency: resolve - mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests --- artifact: storefront from: node: latest git: - add: / app / react-app to: / usr / src / atsea / app / react-app shell: install: - cd / usr / src / atsea / app / react-app - npm install - npm run build --- dimg: app from: java: 8-jdk-alpine shell: beforeInstall: - mkdir / app - adduser -Dh / home / gordon gordon import: - artifact: appserver add: '/usr/src/atsea/target/AtSea-0.0.1-SNAPSHOT.jar' to: '/app/AtSea-0.0.1-SNAPSHOT.jar' after: install - artifact: storefront add: / usr / src / atsea / app / react-app / build to: / static after: install docker: ENTRYPOINT: ["java", "-jar", "/app/AtSea-0.0.1-SNAPSHOT.jar"] CMD: ["--spring.profiles.active = postgres"]
Everything is quite similar to the "classic" Dappfile, but there are a few differences. First, developing YAML-syntax, we decided to abandon inheritance and nesting. As practice has shown, inheritance was too complex a feature and from time to time led to misunderstanding. A linear file —such as Dockerfile—is much clearer: it's more like a script, and scripts understand everything.
Secondly, to copy the results of the artifacts, you now use the import in the dimg volume where you want to place the files. There was a slight improvement: if you did not specify to, then the destination path would be the same as specified in add.
What should I look for when I write Dappfile? A common practice in projects with Dockerfile is to decompose different Dockerfile into directories and therefore the paths in the COPY directives are specified relative to these directories. Dappfile is the same, for the project and the paths in the git directive are relative to the root of the repository. The second point is the WORKDIR directive. In Dappfile, directives from the docker family are executed at the last step, so cd is used to go to the desired directory in the stages.
The assembly of a Java application can be broken down into at least two steps: download dependencies and assemble the application. The first step depends on the changes in pom.xml, the second one depends on changes in java-files, descriptors, resources—in general it can be said that a change in the src directory should result in a jar's rebuilding. Dapp offers 4 stages: before_install (where there are no sources), installs, before_setup, setup (where the sources are already accessible by the paths specified in the git directives).
Downloading dependencies can be made more aggressive by specifying for Maven the goal of dependency: go-offline instead of dependency: resolve. This can be a justified decision because pom.xml does not change very often, and dependency: resolve does not download everything and at the stage of application assembly there will be calls to the Maven repository (central or to your nexus/ artifactory/...).
Total, the step of downloading the dependencies can be carried out in the install stage, which will remain in the cache until the changes to pom.xml, and the application assembly, rendered in the setup stage, registering dependencies on the changes in the src directory.
artifact: appserver from: maven:latest git: - add: /app to: /usr/src/atsea stageDependencies: install: ['pom.xml'] setup: ['src'] shell: install: - cd /usr/src/atsea - mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:go-offline setup: - cd /usr/src/atsea - mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests end
Building a React application can also be broken down into two steps: downloading dependencies at the install stage and building the application in the setup stage. Dependencies are described in /app/react-app/package.json.
artifact: storefront from: node:latest git: - add: /app/react-app to: /usr/src/atsea/app/react-app stageDependencies: install: ['package.json'] setup: ['src', 'public'] shell: install: - cd /usr/src/atsea/app/react-app - npm install setup: - cd /usr/src/atsea/app/react-app - npm run build
I draw your attention that the paths in stageDependencies indicating relative to the path are placed in add.
Commit and Cache
Now let's see how stageDependencies work. To do this, you need to make a commit with the change in the java-file and run the assembly DApp dimg build. In the log you will see that only the setup stage is going:
Setup group Git artifacts: apply patches (before setup) ... [OK] 1.7 sec signature: dimgstage-atsea-sample-shop-app:e543a0f90ba39f198b9ae70a6268acfe05c6b3a6e25ca69b1b4bd7414a6c1067 Setup [BUILDING] [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building atsea 0.0.1-SNAPSHOT [INFO] ------------------------------------------------------------------------ ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 39.283 s [INFO] Finished at: 2018-02-05T13:18:47Z [INFO] Final Memory: 42M/355M [INFO] ------------------------------------------------------------------------ Setup [OK] 46.71 sec signature: dimgstage-atsea-sample-shop-app:264aeb0287bbe501798a0bb19e7330917f3ec62b3a08e79a6c57804995e93137 commands: cd /usr/src/atsea mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests building artifact `appserver` [OK] 49.12 sec
If you change pom.xml, commit, and run the build, the install step is rebuilt with dependency downloads and then the setup stage.
Splitting the assembly into two steps for the Java application has cached the dependencies, and now the install stage image acts as a dependency store. However, DApp provides the ability to mount a directory for this type of repository. You can mount from the temporary directory tmp_dir, whose life is one build, you can from build_dir - this is a permanent directory, unique for each project. The documentation lists the directives for Dappfile, and in the case of our application I'll show how to add mounting a subdirectory from build_dir to dappfile.yml:
artifact: appserver from: maven:latest > mount: > - from: build_dir > to: /usr/share/maven/ref/repository git: ... shell: install: ...
If you do not specify the --build-dir flag, then DApp as build_dir creates a ~ / .dapp / builds /<dapp> project directory. In build_dir, after the build, the mount directory appears, in which there will be a tree of mounted directories. The project name is calculated as the name of the directory that contains the Git repository. If projects from the same directory are collected, then the name of the project can be specified with the --name flag, or explicitly specify different directories with the --build-dir flag. In this case, the name DApp will be calculated from the directory where the project's Git repository is stored and therefore ~ / .dapp / builds / atsea-sample-shop-app/mount/usr/share/maven/ref/repository/will be created.
Running Through Compose
Previously, this was not mentioned, but you can use DApp to build but run the project for verification using docker-compose. To run, you'll need to make tags for the images and fix docker-compose.yml to use the images compiled by DApp.
The easiest way to patch images is to run the DApp command dimg tag without flags (other methods and naming schemes of images are in the documentation). The command displays the names of the images with the latest tag. Now you need to fix docker-compose.yml: remove the build directives and add the directives image with the image names from the output DApp dimg tag.
payment_gateway: image: atsea-sample-shop-app/payment-gateway
Now the project can be started with the docker-compose up command (if the build is left for some reason, the no-build flag will help):
In the next part of the article, we'll talk about building an application on ... PHP or Node.js
Opinions expressed by DZone contributors are their own.