(Un)Expected struggle in a small personal project and why that is a good thing

Anton Giertli
12 min readJan 7, 2019

Couple of weeks ago I decided to create a small (coding) project. The motivation was plentiful and here some of the key factors (in no particular order):

  • I just started a new job and I wanted to prove to myself that my coding skills are still there
  • I was traveling home for a Christmas holiday and I come from a fairly small city. So instead of risking boredom I wanted to make sure I will be occupied
  • I gained a lot of certificates in my professional career and by now I know that unless I start to practice the gained knowledge I tend to forget things pretty quickly. That’s why I don’t list Infinispan on my CV and that’s why I do list Docker on my CV. I have passed certificate for Red Hat Certified Enterprise Microservices Developer recently and I really enjoyed the Eclipse MicroProfile specification and I wanted to make sure this one is going to stick with me.

Idea: Inception

The motivation was there but I still lacked an idea. From the technical perspective I knew I wanted to bring some new things into the picture which I haven’t had the chance to practice so much in the past. The more uncomfortable, the better. Thorntail, mongodb and docker was the bare minimum. This is the opposite of my usual stack which consist of Relational Database, WildFly and on-premise deployment. I also knew I want to expose some REST API and that I also want to consume some external public REST API.

But I still wasn’t sure about the domain of my project. I felt like it was necessary to combine it with some my hobbies to make sure I will enjoy working on such project. I do listen to tons of music so I started to explore public APIs of last.fm, discogs.com, facebook event api. Unfortunately, the idea still didn’t come. It was only when I checked goout.net when the initial spark occurred! GoOut.net is a popular ticket site in the Czech Republic. It sells tickets to the the various music and cultural events in many cities across Czech Republic (and as of recently even in Poland and Germany). Unfortunately goout.net doesn’t have any sort of public API (at least not documented). I didn’t want to give up so easy, so just before I used search bar on goout.net I clicked Inspect -> Network in my Chrome browser. Jackpot.

“Secret” API discovery

It turned out goout.net uses very simple to consume, public facing, REST API producing well formatted JSON. Despite the lacking documentation it was something I knew I could use after few attempts. Let me present you with my final project idea:

Create an application which will allow user to follow his favourite artists in his selected city. If any of the followed artists will have show/concert in that city, let the user know (either by automated email notification or by allowing user to display such events using UI).

It all looked pretty simple and straightforward in my head and I thought I will be finished by the next day :) Boy I was wrong!

Disclaimer: Even though I didn’t think it would matter but since this API isn’t anything officially documented I checked with goout.net support personnel whether it’s OK for me to consume it. They confirmed it was OK :-)

Here comes the struggle

I am not sure if it’s just me but most of the time I code the majority of time I spent in the brainstorming phase, followed closely by failing unit/integration tests phase.

Bye Bye fabric8-maven-plugin

Probably the first serious problem came when I decided to use fabric8-maven-plugin for producing Docker images for the backend of my application. It sounded really cool in my head that consumer of this app will be able to build it with single maven command. Even though I never intended this project to go into “production” (i.e. to be publicly hosted) I still created some artificial constraints to make it a bit harder for me. For example, I wanted to make sure it will be easy to install and run for anybody. fabric8-maven-plugin was supposed to achieve just that. Unfortunately the results of docker build were inconsistent when using f8-m-p. Sometimes the build would fail complaining about “Unknown ADD instruction” and all I did was to change some System.out.println("hello"); statement in the code.The only remedy was to “rm -rf backend/” and “git clone http://github.com..” again. This turned out to be very frustrating and since this project was quite simple it made me question the stability and usability of f8-m-p.

Solution? I dropped fabric8-maven-plugin (and also spotify docker maven plugin) and created my own, very simple Dockerfile and was happy ever since.

Dockerfile:

FROM java:openjdk-8-jdk
ADD target/goout-stalker-backend-thorntail.jar /opt/goout-stalker-backend-thorntail.jar
EXPOSE 8080
CMD java -jar /opt/goout-stalker-backend-thorntail.jar

Installation:

cd goout-stalker-backend && mvn clean package -DskipTests
docker build -t goout-stalker-backend .

Looks pretty simple to me!

This was still “prototype” phase and I’ve got it confirmed once again that it’s OK to try things and then say goodbye to them if they turned out as not feasible. Bye bye fabric8-maven-plugin!

MongoDB — what’s taking you so long?

