Kotlin Data Modelling with Micronaut

Kotlin Data Modelling with Micronaut

Mar 15, 2024

If we are using any sort of JPA/CRUD transaction system in our projects we know that we need to understand a few concepts. The first of course is the database. We need to pick a database and then we know we need tables. Tables can be represented in code and their entities as well. Tables and Identities are not the same. A Table is, simply said, a database row collection. En b can be seen as the object representation of a row during runtime. Then we always need to define an Id which can be composed or not. Optionally we may have relations in our databases with respective foreign keys representations. Essentially a foreign key is a value in one database record row which is the Id of a bound database table. We also need to manage database operations using abstractions. This can be achieved using JPA/CRUD repositories. In order to make changes, updates and especially delete operations, we need to perform our operations with transactions which allow all operations to be fulfilled or rollbacked before effectively making those changes to the database. This operation is known as a commit operation. These operations can be done under different isolation modes. For now, if you didn't know, at least now you have a bit of understanding of what we are going to see.


Let's take a look at the following table:

create table if not exists ticket.ticket_reservation
(
 id UUID NOT NULL,
 reference UUID NOT NULL UNIQUE,
 name varchar,
 address varchar,
 birth_date date,
 parking_reservation_id UUID NULL,
 created_at TIMESTAMP NOT NULL DEFAULT LOCALTIMESTAMP,
 PRIMARY KEY (id),
 CONSTRAINT fk_parking_reservation
 FOREIGN KEY (parking_reservation_id)
 REFERENCES ticket.parking_reservation (id)
);

This table is a representation of tickets bought for a concert. We have a bit of personal data being registered like name and address, and we have a parking_reservation_id, which refers to a foreign table. This table contains car parking information, which is recorded in this table:

create table if not exists ticket.parking_reservation
(
 id UUID NOT NULL,
 reference UUID NOT NULL UNIQUE,
 car_parking_id UUID NULL,
 created_at TIMESTAMP NOT NULL DEFAULT LOCALTIMESTAMP,
 PRIMARY KEY (id),
 CONSTRAINT fk_car_parking
 FOREIGN KEY (car_parking_id)
 REFERENCES ticket.car_parking (id)
);

These two are very simple tables. I have also created this table:

create table ticket.ticket_reservation_concert_day
(
 id UUID NOT NULL,
 ticket_reservation_id UUID,
 concert_day_id UUID,
 CONSTRAINT fk_ticket_reservation
 FOREIGN KEY (ticket_reservation_id)
 REFERENCES ticket.ticket_reservation (id),
 CONSTRAINT fk_concert_day
 FOREIGN KEY (concert_day_id)
 REFERENCES ticket.concert_day (id)
);

Did you notice already something special in these tables? if you haven't we'll see later on as to why they are so special. This has to do with the foreign keys and how the Id's are generated. Also take note that on this exercise and in most places I've use a reactive r2dbc library, automatic dll generation just isn't possible. There are disadvantages in doing that with a reactive library and there are disadvantages in doing that all together. Automatic dll generation implies also that you have no control in how the database tables are created. Essentially you leave that to the framework. And you know what happens when you leave too much of some responsibilities to the framework.


These three tables are only a small part of my project buy-odd-yucca-concert. This set of three tables contain, a ticket to a concert, the associated car parking pass and the concert days this ticket allows access to. These tables are being created in the context of reactive project. If you know anything about reactive, you probably know by now that reactive programming and paradigms are based on a very common principle. Not waiting. And of course!! Reactive is much more than this!! But let's not react to this, and if we think about it, this is what it boils down to.This means that ER models per-se are quite difficult to implement in a reactive way. The whole nature of ER (Entity Relation) paradigm is to get all relations in on dataset and only then returning it back.


I found out, however, that Micronaut, does provide some interesting handles to deal with relations in a reactive way. But let's just have a quick look at the setup in application.yml:

r2dbc:
 datasources:
 default:
 schema-generate: NONE
 dialect: POSTGRES
 url: r2dbc:postgresql://kong@localhost:5432/yucca?currentSchema=ticket
 username: kong
 password: kong

flyway:
 datasources:
 default:
 enabled: true

This sets up the reactive framework and the r2dbc library. Reactive JDBC, as other people call it.


To translate this to code, I first decided to create the data representation of the ticket table TicketReservation. I did ran into a bunch of issues with it, and this is most likely because I come from a Hibernate/JPA perspective in a Spring framework context. If you are so involved into Spring, but can't find the pointers to translate that thought into Micronaut, here is the list of things I ran into:

  1. The ID's in the data base. As reported in issue 32 the way micronaut-data works is by using an id and var. The Id has to be nullable (to this date) because Micronaut does not have a plugin to process immutable Id values.

  2. Join is a tough annotation. You'll get strange exceptions if you don't keep the relation between your database and your code consistent. Spring is much less restrict than this. In Micronaut, and in my opinion, Exception messages still need a bit of work. I had issues in which I wanted to get full objects in an EAGER way, but kept getting null values, in spite of the rows being found any way. How is it possible that in a found row where values are not null, we get null anyways? I don't know, but this was fixed when I matched a nullable in the code to a nullable in the database and a non-nullable in the code to a non-nullable in the database.

These issues took a lot of my time to solve and going into this rabbit hole was a mess and I'm putting them here, the best possible way I can so you don't have to.


