0

this is going to be a longer question due to the required information to make it clear. I am currently writing a Microservice Application in Java with each Application running on an OpenLiberty Server and containerized using docker, so far so good.

This is the first time for me writing such an application and I am having to learn a lot of things at once which is fine, my Problem is the proper understanding of how to actually get the applications to run correctly on the OpenLiberty Server.

To be more precise it seems to me like either the ports I am exposing are not exposing correctly or my Source Code is causing the server to act weirdly.

I will give you an example of two classes within one of the services which, the source code is the following:

package com.coffeeshop.microservice;

import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.agent.model.NewService;
import com.fasterxml.jackson.core.JsonProcessingException;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.sql.SQLException;
import java.util.UUID;
import java.util.logging.Logger;

import jakarta.annotation.PostConstruct;

/**
 * A RESTful web service that exposes endpoints related to order creation and management.
 */

@Path("/orders")
public class OrderFacade {

    @Inject
    private OrderService orderService;

    /**
     * Creates a new order using the specified order details.
     * @param order the order details.
     * @return a response indicating whether the order was created successfully.
     * @throws JsonProcessingException if there was an error serializing the order to JSON.
     * @throws SQLException if there was an error creating the order in the database.
     */
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response createOrder(Order order) throws JsonProcessingException, SQLException {
        // Call the createOrder method of the OrderService class
        Response createdOrder = orderService.createOrder(order);

        // Check if the order was successfully created
        if (createdOrder.getStatus() == Response.Status.CREATED.getStatusCode()) {
            // Forward the order to the product service
            orderService.forwardOrder("http://localhost:9081/product-service/process-order", order);
        }

        return createdOrder;
    }

}

and class number two:

package com.coffeeshop.microservice;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.core.MediaType;

import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * @author Sean Bäker
 * @version 1.0
 * @since April.2023
 * This class represents the Order Service for the CoffeeShop microservice architecture.
 * It handles the creation of new orders, as well as forwarding the order to the Product Service
 * for further processing.
 * The Order Service is implemented as a RESTful web service using JAX-RS and the Jakarta RESTful Web Services API.
 */



@Path("/orders")
public class OrderService{

    String SQL = "INSERT into orders (item) VALUES(?)";

    public String connectionPoolUrl;

    /**
     * Constructor for the OrderService class.
     * @param connectionPoolUrl The URL of the connection pool service.
     */

    public OrderService(String connectionPoolUrl){
        this.connectionPoolUrl = connectionPoolUrl;
    }


    /**
     * This method creates a new order and forwards it to the Product Service for further processing.
     * @param order The Order object representing the new order to be created.
     * @return A JAX-RS Response object indicating the status of the order creation process.
     */
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/create-order")
    public Response createOrder(Order order){
        Connection conn;
        try {
            conn = getConnectionFromPool("http://localhost:9084/connection-pool/get-connection");
            PreparedStatement pstmt = conn.prepareStatement(SQL);
            pstmt.setString(1, order.getItem());
            pstmt.executeUpdate();

            forwardOrder("http://localhost:9081/product-service/process-order", order);

            return Response.status(Response.Status.CREATED).entity(order).build();

        } catch (SQLException sqlException) {
            //Fluentd LOGGER is to be added here
            sqlException.printStackTrace();
        }
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
    }

    /**
     * This method forwards an order to the specified URL using an HTTP POST request.
     * @param url The URL to which the order is to be forwarded.
     * @param order The Order object representing the order to be forwarded.
     */

