Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Step-By-Step Guide On How To Build a Project With DApps Part 1: Java

DZone's Guide to

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!

· Cloud Zone ·
Free Resource

Site24x7 - Full stack It Infrastructure Monitoring from the cloud. Sign up for free trial.

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.

Improved Build

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.

Dependencies

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.

For example:

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):

Image title

Image title


In the next part of the article, we'll talk about building an application on ... PHP or Node.js

Site24x7 - Full stack It Infrastructure Monitoring from the cloud. Sign up for free trial.

Topics:
java ,react ,dapper ,dapp ,cloud

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}