Skip to content

Latest commit

 

History

History
390 lines (317 loc) · 14.1 KB

File metadata and controls

390 lines (317 loc) · 14.1 KB

QuantumMaid - Create your first application

This tutorial will teach you how to create a basic QuantumMaid application. It covers:

  • Implementing a usecase
  • Exporting the implemented usecase via HTTP
  • Writing an integration test
  • Packaging the application

To follow the tutorial, you need:

  • JDK 11+
  • Apache Maven >3.5.3

Make sure that Maven uses the correct Java version:

$ mvn -version

1. What we are going to do

In the next 15 minutes, we will create a simple application. It should offer the trivial usecase of greeting the user with a simple "Hello" message. The usecase will be implemented as a plain Java class by the name GreetingUseCase. In order to serve the GreetingUseCase via the HTTP protocol we will create a WebService class. Furthermore, we will write an integration test. Finally, the application is packaged as an executable .jar file.

2. Bootstrapping the project

QuantumMaid does not require your project to follow a specific format. To start the tutorial, just run the following command:

mvn archetype:generate \
    --batch-mode \
    -DarchetypeGroupId=de.quantummaid.tutorials.archetypes \
    -DarchetypeArtifactId=basic-archetype \
    -DarchetypeVersion=1.0.44 \
    -DgroupId=de.quantummaid.tutorials \
    -DartifactId=basic-tutorial \
    -Dversion=1.0.0 \
    -Dpackaging=java
cd ./basic-tutorial

If you are following the tutorial on a Windows computer, you can alternatively use this command:

mvn archetype:generate ^
    --batch-mode ^
    -DarchetypeGroupId=de.quantummaid.tutorials.archetypes ^
    -DarchetypeArtifactId=basic-archetype ^
    -DarchetypeVersion=1.0.31 ^
    -DgroupId=de.quantummaid.tutorials ^
    -DartifactId=basic-tutorial ^
    -Dversion=1.0.0 ^
    -Dpackaging=java
cd ./basic-tutorial

It generates the following in ./basic-tutorial:

  • The Maven structure
  • An empty class de.quantummaid.tutorials.GreetingUseCase
  • An empty class de.quantummaid.tutorials.WebService
  • An empty test class de.quantummaid.tutorials.GreetingTest (under /src/main/test)

Once generated, look at the pom.xml file. In order to use QuantumMaid for creating web services, you need to add a dependency:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>de.quantummaid.quantummaid</groupId>
            <artifactId>quantummaid-bom</artifactId>
            <version>1.1.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>de.quantummaid.quantummaid.packagings</groupId>
        <artifactId>quantummaid-essentials</artifactId>
    </dependency>
</dependencies>

We also added a BOM so you do not have to specify version numbers.

3. The first usecase

To start the project, modify the GreetingUseCase class to contain the following content:

package de.quantummaid.tutorials;

public final class GreetingUseCase {

    public String hello() {
        return "hello world";
    }
}

It’s a very simple usecase, returning "hello world" to all invocations of hello().

Note: The usecase class does not contain any references or imports to actual web technology like JXR-RS annotations. It is completely infrastructure agnostic.

4. Exporting the usecase

Since the GreetingUseCase class does specify how the usecase should be served using HTTP, that particular aspect needs to be configured outside of the class. Since the GreetingUseCase class does not specify how the usecase should be served using HTTP, that particular aspect needs to be configured outside of the class (do not add it to the project yet):

QuantumMaid.quantumMaid()
        .get("/helloworld", GreetingUseCase.class);

The configuration binds the GreetingUseCase to GET requests to /helloworld, which will then be answered with "hello world".

In order to run our application, we need to bind QuantumMaid to a port. This can be done by modifying the WebService class like this:

package de.quantummaid.tutorials;

import de.quantummaid.quantummaid.QuantumMaid;

public final class WebService {
    private static final int PORT = 8080;

    public static void main(final String[] args) {
        createQuantumMaid(PORT).run();
    }

    public static QuantumMaid createQuantumMaid(final int port) {
        return QuantumMaid.quantumMaid()
                .get("/helloworld", GreetingUseCase.class)
                .withLocalHostEndpointOnPort(port);
    }
}

Now we can run the application. Just start it normally by executing the main()-method. Once started, you can verify that it works as intended like this:

$ curl http://localhost:8080/helloworld
"hello world"

Note: With QuantumMaid, there is no need to add framework-specific annotations (JAX-RS, Spring WebMVC, etc.) to your classes. Their usage drastically decreases application start-up time and promotes less-than-optimal architecture. When done architecturally sane, they tend to come along with significant amounts of boilerplate code. Please refer to our in-depth analysis of the problem for details.

Note: Skip to the bottom of this tutorial for real-world deployments like Docker and AWS Lambda.

5. Mapping request data

Prerequisite (only necessary if you did not use the provided archetype): The following step requires your application to be compiled with the -parameters compile option. Doing so gives QuantumMaid runtime access to parameter names and enables it to map parameters automatically. If you bootstrapped the tutorial from the provided archetype, this option is already set. Otherwise, you need to configure it on your own. Also make sure that your IDE correctly adopted the -parameters option. There are straightforward guides for IntelliJ IDEA and Eclipse in case you need to set it manually (note that it is a compiler option - not a VM option).

Let's make the GreetingUseCase slightly more complex by adding a parameter to its hello() method:

package de.quantummaid.tutorials;

public final class GreetingUseCase {

    public String hello(final String name) {
        return "hello " + name;
    }
}

Modify the WebService class to resolve the name parameter:

