Vert.x / Guice / jOOQ / ULTM

When you are choosing Vert.x as your main framework you have to be fully asynchronous, they said. JDBC? Forget it! You should avoid thread blocking. There are only few of them, so you cannot tell them to stop.

Anyway, Vert.x allows running blocking code, so why not to try it? Let’s block some threads and go back to developer’s comfort zone where SQL queries run one after another. What’s more we will use Transactions to keep our data consistent. Sounds like going back to JEE? Maybe. Fasten your seatbelts!

But first… story about potato (you can skip it if you don’t like potatoes…).

Potatoes and winter

According to potatopro.com Poland is one of the world’s biggest potatoes producer. What is more, winter is coming… Everyone in Poland knows, that in order to survive winter you need to have your basement full of potatoes.

When you buy potatoes you buy them in bags, potato bags - 10 kilos each. With potato bags comes huge risk. If inside the bag there is at least one rotten potato, then sooner or later all of them will be rotten. Winter will be long and you will die…

Basically, what you have to do is to open potato bag before the purchase and check whether potatoes are fresh or not. If one is rotten - then whole bag goes to trash. To summarize:

You cannot have potato bag without checking all of them.

Caulo Poelho

I hope it all sounds like SQL transaction… First you store a bag. Then you store potatoes one by one. If rotten potato is found, you withdraw transaction and neither bag nor potatoes is stored in basement.

Back to code

Whole example is available on Github. In this post I will show only the most interesting parts.

This is project structure:

-- 
  -- conf
  -- scripts
  -- potato-db
  -- potato-logic
  pom.xml
  Makefile

conf - properties for Maven and Vert.x

scripts - linux scripts for Database creation (PostgresSQL)

potato-db - all DB related files and code. More bellow.

potato-logic - Verticle with all “Enterprise” logic for storing potatoes into basement :)

pom.xml - parent pom

Makefile - useful commands

potato-db

Let’s start from potato-db. There’s a lot of stuff!

First of all, look inside pom.xml. I used Liquibase for managing changes in Database structure. If you are not familiar with Liquibase, you should at least know, that all Tables structures are defined inside xml files. Those files are invoked during Maven build process. For example POTATO_BAG table is defined as follow:

<createTable schemaName="potato_schema"  tableName="potato_bag">
  <column name="id" type="bigint" autoIncrement="true">
    <constraints primaryKey="true" nullable="false"/>
  </column>
  <column name="origin" type="varchar(256)">
    <constraints nullable="false"/>
  </column>
</createTable>

Then jOOQ comes in! We are still in pom.xml. jOOQ generates Classes based on DB structure. It allows us to create type safe SQL queries (via awesome DSL) inside application.

All important Java stuff from potato-db is defined in Guice module so let’s dive into DBModule.

1
2
3
4
5
6
7
8
9
10
11
12
public class DbModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(DataSource.class).toProvider(DataSourceProvider.class).in(Singleton.class);
        
        bind(DSLContext.class).toProvider(JooqProvider.class).in(Singleton.class);
        bind(DSLContext.class).annotatedWith(TxJooq.class).toProvider(TxJooqProvider.class).in(Singleton.class);

        bind(ULTM.class).toProvider(ULTMProvider.class).in(Singleton.class);
        bind(TxManager.class).toProvider(TxManagerProvider.class).in(Singleton.class);
    }
}

DSLContext is jOOQ “every operation on database builder”. We need two of them, one transactional (annotated with @TxJooq) and one non-transactional.

Last two bindings give us Transactions. I used ULTM which stands for Ultra Lightweight Transaction Manager for JDBC. If you are looking for something simple for transactions management you should definitely try it. TxManger will be used for running code inside transaction.

That’s all from potato-db. Quite a lot, I guess.

potato-logic

Combining Vert.x and Guice were described in other post. So let’s focus on the other parts of application.

We have PotatoBag and Potato classes, which looks as follow:

