Skip to content

Commit 7041397

Browse files
committed
Tutorial v2
- Follow JB Nizet JPA guidelines - Use Mockk and SpringMockK - Document Maven configuration in addition to Gradle one - Leverage CrudRepository.findIdOrNull extension - Upgrade to Spring Boot 2.1.3 - Various refactoring and improvements Closes gh-22 Closes gh-23
1 parent aa4f2fa commit 7041397

17 files changed

+512
-437
lines changed

Diff for: README.adoc

+354-253
Large diffs are not rendered by default.

Diff for: build.gradle

+17-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
buildscript {
22
ext {
3-
kotlinVersion = '1.2.41'
4-
springBootVersion = '2.1.2.RELEASE'
3+
kotlinVersion = '1.2.71'
4+
springBootVersion = '2.1.3.RELEASE'
55
}
66
repositories {
77
mavenCentral()
@@ -17,6 +17,7 @@ buildscript {
1717
apply plugin: 'kotlin'
1818
apply plugin: 'kotlin-spring'
1919
apply plugin: 'kotlin-jpa'
20+
apply plugin: 'kotlin-allopen'
2021
apply plugin: 'kotlin-kapt'
2122
apply plugin: 'org.springframework.boot'
2223
apply plugin: 'io.spring.dependency-management'
@@ -26,17 +27,21 @@ version = '0.0.1-SNAPSHOT'
2627
sourceCompatibility = 1.8
2728
compileKotlin {
2829
kotlinOptions {
29-
freeCompilerArgs = ["-Xjsr305=strict"]
30-
jvmTarget = "1.8"
30+
freeCompilerArgs = ['-Xjsr305=strict']
31+
jvmTarget = '1.8'
3132
}
3233
}
3334
compileTestKotlin {
3435
kotlinOptions {
35-
freeCompilerArgs = ["-Xjsr305=strict"]
36-
jvmTarget = "1.8"
36+
freeCompilerArgs = ['-Xjsr305=strict']
37+
jvmTarget = '1.8'
3738
}
3839
}
3940

41+
allOpen {
42+
annotation('javax.persistence.Entity')
43+
}
44+
4045
repositories {
4146
mavenCentral()
4247
}
@@ -45,22 +50,20 @@ test {
4550
useJUnitPlatform()
4651
}
4752

48-
4953
dependencies {
5054
compile('org.springframework.boot:spring-boot-starter-data-jpa')
5155
compile('org.springframework.boot:spring-boot-starter-web')
5256
compile('org.springframework.boot:spring-boot-starter-mustache')
5357
compile('com.fasterxml.jackson.module:jackson-module-kotlin')
54-
compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
55-
compile("org.jetbrains.kotlin:kotlin-reflect")
56-
compile("com.atlassian.commonmark:commonmark:0.11.0")
57-
compile("com.atlassian.commonmark:commonmark-ext-autolink:0.11.0")
58-
runtime("com.h2database:h2")
59-
kapt("org.springframework.boot:spring-boot-configuration-processor")
58+
compile('org.jetbrains.kotlin:kotlin-stdlib-jdk8')
59+
compile('org.jetbrains.kotlin:kotlin-reflect')
60+
runtime('com.h2database:h2')
61+
kapt('org.springframework.boot:spring-boot-configuration-processor')
6062
testCompile('org.springframework.boot:spring-boot-starter-test') {
6163
exclude module: 'junit'
64+
exclude module: 'mockito-core'
6265
}
63-
testCompile("com.nhaarman:mockito-kotlin:1.5.0")
6466
testImplementation('org.junit.jupiter:junit-jupiter-api')
67+
testImplementation('com.ninja-squad:springmockk:1.1.0')
6568
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine')
6669
}

Diff for: pom.xml

+14-16
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
<parent>
1515
<groupId>org.springframework.boot</groupId>
1616
<artifactId>spring-boot-starter-parent</artifactId>
17-
<version>2.1.2.RELEASE</version>
17+
<version>2.1.3.RELEASE</version>
1818
<relativePath/> <!-- lookup parent from repository -->
1919
</parent>
2020

2121
<properties>
2222
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
2323
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
2424
<java.version>1.8</java.version>
25-
<kotlin.version>1.2.41</kotlin.version>
25+
<kotlin.version>1.2.71</kotlin.version>
2626
</properties>
2727

2828
<dependencies>
@@ -50,16 +50,6 @@
5050
<groupId>org.jetbrains.kotlin</groupId>
5151
<artifactId>kotlin-reflect</artifactId>
5252
</dependency>
53-
<dependency>
54-
<groupId>com.atlassian.commonmark</groupId>
55-
<artifactId>commonmark</artifactId>
56-
<version>0.11.0</version>
57-
</dependency>
58-
<dependency>
59-
<groupId>com.atlassian.commonmark</groupId>
60-
<artifactId>commonmark-ext-autolink</artifactId>
61-
<version>0.11.0</version>
62-
</dependency>
6353
<dependency>
6454
<groupId>com.h2database</groupId>
6555
<artifactId>h2</artifactId>
@@ -72,8 +62,12 @@
7262
<scope>test</scope>
7363
<exclusions>
7464
<exclusion>
75-
<artifactId>junit</artifactId>
7665
<groupId>junit</groupId>
66+
<artifactId>junit</artifactId>
67+
</exclusion>
68+
<exclusion>
69+
<groupId>org.mockito</groupId>
70+
<artifactId>mockito-core</artifactId>
7771
</exclusion>
7872
</exclusions>
7973
</dependency>
@@ -83,9 +77,9 @@
8377
<scope>test</scope>
8478
</dependency>
8579
<dependency>
86-
<groupId>com.nhaarman</groupId>
87-
<artifactId>mockito-kotlin</artifactId>
88-
<version>1.5.0</version>
80+
<groupId>com.ninja-squad</groupId>
81+
<artifactId>springmockk</artifactId>
82+
<version>1.1.0</version>
8983
<scope>test</scope>
9084
</dependency>
9185
</dependencies>
@@ -108,7 +102,11 @@
108102
<compilerPlugins>
109103
<plugin>spring</plugin>
110104
<plugin>jpa</plugin>
105+
<plugin>all-open</plugin>
111106
</compilerPlugins>
107+
<pluginOptions>
108+
<option>all-open:annotation=javax.persistence.Entity</option>
109+
</pluginOptions>
112110
</configuration>
113111
<dependencies>
114112
<dependency>

Diff for: src/main/kotlin/blog/BlogApplication.kt

+1-32
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,12 @@
11
package blog
22

3-
import com.samskivert.mustache.Mustache
4-
import org.springframework.boot.CommandLineRunner
53
import org.springframework.boot.autoconfigure.SpringBootApplication
64
import org.springframework.boot.context.properties.EnableConfigurationProperties
75
import org.springframework.boot.runApplication
8-
import org.springframework.context.annotation.Bean
96

107
@SpringBootApplication
118
@EnableConfigurationProperties(BlogProperties::class)
12-
class BlogApplication {
13-
14-
@Bean
15-
fun mustacheCompiler(loader: Mustache.TemplateLoader?) =
16-
Mustache.compiler().escapeHTML(false).withLoader(loader)
17-
18-
@Bean
19-
fun databaseInitializer(userRepository: UserRepository,
20-
articleRepository: ArticleRepository) = CommandLineRunner {
21-
val smaldini = User("smaldini", "Stéphane", "Maldini")
22-
userRepository.save(smaldini)
23-
24-
articleRepository.save(Article(
25-
"Reactor Bismuth is out",
26-
"Lorem ipsum",
27-
"dolor **sit** amet https://projectreactor.io/",
28-
smaldini,
29-
1
30-
))
31-
articleRepository.save(Article(
32-
"Reactor Aluminium has landed",
33-
"Lorem ipsum",
34-
"dolor **sit** amet https://projectreactor.io/",
35-
smaldini,
36-
2
37-
))
38-
}
39-
40-
}
9+
class BlogApplication
4110

4211
fun main(args: Array<String>) {
4312
runApplication<BlogApplication>(*args)

Diff for: src/main/kotlin/blog/BlogConfiguration.kt

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package blog
2+
3+
import org.springframework.boot.ApplicationRunner
4+
import org.springframework.context.annotation.Bean
5+
import org.springframework.context.annotation.Configuration
6+
7+
@Configuration
8+
class BlogConfiguration {
9+
10+
@Bean
11+
fun databaseInitializer(userRepository: UserRepository,
12+
articleRepository: ArticleRepository) = ApplicationRunner {
13+
14+
val smaldini = userRepository.save(User("smaldini", "Stéphane", "Maldini"))
15+
articleRepository.save(Article(
16+
"Reactor Bismuth is out",
17+
"Lorem ipsum",
18+
"dolor sit amet",
19+
smaldini
20+
))
21+
articleRepository.save(Article(
22+
"Reactor Aluminium has landed",
23+
"Lorem ipsum",
24+
"dolor sit amet",
25+
smaldini
26+
))
27+
}
28+
}

Diff for: src/main/kotlin/blog/Entities.kt

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package blog
2+
3+
import java.time.LocalDateTime
4+
import javax.persistence.*
5+
6+
@Entity
7+
class Article(
8+
var title: String,
9+
var headline: String,
10+
var content: String,
11+
@ManyToOne var author: User,
12+
var slug: String = title.toSlug(),
13+
var addedAt: LocalDateTime = LocalDateTime.now(),
14+
@Id @GeneratedValue var id: Long? = null)
15+
16+
@Entity
17+
class User(
18+
var login: String,
19+
var firstname: String,
20+
var lastname: String,
21+
var description: String? = null,
22+
@Id @GeneratedValue var id: Long? = null)

Diff for: src/main/kotlin/blog/Extensions.kt

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fun LocalDateTime.format() = this.format(englishDateFormatter)
1010
private val daysLookup = (1..31).associate { it.toLong() to getOrdinal(it) }
1111

1212
private val englishDateFormatter = DateTimeFormatterBuilder()
13-
.appendPattern("MMMM")
13+
.appendPattern("yyyy-MM-dd")
1414
.appendLiteral(" ")
1515
.appendText(ChronoField.DAY_OF_MONTH, daysLookup)
1616
.appendLiteral(" ")
@@ -24,3 +24,10 @@ private fun getOrdinal(n: Int) = when {
2424
n % 10 == 3 -> "${n}rd"
2525
else -> "${n}th"
2626
}
27+
28+
fun String.toSlug() = toLowerCase()
29+
.replace("\n", " ")
30+
.replace("[^a-z\\d\\s]".toRegex(), " ")
31+
.split(" ")
32+
.joinToString("-")
33+
.replace("-+".toRegex(), "-")

Diff for: src/main/kotlin/blog/HtmlController.kt

+9-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import org.springframework.web.bind.annotation.PathVariable
88

99
@Controller
1010
class HtmlController(private val repository: ArticleRepository,
11-
private val markdownConverter: MarkdownConverter,
1211
private val properties: BlogProperties) {
1312

1413
@GetMapping("/")
@@ -19,32 +18,32 @@ class HtmlController(private val repository: ArticleRepository,
1918
return "blog"
2019
}
2120

22-
@GetMapping("/article/{id}")
23-
fun article(@PathVariable id: Long, model: Model): String {
21+
@GetMapping("/article/{slug}")
22+
fun article(@PathVariable slug: String, model: Model): String {
2423
val article = repository
25-
.findById(id)
26-
.orElseThrow { IllegalArgumentException("Wrong article id provided") }
27-
.render()
24+
.findBySlug(slug)
25+
?.render()
26+
?: throw IllegalArgumentException("Wrong article slug provided")
2827
model["title"] = article.title
2928
model["article"] = article
3029
return "article"
3130
}
3231

3332
fun Article.render() = RenderedArticle(
33+
slug,
3434
title,
35-
markdownConverter(headline),
36-
markdownConverter(content),
35+
headline,
36+
content,
3737
author,
38-
id,
3938
addedAt.format()
4039
)
4140

4241
data class RenderedArticle(
42+
val slug: String,
4343
val title: String,
4444
val headline: String,
4545
val content: String,
4646
val author: User,
47-
val id: Long?,
4847
val addedAt: String)
4948

5049
}

Diff for: src/main/kotlin/blog/HttpApi.kt

-32
This file was deleted.

Diff for: src/main/kotlin/blog/HttpControllers.kt

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package blog
2+
3+
import org.springframework.web.bind.annotation.*
4+
5+
@RestController
6+
@RequestMapping("/api/article")
7+
class ArticleController(private val repository: ArticleRepository) {
8+
9+
@GetMapping("/")
10+
fun findAll() = repository.findAllByOrderByAddedAtDesc()
11+
12+
@GetMapping("/{slug}")
13+
fun findOne(@PathVariable slug: String) =
14+
repository.findBySlug(slug) ?: throw IllegalArgumentException("Wrong article slug provided")
15+
16+
}
17+
18+
@RestController
19+
@RequestMapping("/api/user")
20+
class UserController(private val repository: UserRepository) {
21+
22+
@GetMapping("/")
23+
fun findAll() = repository.findAll()
24+
25+
@GetMapping("/{login}")
26+
fun findOne(@PathVariable login: String) = repository.findByLogin(login)
27+
}

Diff for: src/main/kotlin/blog/MarkdownConverter.kt

-21
This file was deleted.

0 commit comments

Comments
 (0)