Slide 1

Slide 1 text

Kotlinと Spring Bootと Doma2で APIサーバーを作る 2017/9/28 どこでもKotlin #2 エムスリー株式会社 前原 @maeharin

Slide 2

Slide 2 text

サンプルコード、githubにあげてます https://github.com/maeharin/kotlin-dvd-rental ● Kotlin x Spring Boot x Doma2で作ったAPIサーバーのサンプル ● DBにPostgreSQL、検索にElasticsearch(Dockerで起動) ● Postgresql tutorialのDVD Rentalサンプルデータベースを利用

Slide 3

Slide 3 text

http://www.postgresqltutorial.com/postgresql-sample-database/

Slide 4

Slide 4 text

自己紹介 ● 前原 秀徳 ● @maeharin(まえはりん) ● エムスリー株式会社 エンジニア ● チームリーダー、グループ会社取締役等を歴任 ● 自慢:ブログ記事が、はてぶ1200 ○http://maeharin.hatenablog.com/

Slide 5

Slide 5 text

エムスリーって何の会社? ・医療に関するWebサービスを多数展開 ・全世界で約400万人の医師会員 ・日本で約25万人の医師会員

Slide 6

Slide 6 text

今、私たちは10年に一度の システムリニューアルをしてます! ↓詳しくは前回の資料をご覧ください https://speakerdeck.com/maeharin/sisutemuriniyuaruto-sabasaidokotlin

Slide 7

Slide 7 text

リニューアル後の技術スタック WEBアプリ APIサーバー スマホアプリ

Slide 8

Slide 8 text

アジェンダ ● Kotlin x SpringBootでHello API ● Kotlin x Springの各種機能 ● Kotlin x Jackson ● Kotlin x Doma2

Slide 9

Slide 9 text

Kotlin x Spring Boot でHello API

Slide 10

Slide 10 text

たった3ステップ ● 1) SPRING INITIALIZRからDLしたzipを解凍 ● 2) KotlinでRestControllerを作成 ● 3) ./gradlew bootRun

Slide 11

Slide 11 text

1) SPRING INITIALIZRからDLしたzipを解凍 Kotlinを選ぶ https://start.spring.io

Slide 12

Slide 12 text

2) KotlinでRestControllerを作成 package com.example.demo.controller import org.springframework.web.bind.annotation.* @RestController class HelloController { @GetMapping fun index():String = "hello!" } src/main/kotlin/com/example/demo/controller/HelloController.kt Kotlinで書く

Slide 13

Slide 13 text

3) ./gradlew bootRun $ ./gradlew bootRun

Slide 14

Slide 14 text

以上!

Slide 15

Slide 15 text

Kotlin x Springの各種機能

Slide 16

Slide 16 text

基本的に、そのまま使える

Slide 17

Slide 17 text

Kotlinのクラスはデフォルトでfinal。普通ならopenが必要 だが... @RestController @RequestMapping("/api/v1/films") open class FilmRestController( 都度openするの大変。。。

Slide 18

Slide 18 text

kotli-springプラグインでopen不要に classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}") apply plugin: 'kotlin' apply plugin: 'kotlin-spring' compileKotlin { kotlinOptions.jvmTarget = "1.8" } compileTestKotlin { kotlinOptions.jvmTarget = "1.8" }lin-reflect:${kotlinVersion}") これのお陰でopen不要に ※Spring Initializr経由で作成する とデフォルトで入る ↓Spring Initializr経由で作成したbuild.gradle(抜粋)

Slide 19

Slide 19 text

