水無瀬のプログラミング日記

プログラムの勉強した時のメモ

Kotlin + Spring bootでRSSリーダーを作ってみる

はじめに

諸事情によりRSSリーダーを作ろうと思った今日このごろ。
具体的に言うとSlackのRSSリーダーを使って色々なものを読んでいたんだけど、
通知が即時でうるさい + ミュートにしたらしたでスマホからだと未読がどこからかわからない。
という問題というか気になることがあったので自作してみる。
作成方針としてはAPIとして立てつつ、どっかのFaaSで定期実行でも良いようにする。

技術選定理由

技術の選定理由としては、最近Java触ってオワコン言われている(と個人的に感じてる)割に書きやすいやん!と思ったのでJavaでやる。
予定だったけど、良く良く考えたらイケてるのJavaじゃなくてlombokだったのでおとなしくKotlinを使う。
Javaも8以降StreamAPIがあったり、10からは型推論もできて HogeClass hoge = new HogeClass() みたいなの書かなくて良いし( var hoge = new HogeClass()って書ける)だいぶ書きやすいと思う。
でも便利系はやっぱりlombokが必要でアノテーション祭りになるのがなと思ってパス。
KotlinにしたのはJava使いたい + 記法がtsに似てて多分そこまで苦労しないだろう読み。

TL;DR

ソースコード

前提条件

KotlinとSpringbootが動くこと。
IntelliJ入れておけば特に困ること無いと思う。(たしか。)

RSSリーダーを作っていく

今回レイヤーアーキテクチャ風に作っていくことにする。
理由としては最近DDDの本を読んだので、試したかったから。
さっそくプロジェクトを作るところから始める。

プロジェクトセッティング

今回はSpringを使うので安定のSpring Initializrスタート。
Projectを Gradle 、Languageを Kotlin に設定。
Dependenciesに Web を追加でダウンロード。

便利そうなライブラリのインポート

大抵のやろうと思っていることは既に先人がやっているはずなのでまずはググってみる。
どうやらJavaRssリーダーを作るには Romeというやつが良いらしい。
build.gradle に下記を追記する。

dependences {
    implementation 'com.rometools:rome:1.12.0'
}

Interface層

APIとして立てるためにサクッとコントローラーを作る。
特に変なことはしてない想定。Service呼び出して取得結果を返す。

@RestController("/v1/rss")
class RssReaderController(private val rssService: RssService) {

    /**
     * Rssフィード取得
     */
    @GetMapping
    fun readRss(): List<RssModel> {
        return this.rssService.fetchRss()
    }
}

Application層

ドメイン層のClassを使うようにしている。
ここらへんアーキテクチャの理解が甘くて間違ってるかもしれないけど、
方針としてはビジネスロジックは含まない + 各ビジネスロジックを使うだけ。
一応取得するURLは apprication.yaml から読むようにした。

@Component
class RssServiceImpl(private val rssConverter: RssConverter, private val rssUseCase: RssUseCase) : RssService {
    // 自分のはてなブログURL
    @Value("\${properties.url.hatena}")
    lateinit var url: String

    override fun fetchRss(): List<RssModel> {
        val syndFeed = rssUseCase.fetchRss(url)
        return this.rssConverter.toRssModel(syndFeed)
    }
}

Domain層

UseCase

ネーミングセンスがないので、 名前が思いつかなかったので、UseCaseにした。
(幅広い名前にしちゃうと後々便利屋クラスになりそうだけど、今回は自分しか触らないのでOKとする。) やることはInfrastructure層を呼び出して使うだけ。Rssフィードを取得するメソッドを作成。

@Service
class RssUseCaseImpl(private var rssReader: RssReader): RssUseCase {

    override fun fetchRss(url: String): SyndFeed {
        return rssReader.fetchRss(url)
    }
} 

Converter

取得したRssフィードから必要なデータを取り出してデータクラスに格納する。
最大取得件数を最初はかいてたんだけどマジックナンバーイケてないし、
ここにハードコードもどうなん?ということで apprication.yaml に切り出し。
取得するデータは、記事タイトル/記事内容(一部)/記事URLの3つ。

@Service
class RssConverterImpl() : RssConverter {
    @Value("\${properties.count}")
    val count: Int = 0

    override fun toRssModel(syndFeed: SyndFeed): List<RssModel> {
        val rssList: MutableList<RssModel> = mutableListOf()
        val getMax = count - 1
        // 最新5件のみ取得
        for (i in 0..getMax) {
            val entry = syndFeed.entries[i]
            rssList.add(RssModel(entry.title, entry.description.value, entry.link))
        }

        return rssList
    }
}

Infrastructure層

実際にRSSフィードを取得するクラス。
Romeの使い方はGithubのExample丸コピなので省略

@Service
class RssReaderImpl: RssReader {

    override fun fetchRss(url: String): SyndFeed {
        return SyndFeedInput().build(XmlReader(URL(url)))
    }
}

使ってみる

ここまでで動くようになったはずなので実際に使ってみる。
試しに自分のはてなブログのデータを取ってみる。

# Springの起動
$ gradlew bootRun
# url叩くだけ(jqはお好みで)
$ curl localhost:8080/v1/rss | jq 
> [
>  {
>    "title": "Docker上でSpring bootのビルド&実行するまで",
>    "description": "はじめに JavaというかSpringを触っている今日このごろ。 ついでにDockerで動かそうと思ったのでその設定をしていく。 前提 Dockerが使えること。 設定方法については割愛。 環境 OS: macOS Mojave Docker: Docker for Mac v2.0たのは下記の通り。書いてないとこは何でも良いと思う。 項目 内容 Project Gradle project Language Java Spring boot 2.2.0(S…",
>    "url": "https://minase-program.hatenablog.com/entry/2019/03/06/233949"
>  },
>  {
>    "title": "React+Redux+TypeScript事始め",
>    "description": "はじめに 普段はAngularを使っているので、他のフレームワークを触ってみようと思った今日このごろ。 Vueは超絶軽く触ったことがあるので今回はReact。 JSXがパッと見とっつきにくかったので後回しにしてたけど、ちゃんと向き合pm install -g create-react-app # TSを使いたいからオプション指定 $ create-react-app react-sample --types…",
>    "url": "https://minase-program.hatenablog.com/entry/2019/02/13/225531"
>  },
>  ...以降続き省略

まとめ

RSSリーダーって思ったより簡単に作れる + Kotlinが結構快適。
とりあえず取得するとこまでは作ったので、次はどこかに上げることを検討することにする。
それでは今回はこの辺で。