Skip to content

Commit 5a628f1

Browse files
committed
update docs
1 parent e111200 commit 5a628f1

File tree

2 files changed

+34
-33
lines changed

2 files changed

+34
-33
lines changed

exercise-1.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This project uses maven for building the application, for gradle projects the sa
88

99
Prepare the maven pom.xml for Kotlin.
1010

11-
**Exercise**: add the `<kotlin.version>` and `<kotlin.compiler.incremental>true</kotlin.compiler.incremental>` maven property to the pom.xml. We will use this property to define the version of Kotlin dependencies used in this project.
11+
**Exercise**: add the `<kotlin.version>` and `<kotlin.compiler.incremental>true</kotlin.compiler.incremental>` properties to the maven pom.xml. We will use this property to define the version of Kotlin dependencies used in this project.
1212

1313
```xml
1414
<properties>
@@ -34,7 +34,7 @@ By adding `<kotlin.compiler.incremental>true</kotlin.compiler.incremental>` we i
3434
</dependency>
3535
```
3636

37-
You project is now ready to use the Kotlin standard library and reflection utilities which are used by frameworks like Jackson.
37+
You project is now ready to use the Kotlin standard library and reflection utilities which are used by frameworks like Spring Boot and Jackson.
3838

3939
### Add the Kotlin maven plugin
4040

exercise-2.md

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Open `OrderItem.java`.
1616

1717
**Exercise**: convert OrderItem.java to Kotlin using IntelliJ _menu > Code > Convert Java File to Kotlin File_.
1818

19-
The outcome of the conversion is far from optimal, because of the Java class being a bit more complex. We can do better!
19+
The outcome of the conversion is far from optimal, because of the Java class being a (little) bit more complex. We can do better!
2020