Another struggle when I started to work on dockerizing my solution was that MongoDB docker container takes really, really long time (more than minute!) to be properly booted. My application requires up and running MongoDB instance in order to function properly but I didn’t want to pollute my code by implement polling mechanism into my application code, instead I wanted to delegate it to docker itself.

I decided to solve this issue by using a small wait utility — my backend container now starts only after mongodb is fully initialized.

Updated Dockerfile:

FROM java:openjdk-8-jdk
ADD target/goout-stalker-backend-thorntail.jar /opt/goout-stalker-backend-thorntail.jar
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.2.1/wait /wait
RUN chmod +x /wait
EXPOSE 8080
CMD /wait && java -Djava.net.preferIPv4Stack=true -jar /opt/goout-stalker-backend-thorntail.jar

I have also switched to docker-compose since I am now working with multiple containers (mongodb+backend) and the relevant section of composer file looks like this:

services:
app:
env_file:
- goout-stalker.env
container_name: "goout-stalker"
image: "goout-stalker-backend"
environment:
<<: *common-variables
WAIT_HOSTS: mongodb:27017
WAIT_HOSTS_TIMEOUT: 90

DB_HOST: mongodb
depends_on:
- mongodb
ports:
- "8080:8080"

Alternative (and probably much more suitable solution) would be to use Healthchecks. But hey, it’s working now, so I won’t touch it!

Also, did you know that using curl for implementing healthcheck is considered as bad practice for some people? Very interesting perspective indeed!

Performance issues in the app used by one user. Wait, WHAT?!

This really caught me by surprise. I didn’t envision that I would be dealing with performance issues when I am literally the only user of this application. But hey, it’s another challenge, another opportunity to learn something new.

This performance issue was really inevitable — I didn’t know it when I first thought of this app, but as soon as I started coding I realised it was waiting to happen. Let me explain.

In order to check for new events of the selected artist I need to call following goout.net API:

curl 'https://goout.net/services/feeder/v1/events.json?source=goout&keywords=ARTIST1&language=cs&unapproved=false&clearDomain=true'

But if my user is following 3 artists at once I need to fetch event list like this:

curl 'https://goout.net/services/feeder/v1/events.json?source=goout&keywords=ARTIST1&language=cs&unapproved=false&clearDomain=true'
curl 'https://goout.net/services/feeder/v1/events.json?source=goout&keywords=ARTIST2&language=cs&unapproved=false&clearDomain=true'
curl 'https://goout.net/services/feeder/v1/events.json?source=goout&keywords=ARTIST3&language=cs&unapproved=false&clearDomain=true'

The point is — goout.net doesn’t expose API which would accept multiple artists at once (trust me, I tried it!) and I have absolutely no control over it, since it’s an external service. It didn’t look bad in integration tests but when I built my hello-world web page with big shiny button “Display Events” I’ve realised it’s going to be a problem. And (unsurprisingly) it was.

I clicked the button. Nothing happened. The web page didn’t do anything. I waited for 3 seconds. I waited another seconds. Results are injected in the web page. Not exactly the best UX. Not even for standards of a personal project. There are really two sides to this issue:

  • I need to inform the user that when he clicks the button something is actually going on (My girlfriend is a UX designer so I trust her on this) — no matter how fast my backend is going to be, there still will be some latency.
  • I really really need to execute those requests in parallel on the backend side

I don’t really enjoy working on the UI, I am more of a backend oriented developer. However, since this was mainly a learning experience for me I decided to don’t stop once my backend was finished, and at least try to create something for the end user. I went with the ancient stack of html+css(bootstrap v4)+jQuery. I know I am 10 years late to the party but whatever floats your boat I guess.

When user clicks that button an overlay with loader icon is added. Once the function is finished, the overlay disappears. Simple. Effective. Especially for a guy like me who really, and I mean really doesn’t enjoy working on UI.

User finally have a feedback about his actions

This was a good enough solution for me. All that remained was to try to execute those requests on backend concurrently so the overall request time would be shortened. My application is a Java based one and for invocation of the GoOut.net REST API I was using JAX-RS API. I vaguely remembered that there was an option to invoke requests asynchronously using async() method. And for reasons unknown to me, I wondered about this and ruled out this as not feasible. Asynchronicity doesn’t necessarily mean concurrency and I think that somehow confused me.

After ruling out jax-rs async option (spoiler alert: I shouldn’t have) I started looking at Java Executor API. I’ve read few good articles on this topic because I was trying to utilise ‘new’ additions to this API which were added in Java 8 — mainly CompletableFuture. Also this one was quite useful and of course, good old friend stackoverflow. I’ve created a working (at least that’s what I thought!) proof of concepts (not using jax-rs client just yet) but the idea was there. It looked similar to this:

