3

I am familiar with several approaches to how this problem can be solved, but probably I miss something really useful:

I have an API entity:

data class SomeApiEntity(
    val id: Long? = null,

    @field:NotBlank
    val name: String,

    val someIntData: Long,
)

And Spring JPA Entity:

@Entity(name = "some_entity")
class SomeEntity(
    @field:NotBlank
    @Column(name = "name", nullable = false)
    val name: String,

    @Column(name = "some_int_data", nullable = false)
    val someIntData: Long,
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    var id: Long? = null
}

Now I need to come up with a way to convert these entities from one to another.

The most popular solution is to implement methods in these entities which should look like this:

This one in SomeApiEntity

fun toApiEntity(): SomeApiEntity = SomeApiEntity(
        id,
        name,
        intData,
    )

And this one in SomeEntity class.

fun toEntity(): SomeEntity = SomeEntity(
        name,
        intData
    )

But I find this solution unsuccessful and poorly scalable.

Large entities, especially with conversion logic, and especially if other Spring Beans are used there, it becomes possible to test. Well, small entities, like here, become unified with large ones.

I know the solution with implementing Converter<S, T> interface in Spring, but it is also bad because it solves only one-way conversion and gives us a small overhead in terms of the amount of code that will have to be written in the future.

Someone
  • 131
  • 3

1 Answers1

2

I'm not sure if there's a best practice particular on Spring, but on Android development, I tend not to create those methods on the class itself (e.g. a method to convert a Entity to a DTO on the Entity class). The reason why I don't do it is that I will tightly couple both classes together and I also don't think is the Entity's reposability to know how to map itself to a domain and vice versa.

What I do is I usually create a extension function that does the same, but on a separate file, like so:

fun SomeDTO.toApiEntity() = SomeApiEntity(
   id = id,
   name = name,
   intData = intData,
)

You can test it just like any other method, if you want:

@Test
fun `GIVEN SomeDTO object WHEN toSomeApiEntity called THEN must return SomeEntity with same values`() {
   // GIVEN
   val name = "Jonh"
   val id = 1L
   val intData = 254

   val someDTO = SomeDTO(id = id, name = name, intData = intData)
   val expectedSomeEntity = SomeEntity(id = id, name = name, intData = intData)

   // WHEN
   val actualSomeEntity = someDTO.toSomeApiEntity()

   // THEN
   Assert.AsserEquals(expectedSomeEntity.id, actualSomeEntity.id)
   Assert.AsserEquals(expectedSomeEntity.name, actualSomeEntity.name)
   Assert.AsserEquals(expectedSomeEntity.intData, actualSomeEntity.intData)
}

If your SomeApiEntity class is a data class and all it's atributes are data classes as well, you can simplify the assertion, like so:

Assert.AsserEquals(expectedSomeEntity, actualSomeEntity)
Leonardo Sibela
  • 1,613
  • 1
  • 18
  • 39