1
2
3
4
5
6
7
8
public class PotatoBag {
    private String origin;
    private List<Potato> items;
}

public class Potato {
    private int quality;
}

Potatoes may come from many regions of the world. If quality of a Potato is less than 100, it is treated as "soon will be rotten". We don’t want such potatoes in basement.

PotatoVerticle can store a PotatoBag and return Potato list for chosen origin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Store PotatoBag
vertx.eventBus().consumer("potato-bag", (Message<String> bagWithPotatoMessage) -> {
    PotatoBag potatoBag = Json.decodeValue(bagWithPotatoMessage.body(), PotatoBag.class);
    log.log(Level.INFO, "Potato bag received {0}", potatoBag);

    vertx.executeBlocking(future -> {
        basementStore.store(potatoBag);
        future.complete();
    }, result -> {
        if (result.succeeded()) {
            bagWithPotatoMessage.reply("Potato bag stored in basement");
        }
        if (result.failed() && result.cause() instanceof RottenPotatoException) {
            bagWithPotatoMessage.reply("Bag with rotten potato cannot be stored");
        }
    });
});

There is some complexity here (If you are not familiar with Vert.x EventBus API), but the most important thing is that we run basement.store(potatoBag) inside vertx.executeBlocking. Vert.x for handling such operation uses extra thread pool, so EventBus is not blocked by JDBC which runs somewhere under the hood.

It all looks similar for fetching potatoes from basement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vertx.eventBus().consumer("potatoes-in-basement", (Message<String> countryMessage) -> {
   String potatoOriginCountry = countryMessage.body();
   log.log(Level.INFO, "Searching in basement for potatoes from {0}", potatoOriginCountry);
   
    vertx.executeBlocking(future -> {
       List<Potato> potatoes = basementFetch.fetchByOrigin(potatoOriginCountry);
       future.complete(potatoes);
    }, result -> {
       if (result.succeeded()) {
           countryMessage.reply(Json.encode(result.result()));
       }
       if (result.failed()) {
           countryMessage.reply(Json.encode(Collections.EMPTY_LIST));
       }
   });
);

The most relevant part of potato-logic module is implemented inside BasementStore class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class BasementStore {

    private final TxManager txManager;
    private final DSLContext txJooq;
    private final PotatoQualityChecker qualityChecker;

    @Inject
    public BasementStore(TxManager txManager, @TxJooq DSLContext txJooq, PotatoQualityChecker qualityChecker) {
        this.txManager = txManager;
        this.txJooq = txJooq;
        this.qualityChecker = qualityChecker;
    }

    public void store(PotatoBag potatoBag) {
        txManager.tx(() -> {
            PotatoBagRecord potatoBagRecord = txJooq.newRecord(POTATO_BAG);
            potatoBagRecord.setOrigin(potatoBag.getOrigin());
            potatoBagRecord.store();

            potatoBag.getItems().stream()
              .forEach((Potato potato) -> {
                  qualityChecker.check(potato);
                  storePotato(potato, potatoBagRecord);
              });
        });
    }

    private void storePotato(Potato potato, PotatoBagRecord potatoBagRecord) {
        PotatoRecord potatoRecord = txJooq.newRecord(POTATO);
        potatoRecord.setBag(potatoBagRecord.getId());
        potatoRecord.setQuality(potato.getQuality());
        potatoRecord.store();
    }
}

Here TxManager and @TxJooq are used. Everything which is inside

1
2
3
4
5
txManager.tx(() -> {

//here

});

is executed inside transaction. All SQL changes will be rollback if something goes wrong inside transaction. And as long as there is PotatoQualityChecker, RottenPotatoException may be thrown when one of Potato quality is insufficient.

Summary

Basically, this is it. I hope this post shows, that you don’t have to resign form SQL and transactions while playing with Vert.x. There are cool and easy-to-use tools for that.

Last but not least. It’s all tested. But how I did it, it’s a topic for another Blog post. Please left thoughts in comments and check whole project on Github.

Remember: write code and eat potatoes!