DEV Community

Cover image for From PHP to Kotlin - Day 2 - Value objects
Fernando Aparicio
Fernando Aparicio

Posted on • Updated on

From PHP to Kotlin - Day 2 - Value objects

First hurdle, finding and understanding the equivalent of Composer in Kotlin. In this case I was lucky to have the Jetbrains IDE (community version is enough) that makes your life easier by creating a console project from scratch.

Also in my case I had to use the 16 version of Java due to incompatibilties with 17 version.

Creating kotlin project

So far so good. Now the good begins...


1. Put the mower kata README to define the goals

As we said, laying the first stone means starting with the tests usind DDD as a structural element.

In Java and Kotlin, by heritage of the former, the main and test folder are at the same level within src.

Let's create inside main/kotlin our bounded context mower and module mower.

Java naming convention rule. The folders (or packages as it is usually called) always in lowercase.


2. Watch how to create a Value object

In this case we're going to see how to create the smallest piece of the DDD universe. The Value object.

A value object must meet the following specifications.

Specifications
Should have a primitive or another value objects
Should be immutable
Should be able to validate itself when built
Should be able to be constructed in different ways if it's necessary "named constructors"

In PHP, a Value object might look like this.

final class YCoordinate
{
    private const MINIMUM_AXIS_VALUE = 0;

    /**
     * @throws YCoordinateOutOfBoundsException
     */
    private function __construct(private int $value)
    {
        if (self::MINIMUM_AXIS_VALUE > $this->value) {
            throw YCoordinateOutOfBoundsException::withCoordinate($this->value);
        }
    }

    public static function build(int $value): self
    {
        return new self($value);
    }

    public function value(): int
    {
        return $this->value;
    }
}
Enter fullscreen mode Exit fullscreen mode

This class conforms to the standards for a Value object.

It should be noted that I am a strong advocate of using named constructors. They provide context and when an object can be created in different ways, having made a first named constructor then opens the door to adding a second or third one, focusing on the context of why that one was used and not another one that the object may have.

Now let's see if a Value object in Kotlin can be created in a similar way with the same rules.

Looking at the language specifications, it seems that the named constructor issue is different. We'll have to work on it a bit...

As we said before... we write tests with our targets for the Value object before we write anything else.

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.*
import kotlin.test.Test

private const val Y_POSITION: Int = 3
private const val INVALID_POSITION: Int = -1

internal class YMowerPositionTest
{
    @Test
    fun `Should be build` ()
    {
        var yMowerPosition = YMowerPosition.build(Y_POSITION)

        assertInstanceOf(YMowerPosition::class.java, yMowerPosition)
        assertThat(yMowerPosition.value).isEqualTo(Y_POSITION)
    }

    @Test
    fun `Should throw exception for invalid values` ()
    {
        assertThrows(Exception::class.java) {
            YMowerPosition.build(INVALID_POSITION)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Using data class

The Data class is promising... with few code you have almost everything we need, but there's a problem with named constructors that invalidates this option.

We can make it immutable declaring its values as "val" instead of "var". They accept "copy", "equals", "toString" directly without our intervention... and access values is easy... buuuuut we can't make the original constructor private, since the "copy" needs to have access to the constructor... and that would create a doorway to a contextless constructor, which is exactly what we want to avoid.

While it would have been nice to have all of that by default, just because you can't make the original constructor private, it's ruled out. Although they can still be used as DTOs.

private const val MINIMUM_AXIS_VALUE: Int = 0

data class YMowerPosition constructor (val value: Int) {

    init {
        if(value < MINIMUM_AXIS_VALUE){
            throw Exception("Invalid value")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
Data class as Value Object Meets Comments
Should have a primitive or another value objects ok -
Should be immutable ok -
Should be able to validate itself when built ok -
Should be able to be constructed in different ways if it's necessary "named constructors" ko We cannot make private primary constructor

Using normal class with "companion object"

With the companion object in a standard class we can achieve the goal of named constructors.

class YMowerPosition private constructor (val value: Int) {

    init {
        if(value < MINIMUM_AXIS_VALUE){
            throw Exception("Invalid value")
        }
    }

    @JvmStatic
    companion object {
        private const val MINIMUM_AXIS_VALUE: Int = 0

        fun build(value: Int): YMowerPosition {
            return YMowerPosition(value)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
Normal class with companion object Meets
Should have a primitive or another value objects ok
Should be immutable ok
Should be able to validate itself when built ok
Should be able to be constructed in different ways if it's necessary "named constructors" ko

You may be wondering why there is no accessor for "value".

It's simple, by being able to declare a property as immutable with "val", we can make it public, so that cannot change anymore.


Optimizing the Value object with Inline class

If we are going to host primitives, Kotlin brought out the concept of Inline classes.

Its use is intended to optimize resources and discards the concept of identity implicit in an object at the instance level.

By declaring a class as "value class", you basically just allow values to be compared with == operator.

So, our value object can be refactored a bit...

@JvmInline
value class YMowerPosition private constructor (val value: Int) {
...
}
Enter fullscreen mode Exit fullscreen mode

And the tests will continue to pass.

Inline classes have a limit!!!

They are only capable of holding a value using their constructor, so if your value object has more values when it is initialized, it should be a normal class.


Bonus track about constants and static functions

Constants can be at companion object level o at file level out of the class. I prefer within the class. There are other ways to achieve something similar, but without using the const* keyword and it doesn't make sense to me. Also, note that constants can be declared with a data type.

Static functions, in order to be called from Java, must hace tha annotation @JvmStatic. If not, Java doesn't be able to use it comfortably.

class C {
    companion object {
        @JvmStatic fun callStatic() {}
        fun callNonStatic() {}
    }
}

C.callStatic(); // works fine
C.callNonStatic(); // error: not a static method
C.Companion.callStatic(); // instance method remains
C.Companion.callNonStatic(); // the only way it works

Enter fullscreen mode Exit fullscreen mode

Links and resources

Naming conventions

https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html

https://www.oracle.com/java/technologies/javase/codeconventions-namingconventions.html

https://www.thoughtco.com/using-java-naming-conventions-2034199

Data classes

https://kotlinlang.org/docs/data-classes.html

Inline classes

https://github.com/Kotlin/KEEP/blob/master/notes/value-classes.md#inline-classes-are-user-defined-value-classes

https://kotlinlang.org/docs/inline-classes.html

Constructors and validation

https://kotlinlang.org/docs/classes.html#constructors

https://kotlinlang.org/docs/classes.html#companion-objects

Constants

https://kotlinlang.org/docs/basic-types.html#literal-constants

https://kotlinlang.org/docs/properties.html#compile-time-constants

Static functions

https://kotlinlang.org/docs/java-to-kotlin-interop.html#static-methods


Discussion (0)