controller @RestController @RequestMapping("/api/v1/films") class FilmRestController( private val filmRepository: FilmDomaRepository ) { @GetMapping fun index(): List = ... @PostMapping fun create( @RequestBody @Validated filmParam: FilmParam ): Int { open不要 springの アノテーションは普 通に使える

Slide 20

Slide 20 text

@Service class FilmApplicationService( private val filmRepository: FilmDomaRepository ) { @Transactional fun create(command: FilmCommand): Int { @Repository class FilmDomaRepository( private val filmDao: FilmDao ) { DI

Slide 21

Slide 21 text

@Configuration class ObjectMapperConfig { @Bean fun objectMapper(): ObjectMapper = ObjectMapper() .registerModule(JavaTimeModule()) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .registerModule(KotlinModule()) } Config

Slide 22

Slide 22 text

SpringSecurity @Configuration @EnableAuthorizationServer class Oauth2AuthorizationServerConfig( private val authManager: AuthenticationManager, private val dataSource: DataSource ) : AuthorizationServerConfigurerAdapter() { @Bean fun passwordEncoder(): BCryptPasswordEncoder = BCryptPasswordEncoder() Spring周辺の ライブラリも 問題なく使える

Slide 23

Slide 23 text

SpringFox(Swagger) @Configuration @EnableSwagger2 class SwaggerConfig { @Bean fun apiV1Document(): Docket = Docket(DocumentationType.SWAGGER_2) .groupName("api v1") .select() .paths(PathSelectors.ant("/api/v1/**")) .build() }

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Kotlin x Jackson

Slide 26

Slide 26 text

data class FilmResource( var name: String = "", var isAdmin: Boolean = false, var companyId: Int = 0 ) APIへのリクエスト(Json)をKotlinへマッピング Jacksonは デフォルト値ありの コンストラクタを要 求 デフォルト値は使い たくないのに。。

Slide 27

Slide 27 text

こうしたい data class FilmResource( val name: String, val isAdmin: Boolean, val companyId: Int )

Slide 28

Slide 28 text

jackson-module-kotlin

Slide 29

Slide 29 text

依存ライブラリに入れるだけ compile "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.0" ※Spring4.3より依存ライブラリに入れれば自動的に registerされるようになった https://spring.io/blog/2016/02/15/developing-spring-boot-applications-with-kotlin#jackson-kotlin-module

Slide 30

Slide 30 text

val source = FilmSource(film) val json = objectMapper.writeValueAsString(source) val indexRequest = IndexRequest(INDEX, TYPE, id) .source(json, XContentType.JSON) ElasticSearchとJsonやり取りする時も嬉しい data class => json

Slide 31

Slide 31 text

searchResponse.hits.map { hit -> val json = hit.sourceAsString val filmSource: FilmSource = objectMapper.readValue(json) } ElasticSearchとJsonやり取りする時も嬉しい json => data class

Slide 32

Slide 32 text

Kotlin x Doma2

Slide 33

Slide 33 text

Doma2とは ● JavaのDBアクセスフレームワーク。2Way-SQL(SQLを外出しできる) ● 社内のJavaプロジェクトではDoma2・MyBatisの利用事例が多い ● 私達のチームではDoma2を選択した ○社内に事例豊富。また、以下の開発方針を良いと思った。 https://qiita.com/nakamura-to/items/099cf72f5465d0323521

Slide 34

Slide 34 text

doma-spring-boot-starter ● spring boot向けのstarterが提供されている ● これを使うとより簡単に使いはじめることができる

Slide 35

Slide 35 text

DomaはKotlin 1.1.2を実験的にサポート http://doma.readthedocs.io/ja/stable/kotlin-support/

Slide 36

Slide 36 text

EntityをKotlinで http://doma.readthedocs.io/ja/stable/kotlin-support/

Slide 37

Slide 37 text

DaoはJava http://doma.readthedocs.io/ja/stable/kotlin-support/

Slide 38

Slide 38 text

kaptで注釈処理 http://doma.readthedocs.io/ja/stable/kotlin-support/

Slide 39

Slide 39 text

以下のバージョンで試したところ、問題なく動いた ● Kotlin 1.1.2 ● Doma 2.16.1

Slide 40

Slide 40 text

ただ、バージョン上げると... ● Kotlin 1.1.4に上げるとエラー ● だが、compileKotlin.dependsOn processResourcesを指定すれ ば動いた ● しかし、Domaを2.17.0にあげるとエラー(原因は未調査) ↓ ● 回避策はあるかもしれないが、できればハマるリスクを少なく したい

Slide 41

Slide 41 text

Domaの層は全てJavaで書く。という選択肢 http://doma.readthedocs.io/ja/stable/kotlin-support/

Slide 42

Slide 42 text

私達の選択:DomaはJava。それ以外はKotlin ● よく触るのはDomaの層ではなく、それを使う層 ● 「使う層」を全てKotlinにすれば、Kotlinの恩恵を十分受けられる ● Domaのコード(Java)はdoma-genという自動生成ツールを使って生成す ることも可能 ○生成されるコードをコンフィグでカスタマイズ可能 ○細かく制御したければ、テンプレートのカスタマイズも可能 ● doma-genを使わないとしても、Domaのコードにロジックを書かなければ Javaのツラミはあまり感じない

Slide 43

Slide 43 text

ディレクトリ例 Java domaに関するコード のみ Kotlin 上記以外全て SQL

Slide 44

Slide 44 text

Javaの部分: DomaのDao @ConfigAutowireable @Dao public interface FilmEntityDao { @Select FilmEntity selectById(Integer filmId); @Select List selectAll(); @Insert int insert(FilmEntity entity);

Slide 45

Slide 45 text

Javaの部分: DomaのEntity @Entity(naming = NamingType.SNAKE_LOWER_CASE) @Table(name = "film") public class FilmEntity { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) @SequenceGenerator(sequence = "film_film_id_seq") public Integer filmId; public String title; public String description; SQL実行結果のマッピ ングと捉え、ロジックを 書かないようにした

Slide 46

Slide 46 text

● Javaといいつつほとんどアノテーション ● domaのコードはDDDでいうところのドメイン層ではなくインフラ 層と考える(domaのEntityはDDDでいうドメイン層のEntityでは なく、インフラ層のSQL実行結果を抽象化したデータと捉え、 Kotlinで書いたドメイン層において、ドメインオブジェクトに都度 マッピングする)

Slide 47

Slide 47 text

Kotlinの部分: Repository @Repository class FilmDomaRepository( ... ) { fun findAll(): List { val entities = filmDao.selectAll() return Film.createByFilmEntities(entities) } Java(DomaのEntity)からKotlin(Model, ValueObject)に マッピング

Slide 48

Slide 48 text

Kotlinの部分: Model, ValueObject class Film( val id: Int? = null, val title: String, val description: String?, val language: Language, val actors: List, val categories: List ) { ...

Slide 49

Slide 49 text

Kotlinの部分: Service @Service class FilmApplicationService( ... ) { @Transactional fun create(command: FilmCommand): Int { val film = Film( command = command, language = langRepo.findById(command.languageId) ?: throw NotFoundException(), actors = actorRepo.findByIds(command.actorIds), categories = categoryRepo.findByIds(command.categoryIds) ) val filmId = filmRepo.store(film) mailService.sendCreated(film) return filmId }

Slide 50

Slide 50 text

Kotlinの部分: Controller @RestController @RequestMapping("/api/v1/films") class FilmRestController( private val filmRepository: FilmDomaRepository ) { @GetMapping fun index(): List = filmRepository.findAll().map(::FilmResource)

Slide 51

Slide 51 text

よく触る部分は 全部Kotlin!

Slide 52

Slide 52 text

まとめ ● Kotlin x Spring BootでAPIサーバーは特に問題なく作れる ● kotlin-springプラグインのおかげでopen不要 ● jackson-module-kotlinのおかげでdata classとのマッピングが楽に ● doma2はKotlinを実験的にサポート ○domaのentityをKotlinにできる。だが、kaptの不安定な挙動リスクあり ● 私達はdoma2の層をjavaにした ○ドメイン層、アプリケーション層はKotlinなのでほぼKotlin ● Kotlinかわいい(^ω^)ペロペロ

Slide 53

Slide 53 text

エムスリー love Kotlin ● 別件のリニューアルにもサーバーサイドKotlinを採用予定 ● 新規Androidアプリ開発はKotlin ● 新規Webアプリ開発の際にも積極的にKotlinを検討

Slide 54

Slide 54 text

WE'RE HIRING! Kotlinerの方々、 一緒にやりましょう!