これはQiita Kotlin Advent Calendar 2018 1日目の記事です。
サーバサイドKotlinのコード公開したらみんな見てくれるかな?そこで開発出来そう!ってなったらサーバサイドKotlinのエンジニアになりたくなるかな?
— shiraji (@shiraj_i) 2018年12月13日
ツイートしたら良い反応もらえたので、サーバサイドKotlinのアプリを以下で公開する事に決めました。
cloneして、docker-compose up
したら、GraphiQLが http://localhost:8090/graphiql 立ち上がって、サーバの動作確認が出来る。簡単!!!
Ubie内で絶賛開発中のシステムに少し手を加えていますが、基本的に実際に開発している環境と同じになっています。
公開目的
公開に至った理由はいくつかあります。
サーバサイドKotlinやりたいって人のハードルを下げたかった
やりたい!という声はいろんなところで聞くけど、「環境ないし・・・。」「経験無いし・・・。」という方も多いと思ってます。実際Ubie入社前の自分がそうでした。
このプロジェクトは、clone後すぐにローカルで動かす事が可能です。
そのため、このお手軽さを見てもらい、もっと多くの方にサーバサイドKotlinやってもらいたいなーと思いました。
Kotlin+SpringBoot+GraphQLの開発者にコードレビューしてもらいたかった。または仲良くなりたかった
今回、色々な事情があり、KotlinでGraphQLの採用をしています。自分にKotlin+GraphQLの実績がないため、コードを公開することにより、いろんな人に見てもらい、より良いものになったら良いなと思いました。さらに同じような環境の方ともっと仲良くなりたいです!
特に後述しますが、多対多のGraphQLResolver
の処理が本当にこんな感じのコードで良いのかが不明です。Spring+GraphQLの情報が少なすぎる。。。もっと効率良く出来そうなんですが・・・
同じ構成のものをOSS化することにより、Ubieの入社即パフォーマンス発揮!をしてもらいたかった
今のところ、Ubieのサーバサイドに関わる人には入社前に1日インターンをしてもらい、チームに溶け込めるか?を見てもらう時間を取っています。しかし、時間が1日しかなく、環境構築に時間を取られてしまうのはもったいないです。そのため、同じ環境をOSS化することにより、先に環境構築出来る状況にしたいと考えました。
さらに将来的には1日インターン宗教上無理!って方にこのOSSプロダクト開発に携わってもらい、雰囲気をそこで味わってもらうのもいいかなと考えています。
技術スタック
このプロジェクトでは以下の技術スタックを利用しています。
- Git/Github
- CircleCI
- Docker
- PostgreSQL
- Spring Boot
- Kotlin
- Gradle(Kotlin Script)
- GraphQL/graphql-java
- Flyway
- Logback
- ktlint
- JaCoCo(ただしまだテストはない)
今後テスト書くので、以下が追加予定
- JUnit5
- Mockk
- WebTestClient
パッケージ構成
app.ubie.kotlingraphqlsample ├── domain ├── infrastructure │ └── jdbc ├── presentation │ ├── queryresolver │ └── resolver └── service
(Query)Resolver → Service → JDBCという呼び出し階層になってます。
DB
薬と傷病の関係性のサンプルテーブルになっています。医療業界で利用されているデータに合わせ、IDがなかったり、外部キーがUniqueじゃなかったり、全て多対多の仕様になっています。
DBアクセスはspringがデフォルトで提供しているJDBCを使い、SQLをコードに書いてDBアクセスしています。これは賛否両論あると思いますが、以下の理由から採用しています。
Flywayを利用して自動マイグレーションが走る状態にしてあります。
GraphQL
GraphQLのスキーマ定義ファイルには薬情報と病気情報が定義されています。
type Query { # 薬を取得します drugs(yjCode: String!) : [Drug!] # 病気を取得します diseases(icd: String!) : [Disease!] } # 薬情報 type Drug { # 薬品名 name: String! # YJ Code。だいたい薬を表すために使われるCode。一意ではないので注意 yjCode: String! # 併用禁忌薬 kinkiDrugs: [Drug!] # 処方してはいけない特定の病気 kinkiDiseases: [Disease!] } # 病気 type Disease { # 傷病に対してつくコード。一意ではないので注意 icd: String! # 病名 name: String! # 処方されてはいけない薬 kinkiDrugs: [Drug!] }
Queryブロックには公開APIが定義されています。graphql-javaではこの定義をGraphQLQueryResolver
を実装したクラスで記述する必要があります。このプロジェクトではqueryresolver
パッケージ内で定義されています。
@Component class DiseaseQueryResolver(private val service: DiseaseService) : GraphQLQueryResolver { fun diseases(icd: String): List<Disease> = service.getDiseases(icd) }
@Component class DrugQueryResolver(val drugService: DrugService) : GraphQLQueryResolver { fun drugs(yjCode: String): List<Drug> = drugService.getDrugs(yjCode) }
Drug/Diseaseブロック内にあるkinkiDrugs
などの外部キーを使った結合はgraphql-javaではGraphQLResolver<T>
を実装する必要があります。resolver
パッケージ内で定義されています。typeがDrug
のkinkiDrugs
の取得時にはfun getKinkiDrugs(drug: Drug): List<Drug>
が呼び出される。
@Component class DiseaseResolver(private val service: DiseaseKinkiDrugService, private val drugService: DrugService) : GraphQLResolver<Disease> { fun getKinkiDrugs(disease: Disease): List<Drug> { val kinkiDrugs = service.findKinkiDrugsByIcd(disease.icd) return drugService.getDrugs(kinkiDrugs.map { it.yjCode }) } }
@Component class DrugResolver( private val kinkiDrugService: KinkiDrugService, private val diseseKinkiDrugService: DiseaseKinkiDrugService, private val drugService: DrugService, private val diseaseService: DiseaseService ) : GraphQLResolver<Drug> { fun getKinkiDrugs(drug: Drug): List<Drug> { val kinkiDrugs = kinkiDrugService.findKinkiDrugsByYjCode(drug.yjCode) return drugService.getDrugs(kinkiDrugs.map { it.kinkiYjCode }) } fun getKinkiDiseases(drug: Drug): List<Disease> { val kinkiDrugs = diseseKinkiDrugService.findKinkiDrugsByYjCode(drug.yjCode) return diseaseService.getDiseases(kinkiDrugs.map { it.icd }) } }
多対多な関係が多く、SQLの発行回数が若干多いなーという印象があります。この辺りどうしたらいいのか誰かと語りたい!!!
終わりに
このコード読んで、UbieでサーバサイドKotlinやってみたいと思った方は @shiraj_i に直接DMか以下のWantedlyさんのサイトから話を聞きにきて下さい!
もっと気軽にUbieに遊びに来たい!という方は以下のBosyuさんからメッセージを送って下さい!