2121
Let get rid of the `equals()`, `hashCode()` and `toString()` boiler-plate. Kotlin has a feature for that: [data classes](https://kotlinlang.org/docs/reference/data-classes.html). Lets do this step by step.
2222

@@ -26,21 +26,24 @@ With data classes we get the `equals()`, `hashCode()` and `toString()` functions
2626

2727
**Exercise**: remove the `equals()`, `hashCode()` and `toString()` functions.
2828

29-
In the converted code we ended up with a [constructor](https://kotlinlang.org/docs/reference/classes.html#constructors) on class level called the primary constructor, and next to that the secondary constructor.
29+
In the converted code we ended up with a primary [constructor](https://kotlinlang.org/docs/reference/classes.html#constructors) and next to that a secondary constructor.
3030

31-
In many situations we can get rid of the duplicate constructors by merging the [constructors](https://kotlinlang.org/docs/reference/classes.html#constructors) into a single primary constructor.
31+
In many situations we can get rid of the generated duplicate constructors by merging the [constructors](https://kotlinlang.org/docs/reference/classes.html#constructors) into a single primary constructor by providing default values for some of the constructor arguments.
3232

33-
Notice that the `productId` argument in the two constructors are different, one which is the non-nullable `val productId: String` and the other which is nullable `val productId: String?`. Since productId should be a mandatory field, we will use the null safe version in the next exercise.
33+
Notice that the `productId` and `price` arguments in the two constructors are of the nullable type String.
3434

35-
**Exercise**: merge the two constructors into primary constructor but *don't remove* the `@JsonCreator` and `@JsonProperty` annotations. Hint, the constructor keyword on class level is mandatory when you want to use annotations on the primary constructor.
35+
**Exercise**: merge the two constructors into primary constructor but *don't remove* the `@JsonCreator` and `@JsonProperty` annotations. Hint, you can also use the constructor keyword on class level when you want to use annotations on the primary constructor just yet. Make all arguments non-nullable and don't provide a default value.
3636

3737
<details>
3838
<summary>Suggested solution:</summary>
3939

4040
```kotlin
41-
data class OrderItem @JsonCreator constructor(@JsonProperty("productId") val productId: String,
42-
@JsonProperty("quantity") val quantity: Int,
43-
val price: BigDecimal) {
41+
data class OrderItem @JsonCreator constructor(
42+
@JsonProperty("productId") val productId: String,
43+
@JsonProperty("quantity") val quantity: Int,
44+
val price: BigDecimal
45+
) {
46+
4447
val totalPrice: BigDecimal
4548
get() = price.multiply(BigDecimal(quantity))
4649
}
@@ -72,9 +75,9 @@ for JSON property price due to missing (therefore NULL) value for creator parame
7275
at [Source: (PushbackInputStream); line: 1, column: 30] (through reference chain: com.bootique.bootique.OrderItem["price"])
7376
```
7477

75-
In the POST body we send only two fields `{"productId":"1","quantity":2}` for an OrderItem, these fields are directly mapped onto the OrderItem class. This used to work when there were two constructors, but after the merge we ended up with a single constructor which requires 3 non-nullable (mandatory) parameters.
78+
In the POST body we send only two fields `{"productId":"1","quantity":2}` for an OrderItem, these fields are directly mapped onto the OrderItem class. This used to work when there were two constructors, but after the merge we ended up with a single constructor which requires 3 non-nullable (mandatory) arguments.
7679

77-
How can we fix this with Kotlin? First lets try to make the price field (optional) nullable and add the ? after the BigDecimal type. You will probable notice that the totalPrice calculation is now also giving you a hard time. Price can now be nullable therefore we need to add null safety checks using the ? operator in the totalPrice calculation, see the snippet below:
80+
How can we fix this with Kotlin? First lets try to make the price field nullable and add the question mark to the BigDecimal type. The totalPrice calculation uses the price property and should handle the value being null by adding the null-safe operator (?.) to the totalPrice calculation, like in the snippet below:
7881

7982
```kotlin
8083
data class OrderItem @JsonCreator constructor(@JsonProperty("productId") val productId: String,
@@ -85,7 +88,8 @@ data class OrderItem @JsonCreator constructor(@JsonProperty("productId") val pro
8588
}
8689
```
8790

88-
A better approach would be to avoid having to deal with null values all together so that way we don't have to worry about potential NPEs. We can do this by providing a default value for the price argument. This way we dont have to explicitly provide a value, but it will never be null. In the Java version price was assigned the value of BigDecimal.ZERO, use that here as well.
91+
A much better approach would be to avoid having to deal with null values all together and not having to worry about potential NPEs.
92+
We can do this by providing a default value for the price argument. This way we can construct the OrderItem without explicitly providing a value for price, but it will never be null. In the Java version price was assigned the value of BigDecimal.ZERO, use that here as well.
8993

9094
**Exercise**: assign a default value, BigDecimal.ZERO to the price argument.
9195

@@ -97,43 +101,41 @@ data class OrderItem @JsonCreator constructor(@JsonProperty("productId") val pro
97101
@JsonProperty("quantity") val quantity: Int,
98102
val price: BigDecimal = BigDecimal.ZERO) {
99103
val totalPrice: BigDecimal
100-
get() = price.multiply(BigDecimal(quantity)) // evaluated every time we access the totalPrice property or call getTotalPrice() from Java.
104+
get() = price.multiply(BigDecimal(quantity))
101105
}
102106
```
103107
</details>
104108
<br>
105109

106-
In the code snippet above the constructor arguments are val, immutable, which means after assignment the value cannot be changed. The `val totalPrice` is calculated based in these immutable properties, because of this we can simplify the code and write the totalPrice calculation as an expression. Would it not be nice if we could write it like:
110+
In the code snippet above the constructor arguments are all immutable now, which means that after initialization their values cannot be changed.
107111

108-
```kotlin
109-
val totalPrice: BigDecimal = price * quantity
110-
```
112+
The `val totalPrice` which is called a property in Kotlin, calculates the totalPrice based on two immutable properties (price and quantity). Since both properties are `vals` (immutable) we can get rid of the get() method on the property. Getters on properties are useful when the expression needs to be evaluated every time the property is accessed.
111113

112-
Or even more concise like this:
114+
But the solutin is not yet the idiomatic Kotlin way of writing code, because it would be even nicer if we could write it like:
113115

114116
```kotlin
115-
val totalPrice = price * quantity
117+
val totalPrice: BigDecimal = price * quantity
116118
```
117119

118-
Lets do this step by step but the application wont work until the end of this exercise!
120+
Lets do this step by step (complete all the exercises before restarting the application).
119121

120-
**Exercise:** change the totalPrice calculation to the snippet below:
122+
**Exercise:** instead of multiply use the times operator and change the totalPrice calculation to the snippet below:
121123

122124
```kotlin
123125
val totalPrice: BigDecimal = price * BigDecimal(quantity)
124126
```
125127

126-
This is not yet how we want to write the expression because we still have to wrap the quantity in a BigDecimal in order to be able to do calculations with BigDecimal. Notice that in Java this way of doing calculations on BigDecimals would not be possible!
128+
We still need to wrap the quantity property in a BigDecimal in order to be able to do calculations with BigDecimal instances.
127129

128-
The Kotlin stdlib includes [overloaded operators](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/java.math.-big-decimal/index.html) for Java types like java.math.BigDecimal, this allows use the * (times) operator on BigDecimal.
130+
Note that performing these kinds of calculations with BigDecimals is not possible in Java since it does not support operator overloading! In Kotlin this is possible, but limited to a few predefined operators only. The Kotlin stdlib includes [overloaded operators](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/java.math.-big-decimal/index.html) for Java types like java.math.BigDecimal, this allows use the * (times) operator on BigDecimal.
129131

130-
Have a look at the signature for times operator on `java.math.BigDecimal`.
132+
Now have a look at the signature for times operator on `java.math.BigDecimal` in IntelliJ (Cmd+Click).
131133

132134
```kotlin
133135
public operator inline fun java.math.BigDecimal.times(other: java.math.BigDecimal): java.math.BigDecimal
134136
```
135137

136-
Note that `price * BigDecimal(quantity)` under the hood is exactly the same as `price.times(BigDecimal(quantity))`, this is just syntactic sugar.
138+
Note that `price * BigDecimal(quantity)` under the hood is identical to `price.times(BigDecimal(quantity))`, consider it to be syntactic sugar.
137139

138140
What we want is being able to invoke the times function with a Int argument so that we do not need to wrap our Int in a BigDecimal. We can implement our own overloaded operator, similar to the one in the Kotlin stdlib.
139141

@@ -148,27 +150,26 @@ operator fun BigDecimal.times(quantity: Int) = this.times(BigDecimal(quantity))
148150
</details>
149151
<br>
150152

151-
You can group these custom extensions like overloaded operators in a Kotlin files, so that they are easily recognizable and can be shared and reused in other parts of the code.
153+
You can add the overloaded operator to the Kotlin file from which you are using it, but you can also group extensions and overloaded operators in a separate Kotlin file so that they are easily recognizable and can be shared and reused in other parts of the code.
152154

153155
### Polishing the code
154156

155-
This application communicates over HTTP using JSON, as you have seen in Swagger or in the curl command in the README.md. The JSON (de)serialization in this application is handled by the Jackson library, the spring-boot-starter-web (Spring Boot 2!) dependency pulls in the Jackson dependencies for us.
157+
This application communicates via JSON over HTTP, as you have seen in Swagger or in the curl command in the README.md. The JSON (de)serialization in this application is handled by the Jackson library, the spring-boot-starter-web (Spring Boot 2!) dependency pulls in the Jackson dependencies for us.
156158

157159
In the BasketController the JSON data is mapped from the POST data directly on the OrderItem class. As you can see, in the OrderItem class we are instructing the Jackson library, with the `@JsonCreator` and `@JsonProperty`, how to map the JSON data to our Java (or Kotlin) class.
158160

159-
Reason for having the `@JsonProperty` annotation is that when compiling Java code, the parameter names of the constructor parameters are lost, so Jackson does not know how to map the json properties to the OrderItem class. In Kotlin, constructor parameter names are preserved when compiling code. We can therefore get rid of the `@JsonProperty` annotations.
161+
Reason for having the `@JsonProperty` annotation is that when compiling Java code, the parameter names in the constructor are lost, Jackson does not know how to map the json properties to the OrderItem class. In Kotlin, parameter names are preserved when compiling the code and stores as meta data. We can therefore get rid of the `@JsonProperty` annotations. As a bonus feature, the Jackson library also allows us to omit the `@JsonCreator` annotation when using Kotlin.
160162

161-
As a bonus feature, the Jackson library also allows us to omit the `@JsonCreator` annotation when using Kotlin, these features are provided by the `jackson-kotlin-module`. This dependency needs to be explicitly added to the project pom.xml!
163+
This functionality is provided by the `jackson-kotlin-module`. This dependency needs to be added explicitly to the maven pom.xml!
162164

163-
When we don't have any annotations on the class definition we can omit the constructor keyword as well.
165+
Once we have no `@JsonCreator` annotations on the `OrderItem` class we can omit the Kotlin constructor keyword as well.
164166

165-
**Exercise**: add the `jackson-module-kotlin` to the maven `pom.xml` (note that `${jackson.version}` is defined by Spring Boot)
167+
**Exercise**: add the `jackson-module-kotlin` to the maven `pom.xml` (note that the version of this module is [managed by Spring Boot](https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html)).
166168

167169
```xml
168170
<dependency>
169171
<groupId>com.fasterxml.jackson.module</groupId>
170172
<artifactId>jackson-module-kotlin</artifactId>
171-
<version>${jackson.version}</version>
172173
</dependency>
173174
```
174175

0 commit comments

Comments
 (0)