long start = System.currentTimeMillis();AsyncGoOutSupplier<Set<Event>> first = new AsyncGoOutSupplier<Set<Event>>();
AsyncGoOutSupplier<Set<Event>> second = new AsyncGoOutSupplier<Set<Event>>();
AsyncGoOutSupplier<Set<Event>> third = new AsyncGoOutSupplier<Set<Event>>();
CompletableFuture<Set<Event>> result1 = CompletableFuture.supplyAsync(first, managedExecutorService);
CompletableFuture<Set<Event>> result2 = CompletableFuture.supplyAsync(second, managedExecutorService);
CompletableFuture<Set<Event>> result3 = CompletableFuture.supplyAsync(third, managedExecutorService);
List<CompletableFuture<Set<Event>>> futureInput = new ArrayList<CompletableFuture<Set<Event>>>();
futureInput.add(result1);
futureInput.add(result2);
futureInput.add(result3);
CompletableFuture<Void> allFutures = CompletableFuture
.allOf(futureInput.toArray(new CompletableFuture[futureInput.size()]));
CompletableFuture<List<Set<Event>>> allPageContentsFuture = allFutures.thenApply(v -> {
return futureInput.stream().map(pageContentFuture -> pageContentFuture.join()).collect(Collectors.toList());
});
List<Set<Event>> finalResult = allPageContentsFuture.get();for (Set<Event> set : finalResult) {set.forEach(e -> {System.out.println("event:" + e.getCity());
});
}
long parallelEnd = System.currentTimeMillis()-start;System.out.println("parallel execution took:" + parallelEnd);

And AsyncSupplier looked like this:

package org.goout.stalker.service.goout;

import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Random;
import java.util.function.Supplier;

import org.goout.stalker.model.Event;

public class AsyncGoOutSupplier<Set> implements Supplier<java.util.Set<Event>> {

public AsyncGoOutSupplier() {

}

@Override
public java.util.Set<Event> get() {

byte[] array = new byte[7]; // length is bounded by 7
new Random().nextBytes(array);
String generatedString = new String(array, Charset.forName("UTF-8"));
java.util.Set<Event> result = new HashSet<Event>();
Event e = Event.builder().withCity(generatedString).build();
result.add(e);

return result;
}

}

Now it was time to replace the above get() method with actual code invoking GoOut.net API and I started to do some further research as some doubts were starting to creep in. Many hours later I realised and found out following:

  • jax-rs Clients are heavy-weight objects. It’s not performance friendly to create a new Client for every concurrent requests — even though this solution would work, you wouldn’t gain the expected performance increase
  • jax-rs 2.0 spec doesn’t say anything about thread safety. This entirely depends on the underlying implementation of the spec (Jersey, RESTEasy, etc..)
  • By default, RESTEasy client is not a pooling client. There is only *one* connection available. Concurrent requests are simply not possible with the default config as you would end up with Invalid use of BasicClientConnManager: connection still allocated error. And I did.

I have decided that maybe it’s another time to discard this prototype. It’s tough to delete something which you’ve spent few hours on, but just like with fabric8-maven-plugin it turned out to be a good thing. After saying goodbye to this PoC and I went back to jax-rs async option one more time to see whether it wouldn’t be feasible this time. First draft looked like this:

public EventsByArtists getEventsAsync(ArtistList artists, String city)
throws InterruptedException, ExecutionException {
Map<String, Future<Response>> allResponse = new HashMap<String, Future<Response>>();
EventsByArtists events = new EventsByArtists();
artists.getArtists().forEach(a -> {try {
Future<Response> response = ClientBuilder.newBuilder().build()
.target(new URL(String.format(config.URL_BASE(), a)).toExternalForm())
.request(MediaType.APPLICATION_JSON_TYPE).async().get();
allResponse.put(a, response);
} catch (MalformedURLException e) {
e.printStackTrace();
}
});
for (String resultArtist : allResponse.keySet()) {
Response r = allResponse.get(resultArtist).get(); //This is blocking call
String json = r.readEntity(String.class);
r.close(); //TODO call this in finally block
Set<Event> eventsByArtist = transformToEvent(resultArtist, json, city);
events.addEvents(resultArtist, eventsByArtist);
}return events;
}

First, I kick off all the requests in the loop and only after all the requests are initiated I am looping through Future<Response> objects and calling get() method in order to fetch result. I can only return the response to the client once all the requests has been finished and processed. This is probably a simple realisation for many of you, but just to be sure:

