# Building and testing a REST HAL API using Kotlin, Spring Data REST and JPA

# Overview

This article will walk through the process of implementing a simple yet powerful hypermedia-driven REST API application, using **Kotlin**, **Spring Boot**, and **Spring Data REST**.

The goal is to demonstrate how we can build a robust REST API, following HATEOAS constraints with very few lines of code, barely only representing our domain core model.

Our API will be themed upon a *Movie Universe,* designed to manage movies, directors, actors and characters within a cinematic universe. These elements will form the core of our domain model.

The tech stack chosen for this demonstration is the following:

* JDK 17 (GraalVM distribution)
    
* Kotlin 1.8
    
* Gradle 8
    
* Spring Boot 3
    
* JPA
    
* Flyway
    
* PostgreSQL
    
* Docker\* & Testcontainers
    

*\* Docker installation process is not part of the article, we assume is already installed.*

### TLDR; show me the code!

The full source code of the article is published on GitHub 😉

[https://github.com/ArnauAregall/kotlin-spring-data-rest-movies](https://github.com/ArnauAregall/kotlin-spring-data-rest-movies)

# Getting started

## 🍃 Generating our project with Spring Initalizr

We will delegate the *bootiful* Spring Initalizr our project bootstrapping.

Head to [https://start.spring.io](https://start.spring.io) and build a new project with the following configuration:

![Screenshot of Spring Initializr project configuration](https://cdn.hashnode.com/res/hashnode/image/upload/v1680513901786/2c1f74b0-98d5-4ebb-85bd-da890f140ebe.png align="center")

Here is a link you can follow to automatically generate the project with the previous configuration:

```plaintext
https://start.spring.io/#!type=gradle-project-kotlin&language=kotlin&platformVersion=3.1.0-SNAPSHOT&packaging=jar&jvmVersion=17&groupId=tech.aaregall.lab&artifactId=kotlin-spring-data-rest-movies&name=kotlin-spring-data-rest-movies&description=Demo%20project%20for%20Spring%20Boot&packageName=tech.aaregall.lab.kotlin-spring-data-rest-movies&dependencies=native,data-rest,data-jpa,validation,flyway,postgresql,testcontainers
```

Once you download the project `.zip` file, extract it to your favorite workspace folder, and we are ready to continue.

## ☕️ Installing our JDK distribution with GraalVM

We will use [SDKMAN!](https://sdkman.io/) to install our JDK 17 distribution with GraalVM.

```bash
$ sdk install java 22.3.r17-grl
$ sdk use java 22.3.r17-grl
```

```bash
$ java -version
openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)
```

## ⬆️ Upgrading Gradle Wrapper to v8

By the time this article was written, the project generated by Spring Initializr comes with Gradle `7.6.1.` We will use [Gradle 8](https://docs.gradle.org/8.0/release-notes.html) as it supports Kotlin DSL out of the box.

For that, we can just simply pinpoint the version `8.0.2` in the `distributionUrl` property under `gradle/wrapper/gradle-wrapper.properties` file.

```plaintext
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
```

Once done, we can run `./gradlew -version` to force the download of the new Gradle wrapper JAR file (`gradle/wrapper/gradle-wrapper.jar` should be automatically replaced).

### Adjustments on our Gradle setup for generating native images

To generate a native image of our application, a few adjustments are needed with the default configuration provided by Spring Intializr.

On our **build.gradle.kts** file:

1. We will add the [`org.graalvm.buildtools.native`](https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html) plugin: once included, running `bootBuildImage` task will automatically detect the plugin presence in the classpath and will generate a native image for us by default.
    
2. Also, we will need the [`org.jetbrains.kotlin.plugin.allopen`](https://kotlinlang.org/docs/all-open-plugin.html) plugin to configure that our classes annotated with `@Entity` are `open` by default upon compilation (Kotlin classes are final by default and thus cannot be proxied unless we explicitly open them). The goal is that Hibernate can apply effective proxies for lazy loading on our entities.
    
3. Then, we will configure the `BootBuildImage` task to define:
    
    1. A custom image name, we will later use it to run the application using Docker.
        
    2. A base Paketo builder tiny version, and since I am an ARM64 arch user, [one that we can use if we are on a Mac M1+ host](https://github.com/dashaun/paketo-arm64) (thanks to @[DaShaun](@dashaun) 🫶).
        
4. Finally, we will configure the `allOpen` extension to open all classes annotated with `@Entity`
    

```kotlin

plugins {
	id("org.springframework.boot")
	id("io.spring.dependency-management")
	id("org.graalvm.buildtools.native")
	id("org.jetbrains.kotlin.plugin.allopen")
	kotlin("jvm")
	kotlin("plugin.spring")
	kotlin("plugin.jpa")
}

// repositories {...}, dependencies {...}

tasks.withType<BootBuildImage> {
	imageName.set("${project.group}/${rootProject.name}")
	builder.set("dashaun/builder:tiny")
}

allOpen {
	annotation("jakarta.persistence.Entity")
}
```

[Note: full `build.gradle.kts` file is available on GitHub.](https://github.com/ArnauAregall/kotlin-spring-data-rest-movies)

# 📐 The API Domain Model

Our *Movie Universe Management* API core domain model is as follows:

* **Actors** can be cast in multiple movies, playing one character per movie.
    
* **Characters** can be played by multiple actors and can appear in multiple movies.
    
* **Directors** can direct multiple movies.
    
* **Movies** can be directed by one director and cast multiple actors, each playing a playing different character.
    

## The SQL schema

To represent our domain model, we will create a relational SQL schema on PostgreSQL. We want our domain schema to be versioned, hence we will create it using a [Flyway migration script](https://documentation.red-gate.com/fd/migrations-184127470.html).

Spring Boot, by default, looks for Flyway migration scripts under `src/main/resources/db/migration` folder, so we will add there our `V0.0.1__create_schema.sql` script to create the required tables to build the mentioned domain model.

```sql
SET SEARCH_PATH TO "kotlin-spring-data-rest-movies";

CREATE TABLE IF NOT EXISTS "actor" (
    id BIGSERIAL UNIQUE NOT NULL,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    birth_date DATE NOT NULL,
    death_date DATE,
    PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS "character" (
    id BIGSERIAL UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS "director" (
    id BIGSERIAL UNIQUE NOT NULL,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS "movie" (
    id BIGSERIAL UNIQUE NOT NULL,
    title VARCHAR(255) NOT NULL,
    release_date DATE NOT NULL,
    director_id BIGSERIAL NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (director_id) REFERENCES director(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS "movie_character" (
    movie_id BIGSERIAL NOT NULL,
    character_id BIGSERIAL NOT NULL,
    PRIMARY KEY (movie_id, character_id),
    UNIQUE (movie_id, character_id),
    FOREIGN KEY (movie_id) REFERENCES movie(id) ON DELETE CASCADE,
    FOREIGN KEY (character_id) REFERENCES character(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS "actor_character" (
    actor_id BIGSERIAL NOT NULL,
    character_id BIGSERIAL NOT NULL,
    PRIMARY KEY (actor_id, character_id),
    UNIQUE (actor_id, character_id),
    FOREIGN KEY (actor_id) REFERENCES actor(id) ON DELETE CASCADE,
    FOREIGN KEY (character_id) REFERENCES character(id) ON DELETE CASCADE
);
```

## Creating our domain classes

Our domain classes (or entities) will represent the core model of our application, each object of those representing a table record on our database, that we will later expose as HTTP REST resources.

### Actor

Our Actor entity will have the following properties:

* An auto-generated ID.
    
* A first name, a last name, and a birth date - all of them are required.
    
* An optional death date.
    
* A collection of Characters that the Actor has interpreted, using a combination of `@ManyToMany` and `@JoinTable`.
    
* And also, a private function that will inform if the Actor is alive or not (that will be automatically mapped to our API response).
    

```kotlin
@Entity
@Table(name = "actor")
class Actor (

    @NotNull @Column(name = "first_name")
    var firstName: String,

    @NotNull @Column(name = "last_name")
    var lastName: String,

    @NotNull@Column(name = "birth_date")
    var birthDate: LocalDate,

    @Column(name = "death_date")
    var deathDate: LocalDate?

) {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null

    @ManyToMany
    @JoinTable(
        name = "actor_character",
        joinColumns = [JoinColumn(name = "actor_id")],
        inverseJoinColumns = [JoinColumn(name = "character_id")]
    )
    var characters: MutableSet<Character>? = mutableSetOf()

    @JsonProperty("is_alive")
    private fun isAlive(): Boolean = isNull(deathDate)
}
```

### Character

Our Character entity will have the following properties:

* An auto-generated ID.
    
* A name, required.
    
* A collection of Actors that have interpreted that Character (the reverse relationship that we saw in `Actor#characters`).
    
* A collection of Movies where the Character appears, again with a `@ManyToMany` and `@JoinTable` combo.
    

```kotlin
@Entity
@Table(name = "character")
class Character (

    @NotNull
    @Column(name = "name")
    var name: String?

) {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null

    @ManyToMany
    @JoinTable(
        name = "actor_character",
        joinColumns = [JoinColumn(name = "character_id")],
        inverseJoinColumns = [JoinColumn(name = "actor_id")]
    )
    var actors: MutableSet<Actor>? = mutableSetOf()

    @ManyToMany
    @JoinTable(
        name = "movie_character",
        joinColumns = [JoinColumn(name = "character_id")],
        inverseJoinColumns = [JoinColumn(name = "movie_id")]
    )
    var movies: MutableSet<Movie>? = mutableSetOf()
}
```

### Director

Our Director entity will have the following properties:

* An auto-generated ID.
    
* A first name and a last name - both required.
    
* A collection of Movies that the Director has directed, mapped by a `@OneToMany` relationship.
    

```kotlin
@Entity
@Table(name = "director")
class Director (

    @NotNull
    @Column(name = "first_name")
    var firstName: String,

    @NotNull
    @Column(name = "last_name")
    var lastName: String

) {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null

    @OneToMany(mappedBy = "director")
    var movies: MutableSet<Movie>? = mutableSetOf()
}
```

### Movie

Finally, our Movie entity has the following properties:

* An auto-generated ID.
    
* A title, a release date, and a Director (mapped by a `@ManyToOne`) - all of them required.
    
* A collection of Characters that appear in that movie, once again using `@ManyToMany` and `@JoinTable`.
    

```kotlin
@Entity
@Table(name = "movie")
class Movie (

    @NotNull
    @Column(name = "title")
    var title: String,

    @NotNull
    @Column(name = "release_date")
    var releaseDate: LocalDate,

    @NotNull
    @ManyToOne
    @JoinColumn(name = "director_id")
    var director: Director

) {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null

    @ManyToMany
    @JoinTable(
        name = "movie_character",
        joinColumns = [JoinColumn(name = "movie_id")],
        inverseJoinColumns = [JoinColumn(name = "character_id")]
    )
    var characters: MutableSet<Character>? = mutableSetOf()
}
```

# Exposing a HAL REST API for our domain with Spring Data REST

Once our domain classes are defined, we are ready to expose them as REST HATEOAS resources using Spring Data REST.

> Spring Data REST builds on top of the Spring Data repositories and automatically exports those as REST resources. It leverages hypermedia to let clients automatically find functionality exposed by the repositories and integrate these resources into related hypermedia-based functionality.
> 
> [🔗 Spring Data REST reference documentation.](https://docs.spring.io/spring-data/rest/docs/current/reference/html/#reference)

## Spring Boot configuration

Let's have a look at how our Spring Configuration would look to achieve the following:

1. Configure our **DataSource**:
    
    1. Our application will be backed by a PostgreSQL database, so we will need to configure our DataSource URL, as well as the database username/password, plus the default schema for Hibernate.
        
2. Configure **Spring Data REST** itself:
    
    1. We will define that our API base path (`/api`) and configure Jackson for a `snake_case` response serialization flavor.
        
    2. We will tell Spring to expose as REST APIs our Spring Data JPA repositories annotated with `@RepositoryRestResource`.
        

```yaml
spring:
  application:
    name: kotlin-spring-data-rest-movies

  data:
    rest:
      base-path: /api/
      detection-strategy: annotated

  datasource:
    url: jdbc:postgresql://localhost:5432/lab
    username: postgres
    password: postgrespw

  jpa:
    properties:
      hibernate:
        default_schema: ${spring.application.name}

  flyway:
    default-schema: ${spring.application.name}

  jackson:
    property-naming-strategy: SNAKE_CASE

  sql:
    init:
      mode: always
```

## Declaring and exposing our repositories as REST endpoints

To expose our domain entities as REST resources, we just need to declare regular interfaces that extend the well-known Spring Data `JpaRepository<DomainClass, IdClass>` interface, and annotate them with `@RepositoryRestResource` (by annotating them Beans will automatically be declared).

```kotlin
@RepositoryRestResource(path = "actors", collectionResourceRel = "actors", itemResourceRel = "actor")
interface ActorRestRepository : JpaRepository<Actor, Long>

@RepositoryRestResource(path = "characters", collectionResourceRel = "characters", itemResourceRel = "character")
interface CharacterRestRepository : JpaRepository<Character, Long>

@RepositoryRestResource(path = "directors", collectionResourceRel = "directors", itemResourceRel = "director")
interface DirectorRestRepository : JpaRepository<Director, Long>

@RepositoryRestResource(path = "movies", collectionResourceRel = "movies", itemResourceRel = "movie")
interface MovieRestRepository : JpaRepository<Movie, Long>
```

We configured the following attributes on each `@RepositoryRestResource`:

* **path**: The path segment under which the resource will be exported, of course, under our base path (`/api/`). Usually is the plural noun of our domain entity name. The above configuration will translate to expose the following endpoints on runtime:
    
    * `/api/actors/**`, `/api/characters/**`, `/api/directors/**`, `/api/movies/**`
        
* **collectionResourceRel**: The relationship name to use when generating links to the collection resource. Usually is the plural noun of our domain entity name. Example: Director -&gt; directors.
    
* **itemResourceRel**: The relationship name to use when generating links to a single element resource. Usually is the singular noun of our domain entity name. Example: Movie -&gt; movie.
    

## Additional Spring Data REST customizations

[As the reference documentation states](https://docs.spring.io/spring-data/rest/docs/current/reference/html/#getting-started.configuration), Spring Data REST configuration can be customized if the out-of-the-box one does not fully suit our needs.

In our case, just for demonstration purposes, we will customize our configuration so the API responses include the IDs of our entities (by default it does not).

To do so, we will implement a `@Configuration` class declaring a `RepositoryRestConfigurer` bean.

```kotlin
@Configuration
class RepositoryRestConfig {

    @Bean
    fun repositoryRestConfigurer(): RepositoryRestConfigurer {
        return object : RepositoryRestConfigurer {

            override fun configureRepositoryRestConfiguration(config: RepositoryRestConfiguration?, cors: CorsRegistry?) {
                config!!.exposeIdsFor(
                    Actor::class.java,
                    Character::class.java, 
                    Director::class.java, 
                    Movie::class.java
                )
            }

        }
    }

}
```

Here we override `configureRepositoryRestConfiguration`, and further configuration customizations could be achieved with the config argument we receive. The interface provides quite a few more methods to configure more aspects of the framework, like the Jackson `ObjectMapper` definition and validation event listeners, among others.

# 🧪 Implementing integration tests

Our application integration tests will consist of performing real HTTP requests to our API to test the full CRUD of each entity, although to keep the article shortened we will just demonstrate a basic movie setup use case.

## Abstract class with PostgreSQL Testcontainer

We want our integration tests to persist/query real data against a real database, so we will implement a base abstract class that our tests will extend, which will be responsible for:

* Spin up the Spring Boot application context with a test profile auto-configuring.
    
* Configure and start a PostgreSQL testcontainer.
    

```kotlin
@ActiveProfiles("test")
@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
abstract class AbstractIT {

    companion object {

        @Container
        private val container = PostgreSQLContainer(DockerImageName.parse("postgres:15-alpine"))
            .apply {
                withDatabaseName("test")
                withUsername("test")
                withPassword("test")
                start()
            }

        @JvmStatic
        @DynamicPropertySource
        fun properties(registry: DynamicPropertyRegistry) {
            registry.add("spring.datasource.url", container::getJdbcUrl)
            registry.add("spring.datasource.username", container::getUsername)
            registry.add("spring.datasource.password", container::getPassword)
        }
    }
}
```

With the usage of `@DynamicPropertySource` from Spring Test libraries, we are able to override the Spring Boot DataSource configuration properties in order to connect to the testcontainer instance. We also need the `@JvmStatic` Kotlin annotation to tell the compiler to generate our method as `static`, as `@DynamicPropertySource` requires static methods.

*Disclaimer: base class for integration test can end up with a serious mess in large codebase projects (I've been there), although we will not discuss this here - maybe in another post?* 😉 🔜

## Starting each test with a clean database

A common pitfall of integration testing using real databases is that data generated by tests might interfere with the execution of others.

For example, think about a test where we want to get all available Actors by performing a `GET /api/actors?page=0&size=50` asserting the data returned by the API is the one we generate within that test execution, let's say 50 generated Actor records.

What would happen if another test that creates an actor runs first, without cleaning the database? 🤔 You guessed right: 51 actors will be returned and our test would fail.

To solve this issue and start with a clean database when running each test, we will implement a custom annotation `@CleanDatabase` that could be placed at class level of each JUnit test class. Behind the scenes, before each test execution of each `@Test` in the class, it will `TRUNCATE` all our database tables (inspired by [@maciejwalkowiak](https://twitter.com/maciejwalkowiak)'s [blog post](https://maciejwalkowiak.com/blog/spring-boot-flyway-clear-database-integration-tests/)).

```kotlin
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(CleanDatabaseCallback::class)
annotation class CleanDatabase

private class CleanDatabaseCallback : BeforeEachCallback {

    override fun beforeEach(context: ExtensionContext) {
        val dataSource = SpringExtension.getApplicationContext(context).getBean(DataSource::class.java)
        ResourceDatabasePopulator(ClassPathResource("db/scripts/clean_db.sql")).execute(dataSource)
    }
}
```

The clean-up SQL script would be placed under `src/test/resources/db/scripts` folder:

```sql
SET SEARCH_PATH TO "kotlin-spring-data-rest-movies";

TRUNCATE TABLE "actor" RESTART IDENTITY CASCADE;
TRUNCATE TABLE "director" RESTART IDENTITY CASCADE;
TRUNCATE TABLE "character" RESTART IDENTITY CASCADE;
TRUNCATE TABLE "movie" RESTART IDENTITY CASCADE;
```

## Integration tests for CRUD operations

Now we have everything prepared to start writing actual integration tests that would validate the behavior of our REST API.

We will take the Actor entity as an example to demonstrate how we would test at least 3 endpoints:

* Find available Actors using pagination
    
* Get an Actor by ID
    
* Create an Actor
    

To do so, we will create a new test class that would extend `AbstractIT`, annotated with `@CleanDatabase`, and it's constr the MockMvc and ActorRestRepository beans (the later only to perform post-request assertions).

```kotlin
private const val BASE_PATH = "/api/actors"

@CleanDatabase
class ActorRestRepositoryIT (
    @Autowired val mockMvc: MockMvc,
    @Autowired val actorRestRepository: ActorRestRepository): AbstractIT() {

    @Nested
    @DisplayName("GET $BASE_PATH")
    inner class Get {

        @Test
        fun `Should find available Actors using pagination` () {
            val actors = IntStream.rangeClosed(1, 50)
                .mapToObj {index -> actorRestRepository.save(
                    Actor( "FirstName $index", "LastName $index", LocalDate.now().minusYears(index.toLong()), null)
                )}
                .toList()

            mockMvc.perform(get(BASE_PATH)
                .accept(HAL_JSON)
                .param("page", "0")
                .param("size", actors.size.toString()))
                .andExpect(status().isOk)
                .andExpect(content().contentType(HAL_JSON))
                .andExpectAll(
                    jsonPath("$._embedded.actors.length()").value(actors.size),
                    jsonPath("$._embedded.actors[*].id",
                        containsInAnyOrder(streamToIsMatcher(actors.stream().map { it.id!!.toInt() }))
                    ),
                    jsonPath("$._embedded.actors[*].first_name",
                        containsInAnyOrder(streamToIsMatcher(actors.stream().map(Actor::firstName)))
                    ),
                    jsonPath("$._embedded.actors[*].birth_date",
                        containsInAnyOrder(streamToIsMatcher(actors.stream().map(Actor::birthDate).map(LocalDate::toString)))
                    ),
                    jsonPath("$.page").isNotEmpty,
                    jsonPath("$.page.size").value(actors.size),
                    jsonPath("$.page.total_elements").value(actors.size),
                    jsonPath("$.page.total_pages").value(1),
                    jsonPath("$.page.number").value(0)
                )
        }
    }

    @Nested
    @DisplayName("POST $BASE_PATH")
    inner class Post {

        @Test
        fun `Should Create an Actor` (@Autowired objectMapper: ObjectMapper) {
            val actor = Actor("Daniel", "Craig", LocalDate.of(1968, 3, 2), null)

            val result = mockMvc.perform(post(BASE_PATH)
                .accept(HAL_JSON)
                .contentType(HAL_JSON)
                .content(objectMapper.writeValueAsString(actor)))
                .andExpect(status().isCreated)
                .andExpect(content().contentType(HAL_JSON))
                .andExpectAll(
                    jsonPath("$.id").value(notNullValue()),
                    jsonPath("$.first_name").value(actor.firstName),
                    jsonPath("$.last_name").value(actor.lastName),
                    jsonPath("$.birth_date").value(actor.birthDate.toString()),
                    jsonPath("$.death_date").value(nullValue()),
                    jsonPath("$.is_alive").value(true)
                ).andReturn()

            val actorId = JsonPath.read<Int>(result.response.contentAsString, "$.id")

            assertThat(actorRestRepository.getReferenceById(actorId.toLong()))
                .isNotNull
                .extracting(Actor::firstName, Actor::lastName, Actor::birthDate, Actor::deathDate)
                .containsExactly(actor.firstName, actor.lastName, actor.birthDate, null)
        }

    }
    
    // PATCH, PUT, and DELETE tests follow on...

}
```

[You can find the rest of the integration tests on GitHub.](https://github.com/ArnauAregall/kotlin-spring-data-rest-movies)

## Integration tests for entities' relationships

Now that we have seen how to implement integration tests with CRUD operating with a single domain entity, we can move on to **how to write integration tests for relationships between entities** using Spring Data REST and HAL links.

We will continue with the James Bond theme, which suits us perfectly to illustrate the following entity relationship use case:

* We want to create a couple of *James Bond* saga movies, each one directed by the same director.
    
* In each movie, *James Bond* appears as a character but is interpreted by a different actor.
    
* Also, each movie has its main villain, each one interpreted by a different actor.
    

To build relationships between entities, we can make use of the HATEOAS links. When creating or updating an entity, the request body JSON payload would contain the **relationship property with a link/array of links** **pointing to the existing REST resource**.

For example, if we want to create a new Character interpreted by two existing Actors, the request would look like the following:

```bash
curl --location 'http://localhost:8080/api/characters' \
--header 'Content-Type: application/json' \
--data '{
    "name": "James Bond",
    "actors": ["http://localhost:8080/api/actors/1", "http://localhost:8080/api/actors/2"]
}'
```

An integration test to cover the previous use case would have different steps following the AAA principle "Arrange-Act-Assert".

### Arrange

First, we will implement a couple of utility functions to help us perform a POST request with a given payload on a given path, verify the resource was created by checking the response status code, and retuning the link of the created resource.

```kotlin
fun performPost(path: String, body: String) : ResultActions {
    return mockMvc.perform(post(path)
        .accept(HAL_JSON)
        .content(body))
        .andExpect(status().isCreated)
        .andExpect(content().contentType(HAL_JSON))
}

fun createAndReturnSelfHref(path: String, body: String): String {
    return JsonPath.read(
        performPost(path, body)
            .andReturn().response.contentAsString, "_links.self.href")
}
```

Now we can start arranging our data, the **Actors** first - we will create 4 different resources:

```kotlin
val pierceBrosnanLink = createAndReturnSelfHref("/api/actors",
    """
        {"first_name": "Pierce", "last_name": "Brosnan", "birth_date": "1953-05-16"}
    """.trimIndent())

val danielCraigLink = createAndReturnSelfHref("/api/actors",
    """
        {"first_name": "Daniel", "last_name": "Craig", "birth_date": "1968-03-02"}
    """.trimIndent())

val madsMikkelsenLink = createAndReturnSelfHref("/api/actors",
    """
        {"first_name": "Mads", "last_name": "Mikkelsen", "birth_date": "1965-11-22"}
    """.trimIndent())

val seanBeanLink = createAndReturnSelfHref("/api/actors",
    """
        {"first_name": "Sean", "last_name": "Bean", "birth_date": "1959-04-17"}
    """.trimIndent())
```

Once we have the Actors, we can start creating the **Characters**, by specifying in the request body the Actors that interpret each Character:

```kotlin
val jamesBondLink = createAndReturnSelfHref("/api/characters",
    """
        {"name": "James Bond", "actors": ["$pierceBrosnanLink", "$danielCraigLink"]}
    """.trimIndent())

val leChiffreLink = createAndReturnSelfHref("/api/characters",
    """
        {"name": "Le Chiffre", "actors": ["$madsMikkelsenLink"]}
    """.trimIndent())

val alecTrevelyanLink = createAndReturnSelfHref("/api/characters",
    """
        {"name": "Alec Trevelyan", "actors": ["$seanBeanLink"]}
    """.trimIndent())
```

We also need to create the **Director**:

```kotlin
val directorLink = createAndReturnSelfHref("/api/directors",
    """
        {"first_name": "Martin", "last_name": "Campbell"}
    """.trimIndent())
```

And finally, we can create the two mentioned **Movies**, directed by the same Director, with at least a shared Character interpreted by two different Actors:

```kotlin
val goldeneyeLink = createAndReturnSelfHref("/api/movies",
    """
        {"title": "Goldeneye", 
        "release_date": "1995-12-20", 
        "director": "$directorLink", 
        "characters": ["$jamesBondLink", "$alecTrevelyanLink"]}
    """.trimIndent())

val casinoRoyaleLink = createAndReturnSelfHref("/api/movies",
    """
        {"title": "Casino Royale", 
        "release_date": "2006-11-14", 
        "director": "$directorLink", 
        "characters": ["$jamesBondLink", "$leChiffreLink"]}
    """.trimIndent())
```

### Act and Assert

The "Act" step of our integration test consists in performing GET requests to the different Movie resources.

The "Assert" step will consist in asserting the next statements using again GET request on the HAL links:

* Each Movie is directed by the same Director.
    
* Each Movie has the expected Characters.
    
* Each Character is interpreted by the expected Actor in each Movie.
    

We will again implement a utility function to perform the GET requests:

```kotlin
fun performGet(path: String): ResultActions {
    return mockMvc.perform(get(path)
        .accept(HAL_JSON))
        .andExpect(status().isOk)
        .andExpect(content().contentType(HAL_JSON))
}
```

The Movie assertions would consist in:

* GET the Movie.
    
* Follow the Director link.
    
* Follow the Characters link.
    
* For each Character, follow the Actors link.
    

The following would be the assertion for *Goldeneye*:

```kotlin
performGet(goldeneyeLink)
    .andExpectAll(
        jsonPath("$.id").isNotEmpty,
        jsonPath("$.title").value("Goldeneye"),
        jsonPath("$.release_date").value("1995-12-20")
    )
    .andDo {
        val movieResponse = it.response.contentAsString

        performGet(JsonPath.read(movieResponse, "$._links.director.href"))
            .andExpectAll(
                jsonPath("$.id").isNotEmpty,
                jsonPath("$.first_name").value("Martin"),
                jsonPath("$.last_name").value("Campbell")
            )

        performGet(JsonPath.read(movieResponse, "$._links.characters.href"))
            .andExpectAll(
                jsonPath("$._embedded.characters.length()").value(2),
                jsonPath("$._embedded.characters[*].name", containsInAnyOrder("James Bond", "Alec Trevelyan"))
            )
            .andDo { charactersResult ->

                val charactersActorsLinks = JsonPath.read<JSONArray?>(charactersResult.response.contentAsString, "$._embedded.characters[*]._links.actors.href")
                    .filterIsInstance<String>()
                    .sorted()

                performGet(charactersActorsLinks[0])
                    .andExpectAll(
                        jsonPath("$._embedded.actors.length()").value(2),
                        jsonPath("$._embedded.actors[*].first_name", containsInAnyOrder("Pierce", "Daniel")),
                        jsonPath("$._embedded.actors[*].last_name", containsInAnyOrder("Brosnan", "Craig"))
                    )

                performGet(charactersActorsLinks[1])
                    .andExpectAll(
                        jsonPath("$._embedded.actors.length()").value(1),
                        jsonPath("$._embedded.actors[0].first_name").value("Sean"),
                        jsonPath("$._embedded.actors[0].last_name").value("Bean")
                    )

            }
    }
```

And this one would correspond to *Casino Royale*:

```kotlin
performGet(casinoRoyaleLink)
    .andExpectAll(
        jsonPath("$.id").isNotEmpty,
        jsonPath("$.title").value("Casino Royale"),
        jsonPath("$.release_date").value("2006-11-14")
    )
    .andDo {
        val movieResponse = it.response.contentAsString

        performGet(JsonPath.read(movieResponse, "$._links.director.href"))
            .andExpectAll(
                jsonPath("$.id").isNotEmpty,
                jsonPath("$.first_name").value("Martin"),
                jsonPath("$.last_name").value("Campbell")
            )

        performGet(JsonPath.read(movieResponse, "$._links.characters.href"))
            .andExpectAll(
                jsonPath("$._embedded.characters.length()").value(2),
                jsonPath("$._embedded.characters[*].name", containsInAnyOrder("James Bond", "Le Chiffre")),
            )
            .andDo { charactersResult ->

                val charactersActorsLinks = JsonPath.read<JSONArray?>(charactersResult.response.contentAsString, "$._embedded.characters[*]._links.actors.href")
                    .filterIsInstance<String>()
                    .sorted()

                performGet(charactersActorsLinks[0])
                    .andExpectAll(
                        jsonPath("$._embedded.actors.length()").value(2),
                        jsonPath("$._embedded.actors[*].first_name", containsInAnyOrder("Pierce", "Daniel")),
                        jsonPath("$._embedded.actors[*].last_name", containsInAnyOrder("Brosnan", "Craig"))
                    )

                performGet(charactersActorsLinks[1])
                    .andExpectAll(
                        jsonPath("$._embedded.actors.length()").value(1),
                        jsonPath("$._embedded.actors[0].first_name").value("Mads"),
                        jsonPath("$._embedded.actors[0].last_name").value("Mikkelsen")
                    )

            }
    }
```

[The full "James Bond use case" integration test is on GitHub.](https://github.com/ArnauAregall/kotlin-spring-data-rest-movies)

# 🐳 Running the application natively using Docker

To run the application using Docker, we will create a `docker-compose.yml` file that will:

* Create a service that will deploy our REST API application on a Docker container using the latest Docker image version (native! 🚀).
    
* Create a service that will start a Postgres container with our Database "lab" created. It will mount a `init_db.sql` as entry point that will create our database when Postgres server starts with a plain `CREATE DATABASE lab;` command.
    
* We will provide the database connection settings for both containers using environment variables.
    

```yaml
version: '1'
services:
  rest-api:
    image: tech.aaregall.lab/kotlin-spring-data-rest-movies:latest
    restart: always
    depends_on:
      - postgres
    ports:
      - 8080:8080
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/lab
  postgres:
    image: postgres:latest
    container_name: postgres
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgrespw
    ports:
      - '5432:5432'
    volumes:
      - ./init_db.sql:/docker-entrypoint-initdb.d/init_db.sql
```

To keep everything well organized we will save the previous files under `src/main/docker` directory.

Once we are ready we can just build our native image and run `docker-compose up`.

```bash
$ ./gradlew bootBuildImage
$ docker-compose -f src/main/docker/docker-compose.yml up
```

The application will be running `localhost`, port `8080`.

```bash
curl -i --location 'http://localhost:8080/api/actors' \
--header 'Content-Type: application/json' \
--data '{
    "first_name": "Daniel",
    "last_name": "Craig",
    "birth_date": "1968-03-02"
}'

HTTP/1.1 201
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Location: http://localhost:8080/api/actors/1
Content-Type: application/hal+json
Transfer-Encoding: chunked
Date: Sat, 22 Apr 2023 13:15:22 GMT

{
  "first_name" : "Daniel",
  "last_name" : "Craig",
  "birth_date" : "1968-03-02",
  "death_date" : null,
  "id" : 1,
  "is_alive" : true,
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/api/actors/1"
    },
    "actor" : {
      "href" : "http://localhost:8080/api/actors/1"
    },
    "characters" : {
      "href" : "http://localhost:8080/api/actors/1/characters"
    }
  }
}
```

# Conclusion

This article demonstrated:

* How to configure our Gradle 8 build files using Kotlin DSL.
    
* How to configure Gradle to generate a native Docker image ARM64 compatible.
    
* How to implement our domain model entities using Kotlin and JPA.
    
* How to expose a REST HAL API for each entity using Spring Data REST.
    
* How to implement integration test for our application.
    
* How to run our application natively using Docker.
    

The examples used on this article are available in the linked [GitHub repository](https://github.com/ArnauAregall/kotlin-spring-data-rest-movies).