package de.quantummaid.tutorials;

import de.quantummaid.quantummaid.QuantumMaid;

public final class WebService {
    private static final int PORT = 8080;

    public static void main(final String[] args) {
        createQuantumMaid(PORT).run();
    }

    public static QuantumMaid createQuantumMaid(final int port) {
        return QuantumMaid.quantumMaid()
                .get("/hello/<name>", GreetingUseCase.class)
                .withLocalHostEndpointOnPort(port);
    }
}

Stop the old running application and start it again with the new functionality:

$ curl http://localhost:8080/hello/quantummaid
"hello quantummaid"

Explanation: We configured QuantumMaid to map a so-called HTTP path parameter to the name parameter. Requests to /hello/quantummaid should result in the method being called as hello("quantummaid"), requests to /hello/frodo as hello("frodo), etc. In traditional application frameworks, we achieve this by annotating the name parameter with something like the JAX-RS @PathParam annotation. Since QuantumMaid guarantees to keep your business logic 100% infrastructure agnostic under all circumstances, this is not an option.

6. Testing

Please add the following test dependency to your pom.xml:

<dependency>
    <groupId>de.quantummaid.quantummaid.packagings</groupId>
    <artifactId>quantummaid-test-essentials</artifactId>
    <scope>test</scope>
</dependency>

Implement the empty de.quantummaid.tutorials.GreetingTest test class like this:

package de.quantummaid.tutorials;

import de.quantummaid.quantummaid.QuantumMaid;
import de.quantummaid.quantummaid.integrations.junit5.QuantumMaidTest;
import de.quantummaid.quantummaid.integrations.testsupport.QuantumMaidProvider;
import org.junit.jupiter.api.Test;

import static de.quantummaid.tutorials.WebService.createQuantumMaid;
import static io.restassured.RestAssured.given;
import static org.hamcrest.core.Is.is;

@QuantumMaidTest
public final class GreetingTest implements QuantumMaidProvider {

    @Override
    public QuantumMaid provide(final int port) {
        return createQuantumMaid(port);
    }

    @Test
    public void testGreeting() {
        given()
                .when().get("/hello/quantummaid")
                .then()
                .statusCode(200)
                .body(is("\"hello quantummaid\""));
    }
}

Now you can run the test and verify that the application indeed behaves correctly.

Explanation: By declaring the @QuantumMaidTest annotation, QuantumMaid will automatically create an application instance for every test and configure REST Assured accordingly. This way, you don't have to provide a URL in your tests or worry about port allocation and cleanup.

Warning: This tutorial uses the REST Assured library because it is well known and allows for very readable test definitions. Despite its widespread use, REST Assured introduces the vulnerabilities CVE-2016-6497, CVE-2016-5394 and CVE-2016-6798 to your project. Please check for your project whether these vulnerabilities pose an actual threat.

7. Packaging the application

QuantumMaid applications can be packaged in exactly the same way as every other normal Java application. A common way to achieve this would be to use the maven-assembly-plugin. All you need to do is to insert the following code into the <plugins>...</plugins> section of your pom.xml:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.3.0</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
            <configuration>
                <finalName>quantummaid-app</finalName>
                <appendAssemblyId>false</appendAssemblyId>
                <archive>
                    <manifest>
                        <mainClass>
                            de.quantummaid.tutorials.WebService
                        </mainClass>
                    </manifest>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
        </execution>
    </executions>
</plugin>

You can now tell Maven to package your application:

$ mvn clean package

Afterwards, you will find a fully packaged executable .jar file under target/quantummaid-app.jar. To start it, just run:

$ java -jar target/quantummaid-app.jar

8. Bonus: Adding a POST route

In order to serve the GreetingUseCase via the POST HTTP method, you would have to modify the WebService class like this:

package de.quantummaid.tutorials;

import de.quantummaid.quantummaid.QuantumMaid;

public final class WebService {
    private static final int PORT = 8080;

    public static void main(final String[] args) {
        createQuantumMaid(PORT).run();
    }

    public static QuantumMaid createQuantumMaid(final int port) {
        return QuantumMaid.quantumMaid()
                .get("/hello/<name>", GreetingUseCase.class)
                .post("/hello", GreetingUseCase.class)
                .withLocalHostEndpointOnPort(port);
    }
}

Try it out with the curl command (don't forget to restart the application to reflect the new changes):

$ curl -X POST -H "Content-Type: application/json" --data '{"name": "quantummaid"}'  http://localhost:8080/hello/
"hello quantummaid"

Note how the name parameter is now sent in a JSON body. Also note that we did not modify anything in the GreetingUseCase, we only changed how it is served.

You can add a test for the new route in GreetingTest like this:

@Test
public void testGreetingPost() {
    given()
            .when()
            .body("{ \"name\": \"quantummaid\" }").post("/hello")
            .then()
            .statusCode(200)
            .body(is("\"hello quantummaid\""));
}

9. What's next?

If you are interested in packaging a QuantumMaid application for specific targets, simply follow one of our advanced tutorials:

Coming soon: Packaging for AWS Lambda

Coming soon: Packaging for Docker/Kubernetes

Coming soon: Packaging for Tomcat/JBoss/Glassfish

10. Did you like what you just read?

Please do not hesitate to share your thoughts and criticism with us! We are always and directly available on Slack and Gitter. Every single piece of feedback will be accepted gratefully and receive undivided attention from our entire development team. Additionally, anyone who gives feedback will be mentioned in our contributors' list (unless you prefer to stay anonymous, of course).

Last but not least, please do not forget to follow us on Twitter!