# Ktor勉強会 #004
## 前回(#003)の内容
- HackMD
- https://hackmd.io/4_9hMrPUSre3xRxY0cK9pQ?view
- Gist(HackMDの内容と同じ)
- https://gist.github.com/yoshixmk/8720d48ad19bbf2d5675cde0937ecb92
- repository
- https://github.com/yoshixmk/ktor-sample
## 参加者
- うえき
- やました
- あっきー
## 開催日
2020/05/17(日) 13:00ごろ
## 宿題
* Heroku アカウントつくっておく
* [x] うえき
* [x] やました
* [x] あっきー
* Heroku CLIを Terminal で打てるようにしておく
* [x] うえき
* [x] やました
* [x] あっきー
## Agenda
ざっくり書くとこんな感じ
- heroku
- クリーンアーキテクチャ(Users)
- クリーンアーキテクチャ化(既存のMemos) + Test(既存のMemosに対して)
### heroku
- やましたさんのパート
- [資料](https://ktor.io/servers/deploy/hosting/heroku.html)
```bash
# heroku-cli/6.15.36 (darwin-x64) node-v9.9.0より大きなら大丈夫かな
$ heroku --version
heroku/7.41.1 darwin-x64 node-v12.16.2
# 資料の内容を書いてね
$ touch app.json
# 資料の内容を書いてね
$ touch system.properties
```
#### みんなの予想
* `app.json` を作る
* `Procfile` をつくる
* `system.properties` を作る
* `git push master heroku`
* 動く!!!
### 実録
* `heroku create ktor-sample-<unique key>`
* ブラウザ立ち上がるのでログイン
* 名前をktor-sample-xxxとして作成。自由にunique key考えて。
* `git remote`
* `heroku` が追加されているはず
* prerequirement
* `app.json` つくる
* `Procfile` つくる
* `system.proeprties` つくる
* 以下のファイルがあること
* `build.gradle`
* `settings.gralde`
* `gradlew`
* `gradle/wrapper/gradle-wrapper.jar`
* `gradle/wrapper/gradle-wrapper.properties`
* `build.gradle` に `stage` taskを追加する
* `git push heroku master`
* pushできる、でも動くか不明
* やましたはbuildがしっぱしたけど、他のひとなら通るかも、やってみてください
* herokuのログを見る: `heroku logs -t`
* herokuの再起動(pushしない時): `heroku restart`
#### `app.json`
```=json
{
"name": "demo-comprendre",
"description": "demo-comprendre",
"image": "heroku/java",
"addons": [ "heroku-postgresql" ]
}
```
##### `build.gralde`
##### やました
`stage` を追加
```=gradle
task stage(dependsOn: ['build', 'clean'])
build.mustRunAfter clean
```
##### かわかみ
コマンドライン上で、以下のコマンドを実行する事によって、 `gradle task` の `stage` を設定しなくてもよくなった
```
heroku config:set GRADLE_TASK="shadowJar"
```
##### `Procfile`
`jar` の名前は変更してください。
```=Procfile
web: java -jar build/libs/demo-comprendre-0.0.1-SNAPSHOT.jar
```
##### `system.properties`
```
java.runtime.version=1.8
```
##### やましたはこれでうごいた
`https://pacific-cove-85277.herokuapp.com/`
**ただしまだDBはうまくいかない**
##### うえき `application.conf` にて `port = ${PORT}` を設定してる場合、環境変数で80をいれる。

##### DB接続までやる場合
下記をやれば動く(はず)
- application.confで定義した環境変数設定を追加する
- flyway migrationをする
### [クリーンアーキテクチャ](https://blog.tai2.net/the_clean_architecture.html)

[Hexagonal Architecture](https://blog.tai2.net/hexagonal_architexture.html) (別名 Ports and Adapters)、[Onion Architecture](http://jeffreypalermo.com/blog/the-onion-architecture-part-1/)、[Screaming Architecture](http://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html)などのアーキテクチャを参考に作られた、綺麗なアーキテクチャ。アーキテクチャでは、一番バズってると思う
原点にサンプルコードがないので、「やってみた系」の記事が目立つ。
- [参考1: いつもの、社内エンジニアが書いたブログ](https://rinoguchi.hatenablog.com/entry/2020/04/24/100000#DI%E4%BE%9D%E5%AD%98%E6%80%A7%E3%81%AE%E6%B3%A8%E5%85%A5%E3%81%AE%E5%B0%8E%E5%85%A5)
- [参考2: Qiita repository by kaonash](https://qiita.com/kaonash/items/41f605c10bbff413eabe#infrastructure)
- [参考2: Github repository by kaonash](https://github.com/kaonash/ktor-clean-architecture-sample)
**参考にした内容も、構成の良さそうなところだけ真似ていくことに。**
**鵜呑みにせず納得できなければ、自分の書き方に変えてたりするので、間違ってたら、ごめんなさい**
1. package作る
1. interfaceのやりとり作る
1. class実装
1. DI導入
1. Memosの内容をそれっぽく移動(各自)
#### package作る
サンプルの、複数形ではなかったところは複数形に。
interface実装では、ixxxという感じの名前にした
(図はkaonashさんのもの)

```bash=
# Ktorの仕組みはこの層だけ
$ mkdir -p src/infrastructures/routes
$ touch src/infrastructures/routes/.gitkeep
# Exposedの仕組みはこの層だけ
$ mkdir -p src/databases/repository
$ touch src/databases/repository/.gitkeep
# serviceを使う。responseデータへ詰め替え。
$ mkdir -p src/interfaces/controllers
$ mkdir -p src/interfaces/repository
$ touch src/interfaces/controllers/.gitkeep
$ touch src/interfaces/repository/.gitkeep
# use-cases -> usecases ハイフンあんまり見ないので。
# dtoがこの層にあったが、domainsに配置
$ mkdir -p src/usecases/service
$ touch src/usecases/service/.gitkeep
# common-libはdomainsに変えました
$ mkdir -p src/domains/users
$ touch src/domains/users/.gitkeep
# https://github.com/nrslib/CleanArchitecture/blob/master/CleanArchitectureSample/Domain/Domain/Users/IUserRepository.cs
$ mkdir -p src/domains/irepository
$ touch src/domains/irepository/.gitkeep
```
ドメインを追加します
- domains.users.User
```kotlin=
data class User(val id: Long, val familyName: String, val givenName: String) {
val fullName = "$familyName $givenName"
}
data class Users(val users: List<User>)
```
#### interfaceのやりとり作る
先ほど作ったドメインクラスを使って、interfaceで仮実装していきます
ドメインを返す、repositoryのinterface
- domains.irepository.IUserRepository
```kotlin= :yoshixmk.domains.irepository
package domains.irepository
import domains.users.User
interface IUserRepository {
fun findById(userId: Long): User?
}
```

[出典](https://terasolunaorg.github.io/guideline/public_review/ImplementationAtEachLayer/DomainLayer.html#id10)
- usecases.service.UserService
- ファイル名は、IUserServiceでも良いが、あとでここにUserService同居させたいため
```kotlin= UserService
interface IUserService {
fun findById(userId: Long): User
}
```
- interfaces.controllers.UserController
- usecasesと同様の理由で、ファイル名にはIをつけてない
- IN(request data): リクエストデータの形式を決定
- OUT(response data): レスポンスデータの形式を決定
```kotlin= IUserController
package interfaces.controllers
interface IUserController {
fun getUser(userId: UserId): UserResponse
}
// IN
data class UserId(var id: Long)
// OUT
data class UserResponse(var userId: Long, var familyName: String, var givenName: String)
```
#### class実装
- databases.repository.UserRepository
- domainを返す仮実装です。
```kotlin=
class UserRepository : IUserRepository {
override fun findById(userId: Long): User? {
// TODO DBからのデータ形式を、domainsに詰め替え。Service層にinject
return User(1, "Test", "Taro")
}
}
```
- usecases.service.UserServiceに実装
- IUserServiceを具象化
```kotlin=
・・・
class UserService(
private val userRepository: IUserRepository
) :
IUserService {
override fun findById(userId: Long): User {
return userRepository.findById(userId) ?: throw IllegalStateException("No User Found for Given Id")
}
}
・・・
```
- interfaces.controllers.UserController
- IUserControllerを具象化
```kotlin= UserController
・・・
class UserController(private val userService: IUserService) : IUserController {
override fun getUser(userId: UserId): UserResponse {
return userService.findById(userId.id).toResponse()
}
}
private fun User.toResponse() = UserResponse(id, familyName, givenName)
```
- ルーティングの実装
- UserRoutes.kt
```kotlin= :Routing.users
fun Routing.users() {
// TODO injectを用いる
val userController: UserController = UserController(UserService(UserRepository()))
route("v1/users") {
route("{id}") {
get {
val userId = call.parameters["id"]?.toLongOrNull() ?: return@get call.respond(
HttpStatusCode.BadRequest,
"Invalid parameter: [${call.parameters["id"]}]"
)
call.respond(userController.getUser(UserId(userId)))
}
}
}
}
```
- Routes.kt
```kotlin=
fun Routing.routes() {
// ユーザ機能
users()
}
```
- ここまでうまくできていれば、モックで作成したテストユーザの内容が見れます
- http://localhost:8080/v1/users/1

#### DI導入
次に、変更容易性を保つための仕組みとしてDI(Dependency Injection)を取り入れます。
これは依存関係(Dependency)をオブジェクト内のコードに直接記述せず、外部から注入(Inject)することで、組み替え可能にする、デザインパターンです
- DIには、[Koin](https://github.com/InsertKoinIO/koin)を使用する
- [参考: いつもの(再掲)](https://rinoguchi.hatenablog.com/entry/2020/04/24/100000#DI%E4%BE%9D%E5%AD%98%E6%80%A7%E3%81%AE%E6%B3%A8%E5%85%A5%E3%81%AE%E5%B0%8E%E5%85%A5)
- gradle.properties
```groovy=
・・・
koin_version=2.1.5
```
- build.gradle
```groovy=
buildscript {
・・・
dependencies {
classpath "org.koin:koin-gradle-plugin:$koin_version"
}
}
apply plugin: 'koin'
dependencies {
implementation "org.koin:koin-ktor:$koin_version"
}
```
- 前述で、UserRoutesに書いてた、これは消してね!
```kotlin=
・・・
val userController: UserController = UserController(UserService(UserRepository()))
・・・
```
- KoinModules.kt
- 今後大きくなるので、最初から外だしで定義
```kotlin= :KoinModules.kt
val koinModules = module(createdAtStart = true) {
singleBy<IUserController, UserController>()
singleBy<IUserService, UserService>()
singleBy<IUserRepository, UserRepository>()
}
```
- Application.kt
- 追記
```kotlin=
install(org.koin.ktor.ext.Koin) {
// あとでtestKoinModules作ります
modules(koinModules)
}
```
- UserRoutes.kt: IUserControllerへInject
- ここは明示的にinjectを書きます
```kotlin=
import org.koin.ktor.ext.inject
fun Routing.users() {
val userController: IUserController by inject()
・・・
}
```
- UserController.kt: IUserServiceへInject
- Controller以降はコンストラクタに受け取るだけで良い
```kotlin=
interface IUserController {
fun getUser(userId: UserId): UserResponse
}
class UserController(private val userService: IUserService) : IUserController {
override fun getUser(userId: UserId): UserResponse {
return userService.findById(userId.id).toResponse()
}
}
- UserService.kt: IUserRepositoryへinject
- ここもコンストラクタに受け取るだけで良い
```kotlin=
interface IUserService {
fun findById(userId: Long): User
}
class UserService(private val userRepository: IUserRepository) : IUserService {
}
```
- ここまで修正して、再度テストユーザの内容が見れることを確認します
- http://localhost:8080/v1/users/1

- ここまでの修正内容
https://github.com/yoshixmk/ktor-sample/pull/3
#### Memosの内容に、クリーンアーキテクチャを
各自で、Usersを参考に書き換えをやりましょう
- interfaceの名前は、下記のようにする。
- フレームワークやエンジニアによって、やり方に差異がある気がしてます。[Spring framework の CrudRepository](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html)などは、save()としてcreateもしくはupdateを隠蔽しています。
- 個人的には、上記の`CrudRepository`のようなことをすると、エンドポイントで分割されてたCRUDがくっついたり離れたりするので、余計なコードが増えてしまうので、意図的にRepositoryをUseCase層寄りに自由度を高めます
- 逆の視点からすると、同じ理由で、クエリの発行も、ソートまでかけた方がコストが安いなどあるため、UseCase層でソート処理などはしないことにします
- MemoRepository.kt
```kotlin= IMemoRepository.kt
interface IMemoRepository {
fun findById(id: Int): Memo?
fun findAll(): List<Memo>
fun findAllSortedById(): List<Memo>
fun create(memo: Memo): Memo
fun update(memo: Memo): Memo?
fun deleteById(id: Int): Long
}
```
- MemoService.kt
```kotlin=
interface IMemoService {
fun findById(userId: Int): Memo?
fun findAllSortedById(): List<Memo>
fun create(subject: String): Memo
fun update(id: Int, subject: String): Memo?
fun deleteById(id: Int): Long
}
```
- IMemoController.kt
```kotlin=
interface IMemoController {
fun getMemo(memoId: MemoId): Memo
fun getMemos(): List<Memo>
fun postMemo(memo: MemoContent): Int
fun putMemo(memo: Memo): Memo
fun deleteMemo(memoId: MemoId): Unit
}
// IN
data class MemoId(val id: Int)
// IN
data class MemoContent(val subject: String)
// IN or OUT
data class Memo(val memo_id: Int, val subject: String)
```
- ここまでのPR
- https://github.com/yoshixmk/ktor-sample/pull/4/files
- 厳密には、少し足りないです。
== Memos ==
## 次回の開催予定
- 2020/05/23(土) 13:00
- 6(React) : 4(Ktor)
### 次回、「テスト part2」
- 今日できなかったとこ
### 次回、「Locations」
- Routingにプラスする感じでできそうな気がしたのでやってみる
### Front作る
- React with TypeScript
- create-react-apps
- react-dev-server
- サンプル作る?
- ktor講習会のmemoをブラウザからいじれるようなかんじの物をつくる
### 本日の成果
* うえき
* やました
* [20200517](https://github.com/jamashita/demo-comprendre/releases/tag/20200517)
* あっきー