artists.getArtists().forEach(a -> {
Future<Response> response = ClientBuilder.newBuilder().build()
.target(new URL(String.format(config.URL_BASE(), a)).toExternalForm())
.request(MediaType.APPLICATION_JSON_TYPE).async().get();
response.get();
});

The loop above would act synchronously because the response.get() is a blocking call. So don’t do it :)

There is only one issue to solve — I am still creating new Client instances for every request and we I’ve learned previously that’s a heavy weight operation.

I decided to create a Singleton RESTClient and attempt to configure it with pool of connections. Seemed like a pretty straightforward task to me. I have found this piece of code somewhere which I was hoping it would achieve it:

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
cm.setMaxTotal(200); // Increase max total connection to 200
cm.setDefaultMaxPerRoute(20); // Increase default max connection per route to 20
ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
ResteasyClient client = new ResteasyClientBuilder().httpEngine(engine).build();

This caused all sorts of hell when deployed on top of Thorntail. Example of classloading hell #1, classloading hell #2. I knew I was getting very close so I kept searching. I have found this and I was sure it’s going to work as it was part of the official QE Test Suite. Unfortunately still no luck — I was back to Invalid use of BasicClientConnManager: connection still allocated.

Unfortunately jax-rs spec doesn’t dictate anything about connection pool. At the time of writing this article, configuring such pool is still implementation specific thing. I decided to take a look into the documentation of the jax-rs implementation I was using — RESTEasy and found this method — connectionPoolSize(int poolSize).

Bingo! I actually have a pool of connection and my client can finally be used concurrently. Final client solution look like this:

package org.goout.stalker.rest;import javax.annotation.PostConstruct;import javax.annotation.Resource;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.enterprise.concurrent.ManagedExecutorService;
import javax.inject.Inject;
import org.goout.stalker.config.GlobalConfig;
import org.jboss.resteasy.client.jaxrs.RestetsyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
@Singleton
@Startup
public class SingletonRESTClient {
@Inject
private GlobalConfig config;
private ResteasyClient client;
@Resource
private ManagedExecutorService managedExecutorService;
@PostConstruct
public void init() {
client = new ResteasyClientBuilder()
.connectionPoolSize(Integer.valueOf(config.REST_CLIENT_CONNECTION_POOL_SIZE())).asyncExecutor(managedExecutorService).build();
}public ResteasyClient getClient() {
return client;
}
@PreDestroy
public void shutdown() {
this.client.close();
}
}

And in my async method I have only added two lines:

@EJB
private SingletonRESTClient singletonClient;
...
Future<Response> response = singletonClient.getClient()...

I did some further tests but both, integration tests and testing manually with UI yield positive results — the performance was significantly increased:

@Test
public void testParallelRest() throws InterruptedException, ExecutionException {
Set<String> input = new HashSet<String>();
input.add("dné");
input.add("Aurora");
input.add("Jedi Mind Tricks");
input.add("Jessie");
input.add("LP");
ArtistList artists = new ArtistList(input);long start = System.currentTimeMillis();
service.getEvents(artists, "Prague");
long serialDuration = (System.currentTimeMillis() - start);
System.out.println("The sync api call took:" + serialDuration);

start = System.currentTimeMillis();
service.getEventsAsync(artists, "Prague");
long asyncDuration = (System.currentTimeMillis() - start);
System.out.println("The async api call took:" + asyncDuration);

assertTrue(asyncDuration < serialDuration); //otherwise it doens't make sense
}

And the output:

[0m[0m2019-01-07 11:33:54,313 INFO  [stdout] (nioEventLoopGroup-3-1) The sync api call took:1710[0m[0m2019-01-07 11:33:54,780 INFO  [stdout] (nioEventLoopGroup-3-1) The async api call took:467

Case closed!

Why that was a good thing

I’ve had much more issues than those I have described above. I struggled with failing unit tests, integration tests, various NullPointerExceptions, CSS styling, javascript being unreasonable, etc. However I did manage to overcome most of these and while my project is not production-grade enterprise software (on the contrary) I still enjoy the overall result.

The fact I purposefully injected new technologies and conditions made it rather uncomfortable at times but it is also the very reason why I also enjoyed it so much and learned tons of new stuff. Overcoming these issues is something which make the project development exciting in general and I think most of the devs out there can relate to that.

The project is published on github in case you are curious.

On to the next challenge!

--

--