Knowing all of this, let's have a quick look at the data-model in the code. This is the ticket model:

@MappedEntity(namingStrategy = NamingStrategies.UnderScoreSeparatedLowerCase::class)
data class TicketReservation(
 @field: Id
 @field: AutoPopulated
 var id: UUID? = null,
 val reference: UUID = UUID.randomUUID(),
 val name: String,
 val address: String,
 val birthDate: LocalDate,
 @field: Relation(value = Relation.Kind.ONE_TO_ONE, cascade = [Relation.Cascade.ALL])
 val parkingReservation: ParkingReservation? = null,
 @field:DateCreated
 val createdAt: LocalDateTime? = LocalDateTime.now(),
)

All of this should be easy to just read through, but where we are going to focus now is in ParkingReservation. We can see that this is a nullable field. If you remember from the above, this field in the database is: parking_reservation_id UUID NULL. You can see the match here right?. So now we are ready to make our CRUD repository:

@R2dbcRepository(dialect = POSTGRES)
interface TicketRepository : CoroutineCrudRepository<TicketReservation, UUID>,
 CoroutineJpaSpecificationExecutor<TicketReservation> {

 @Join(value = "parkingReservation", type = Join.Type.OUTER)
 override suspend fun findById(id: UUID): TicketReservation
}

In this case, we are adding a @Join. This Join is just a way to let Micronaut know that we want to join our table ticket_reservation to the parking_reservation table. Micronaut does this on the basis of the Id. The ONE_TO_ONE relationship that is being declared on top of field parkingReservation, is the only relationship I found that actually works in a reactive model. In their examples on micronaut-data, I did find MANY_TOONE, examples, but not only I wasn't able to get it to work, I also don't need it at the moment. I couldn't find any MANY_TO_MANY examples for a reactive model. This now works really well.

If you haven't noticed yet, the id is also a var. Let's give this a thought. I hear very often about this story about how great it is to have a fully immutable system. And it is! Predictability in code is great! It's very much a good thing to have and mutability is just so 90's right? While all of this is true, one thing we can't really make immutable is database interactions and this is where the immutability concepts get, in my view, too stretched and complicated. You see, in Java, when we make CRUD/JPA repositories, we usually don't think about nullable or not-nullable. In Java, all objects are nullable unless we specifically say that they are not. And this is also because we know that on that lower level as in the data level, data does changes. If we leave the database to decide and generate the ID for us we still need to send an instance with a null ID. See? because we don't decide the ID, it has to be null, when we persist it to the database. The Database does decide this for us in this case. And when the database wants to return an instance, your code, then needs to be able to change the value of the ID. In Spring, you can bypass this by using plugins that do this for you under the hood. In Micronaut I haven't found any but apparently they don't exist yet as dicussedd in issue 32. So I'm forced to use var. Is this that bad? Again, this happens in the data layer of this architecture and on that level, there should be no logic at all, so making stuff immutable to make it predictable seems like a bridge too far for no gain at all.

On a higher level, I just want to clarify, that parkingReservation is nullable in this case, because not everyone that buys a ticket for this concert will want a parking space or even be able to get it before the all spaces are taken. For my project I make this impossible to change and make only a refund and post purchase possible. This is consciously done to give a fair chance to everyone to get a parking place.


Finally one last example. This is a manual implementation of a MANY_TO_MANY relationship:

@MappedEntity(namingStrategy = NamingStrategies.UnderScoreSeparatedLowerCase::class)
data class TicketReservationConcertDay(
 @field: Id
 @field: AutoPopulated
 var id: UUID? = null,
 @field: Relation(value = Relation.Kind.ONE_TO_ONE, cascade = [Relation.Cascade.ALL])
 val ticketReservation: TicketReservation,
 @field: Relation(value = Relation.Kind.ONE_TO_ONE, cascade = [Relation.Cascade.ALL])
 val concertDay: ConcertDay
)

In this case I've simply created a binding table that sits between the ticketReservation and the concerDay. If you remember from the SQL queries from above, these non-null fields are also non-null in their counterparts: ticket_reservation_id UUID and concert_day_id UUID. The @Join can be declared multiple times this way:

@R2dbcRepository(dialect = Dialect.POSTGRES)
interface TicketReservationConcertRepository : CoroutineCrudRepository<TicketReservationConcertDay, UUID>,
 CoroutineJpaSpecificationExecutor<TicketReservationConcertDay> {

 @Join(value = "ticketReservation", type = Join.Type.FETCH)
 @Join(value = "concertDay", type = Join.Type.FETCH)
 override suspend fun findById(id: UUID): TicketReservationConcertDay
}

For these small pieces of code, I had to struggle quite a bit, given on hand my lack of experience with Micronaut, but also, as I mentioned on previous bites, because Micronaut is a whole different way of developing software development. In spite of some difficult challenges in Micronaut, what I always find is that, things I wasn't forced to understand with Spring, I am actually forced to with Micronaut. A deep level understanding is really highly required when working with Micronaut in my view and this is what is making it so special to me. I hope you enjoyed this and that I was able to help you in some way. Check further developments of my project in buy-odd-yucca-concert. Have a good one 👍 !

Enjoy this post?

Buy João Esperancinha a coffee

More from João Esperancinha