spring-projects / spring-projects/spring-data-mongodb

Support for Kotlin's value class as document's @Id

Open
#4,078 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

type: enhancement
Dominant language
Java
Stars
1.7k
Forks
1.1k
PR merge metrics
No merged PRs in 30d

Description

Hi. I think it's a good idea to use Kotlin's inline class to enforce a restriction at the compile time and prevent yourself from mixing up an id of one entity with id on another entity.

Usually the document class looks like this:

@Document
data class Person(@Id var id: String? = null, val name: String)

And you can pass any String to repo's findById method.
I would like to use an inline class for @Id as follows:

@JvmInline
value class PersonId(val value: String)

@Document
data class Person(@Id var id: PersonId? = null, val name: String)

The problem is that it's not fully supported. The repository's save operation works fine (as far as I can see by inspecting my local Mongo instance), but, for example, findAll and findByIdOrNull fail to act as expected in different manners.

Below is an example source code, which can be put into a single test file of a project scaffolded with start.spring.io:

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Repository

@JvmInline
value class PersonId(val value: String)

@Document
data class Person(@Id var id: PersonId? = null, val name: String)

@Repository
interface PersonRepository : MongoRepository<Person, PersonId>

@DataMongoTest
class PersonRepositoryTest(@Autowired private val repo: PersonRepository) {

    @Test
    fun `successfully saves person`() {
        val newPerson = Person(name = "Bob")
        val savedPerson = repo.insert(newPerson)
        assertThat(savedPerson.id).isNotNull
    }

    @Nested
    inner class WhenPersonSavedToRepository {

        private var id: PersonId? = null

        @BeforeEach
        fun persistNewPerson() {
            val newPerson = Person(name = "Bob")
            val savedPerson = repo.save(newPerson)
            id = savedPerson.id
        }

        @Test
        fun `finds all persons in the repository (unfortunately throws MappingException)`() {
            val all = repo.findAll()
            assertThat(all).isNotEmpty
        }

        @Test
        fun `finds person by id (unfortunately returns nothing)`() {
            val found = repo.findByIdOrNull(id!!)
            assertThat(found).isNotNull
        }
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the supplied PersonRepositoryTest reproduction in a Spring Boot project and run the tests for findAll and findByIdOrNull. Trace the MongoDB mapping and repository behavior exposed by the example; done means both operations work with a Kotlin value class used as the document @Id, while the existing save behavior remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
kotlin, mongodb
Domain
database
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.