Kotlin Spring Boot REST Template

Build Status Coverage Status

Architecture

Package By Feature

Package-by-feature uses packages to reflect the feature set. It tries to place all items related to a single feature (and only that feature) into a single directory/package. This results in packages with high cohesion and high modularity, and with minimal coupling between packages. Items that work closely together are placed next to each other. They aren't spread out all over the application. It's also interesting to note that, in some cases, deleting a feature can reduce to a single operation - deleting a directory. (Deletion operations might be thought of as a good test for maximum modularity: an item has maximum modularity only if it can be deleted in a single operation.)

Source: http://www.javapractices.com/topic/TopicAction.do?Id=205

HTTP

Thread HTTP only as your transporting layer. Try to avoid leaking HTTP logic into your briskness logic. Utilize domain-driven design.

Build

Default

gradle

Build

gradle build

Run

gradle run

Tests

gradle check

Continuous tests

gradle test -t

or

gradle test --continuous

Integration tests

gradle integrationTest

One-Jar

gradle assemble

java -jar build/libs/{NAME}-assembly-${VERSION}.jar

java -Dspring.profiles.active=dev -jar build/libs/{NAME}-assembly-${VERSION}.jar
java -Dspring.profiles.active=qa -jar build/libs/{NAME}-assembly-${VERSION}.jar
java -Dspring.profiles.active=staging -jar build/libs/{NAME}-assembly-${VERSION}.jar
java -Dspring.profiles.active=production -jar build/libs/{NAME}-assembly-${VERSION}.jar

java -Denvironment=dev -jar build/libs/{NAME}-assembly-${VERSION}.jar
java -Denvironment=qa -jar build/libs/{NAME}-assembly-${VERSION}.jar
java -Denvironment=staging -jar build/libs/{NAME}-assembly-${VERSION}.jar
java -Denvironment=production -jar build/libs/{NAME}-assembly-${VERSION}.jar

Code coverage

gradle jacocoTestReport
open build/reports/jacoco/test/html/index.html

PostgreSQL

Docker

docker run --name softwareberg-postgres-db -p 5432:5432 -e POSTGRES_DB=softwareberg -e POSTGRES_USER=softwareberg -e POSTGRES_PASSWORD=softwareberg -d postgres:9.6

Library

build.gradle:

// ...
dependencies {
    // ...
    compile 'org.postgresql:postgresql:9.4.+'
    // ...
}
// ...

Configuration

application.properties:

# ...
datasource.jdbcUrl=jdbc:postgresql://localhost:5432/softwareberg
datasource.username=softwareberg
datasource.password=softwareberg
# ...

Heroku

Test on local

heroku local web

Deploy

heroku login
heroku create
git push heroku master
heroku logs -t

or

heroku git:remote -a NAME_OF_APP
git push heroku master
heroku logs -t

https://spring-boot-kotlin-template.herokuapp.com/

IntelliJ

Remember to turn on "Annotation Processors"

Annotation Processors

Random

package pl.michalkowol.repository.simple

import org.springframework.stereotype.Repository

/*
| Annotation | Meaning                                             |
+------------+-----------------------------------------------------+
| @Component | generic stereotype for any Spring-managed component |
| @Repository| stereotype for persistence layer                    |
| @Service   | stereotype for service layer                        |
| @Controller| stereotype for presentation layer (spring-mvc)      |
 */

data class Address(val street: String, val city: String)
data class Person(val name: String, val addresses: List<Address>)

@Repository
open class SimplePeopleRepository {
    /*
    Why does it have `open` modifier?
    Unable to proxy method [public final java.util.List pl.michalkowol.repository.simple.SimplePeopleRepository.findByName(java.lang.String)]
    because it is final: All calls to this method via a proxy will NOT be routed to the target instance.
     */
    open fun findOne(id: Long): Person? {
        if (id == 1L) {
            return Person("Michal", listOf(Address("Chemiczna", "Gliwice"), Address("Pulawska", "Warszawa")))
        } else if (id == 2L) {
            return Person("Kasia", listOf(Address("Przybylskiego", "Warszawa")))
        }
        return null
    }

    open fun findAll(): List<Person> {
        val people = listOf(findOne(1), findOne(2)).filterNotNull()
        return people
    }

    open fun findByName(name: String): List<Person> {
        val people = listOf(findOne(1), findOne(2)).filterNotNull()
        return people.filter { it.name == name }
    }
}

Links

References

References