    public void forwardOrder(String url, Order order){

        String json = serializeJson(order);

        try {
            OkHttpClient ok = new OkHttpClient();
            RequestBody requestBody = RequestBody.create(MediaType.APPLICATION_JSON, okhttp3.MediaType.parse(json));
            okhttp3.Request request = new okhttp3.Request.Builder()
                    .url(url)
                    .post(requestBody)
                    .build();
            try (okhttp3.Response response = ok.newCall(request).execute()){
                if (!response.isSuccessful()) {
                    throw new IOException("Unexpected code " + response);
                }
            }

        } catch(IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * This method serializes an Order object into JSON format.
     * @param order The Order object to be serialized.
     * @return A String representation of the Order object in JSON format.
     */
    public String serializeJson(Order order){

        try {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(order);
        } catch(JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Gets a connection from the connection pool service using a REST API call.
     * @param url the URL of the connection pool service
     * @return a Connection object from the connection pool
     * @throws SQLException if a database access error occurs
     */
    private Connection getConnectionFromPool(String url) throws SQLException{
        jakarta.ws.rs.core.Response response = ClientBuilder.newClient()
                .target(url)
                .path("get-connection")
                .request(MediaType.APPLICATION_JSON)
                .get();
        return response.readEntity(Connection.class);
    }

}

Now what these classes are responsible is a.) the orderfacade is supposed to be the entrypoint for the user to reach http://localhost:9080/orders at which he should be able to place an order which is a single item within an JSON in this form: '{"item":"Latte"}'.

Now when I package my application in dockerfiles which generally look like this:

FROM icr.io/appcafe/open-liberty:kernel-slim-java8-openj9-ubi
COPY --chown=1001:0  src/main/liberty/config/server.xml /config/
COPY --chown=1001:0 target/orderservice-microservice.war /config/dropins/

I can reach http://localhost:9080 but not the /orders endpoint, here I mostly get ERR_EMPTY_RESPONSE. For my understanding I have configured the server correctly, the server.xml for the OpenLiberty server for the shown service above is:

<?xml version="1.0" encoding="UTF-8"?>
<server description="${project.name}">

    <httpEndpoint id="defaultHttpEndpoint"
                  host= "*"
                  httpPort="9080"
                  httpsPort="9443"/>

    <webApplication location="${project.name}.war" contextRoot="${app.context.root}">
        <classloader apiTypeVisibility="+third-party" />
    </webApplication>
    <mpMetrics authentication="false"/>

    <!-- This is the keystore that will be used by SSL and by JWT. -->
    <keyStore id="defaultKeyStore" location="public.jks" type="JKS" password="atbash" />


    <!-- The MP JWT configuration that injects the caller's JWT into a ResourceScoped bean for inspection. -->
    <mpJwt id="jwtUserConsumer" keyName="theKeyId" audiences="targetService" issuer="${jwt.issuer}"/>

</server>

I have already tried exposing different ports, opening the ports on the firewall settings in my windows machine. But nothing seems to be working.

1 Answers1

2

A few comments:

  1. I dont see your Application class - you should have class like this, which has rest path - /inventory in this case:
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("/inventory")
public class XYZApplication extends Application {

}

  1. In your server.xml - you have:
    <webApplication location="${project.name}.war" contextRoot="${app.context.root}">
        <classloader apiTypeVisibility="+third-party" />
    </webApplication>

which is using variable ${app.context.root} which is probably not found in your docker environment - I'd suggest to use fixed value there.

  1. If you have webApplication in your server.xml it should be copied to app folder NOT dropins. dropins is for apps not defined in server.xml.
    Your dockerfile should have this:
COPY --chown=1001:0 target/orderservice-microservice.war /config/apps
  1. I dont see what <featues> you have enabled. Typically for REST app you should have:
    <featureManager>
        <feature>restfulWS-3.0</feature>
        <feature>jsonb-2.0</feature>
        <feature>jsonp-2.0</feature>
        <feature>cdi-3.0</feature>
    </featureManager>
  1. And after all this is configured correctly your service should be reachable on the following url:
http://yourhostname:9080/contextRoot/applicationPath/orders
  1. Additional issue is that you have 2 resources on the same path:
@Path("/orders")
public class OrderService{

@Path("/orders")
public class OrderFacade {

you cannot have that. Also you should not @Inject rest service in another rest service.

If you are starting with OpenLiberty and microservices, I'd strongly advise you to look at very good guides and tutorials here - https://openliberty.io/guides/
For a start for example https://openliberty.io/guides/rest-intro.html

Gas
  • 17,601
  • 4
  • 46
  • 93
  • Thank you for the good and in-depth answer I will definetly look into the links you provided me with! – SeanBaker Apr 25 '23 at 09:59
  • Hey just wanted to come back and thank you loads for hinting me to the right resources and giving me some tips on what to do, the application is now running as intended, thank you very much! – SeanBaker Apr 25 '23 at 13:33
  • @SeanBaker Great! Good job fixing the issues ;-) – Gas Apr 25 '23 at 16:12