本記事は、Kotlin Advent Calendar 2021 24日目の記事になります。
以前書いたKotlessに関する記事で作ろうとしているアプリケーションがあると言っていたのですが、その実装でやろうとしていることを使って、ちょっとしたアプリケーションを作って紹介したいと思います。
Kotlessとは?
JetBrains社純正のKotlinのサーバーレスフレームワークです。
簡単に言うと、Kotlinの実装とGradleタスクでAWS Lambdaにアプリケーションをデプロイしたりできるものです。
Kotless自体の説明や導入方法などは以下の記事で紹介していますので、そちらをご覧ください。 blog.takehata-engineer.com
サンプルコード
前回のKotless記事を書いた際に公開していたプロジェクトに、今回のアプリケーションの入ったディレクトリを追加しています。
一部Twitter API関連で設定が必要で、必要な対応はREADMEに書いてあるので、動かしてみたい方はこちらも参考にしてください。
作ろうと思っているアプリケーション
まず、作ろうとしているアプリケーションについて簡単に説明します。
自分の過去のツイートを日ごとに閲覧できるアプリケーション(カコミエールというアプリの模倣)
カコミエールというアプリがあります。
apps.apple.comこれは自分のTwitterで過去ツイートした全ての内容が日ごとに見れるアプリケーションです。 例えば12月22日だった場合、Twitterを開始した年から現在日時の前年までの12月17日のツイートが全部表示されます。
例)もし2018年に始めた人が、12月24日に見た場合
2018年12月24日
2019年12月24日
2020年12月24日
のツイートがリストで全部表示される。
愛用していたのですが、Twitter APIの仕様変更などの影響か、私のアカウントではツイートが表示されなくなってしまいました。
そのため自分のアカウントで説明ができないのですが、現在も使えてる方もいるようなので、画面を実際に見たい方はTwitterで #カコミエール で検索するなどしていただくと良いと思います。
2018年を最後にアップデートもされておらず、今後も使えるようになる見込みがないため、類似のアプリで少し機能を拡張したものなどを作ろうと考えました。
通常のTwitter APIでは実現が不可能だった
ただ、Twitter APIは過去1週間分のデータまでしか取得できないしようになっています。
そのため、今回のように自分がTwitterを始めてから現在までのツイートを対象に取得することはできません。
search/universalというAPIを使うとできるらしいのですが、本来一般人が使用するようには公開されておらず、"公式のConsumerKey/ConsumerSecret"を使わないとできないということで怪しくて使いたくなく・・・
あとはstatuses/user_timelineというAPIを使うことでユーザー指定でツイートのリストを取得することはできるのですが、こちらも3200件までという制限があるため全然足りません。
名前の通り、あくまでタイムラインをリアルタイムで流す時などに使うもののようです。
Twitter APIで取得したデータを日々保存し、自分用のアプリケーションとして最小限の構成で作る
そのため別の方法を取ろうと考えました。
その方法は
- DailyでTwitter APIを叩き、ツイートの情報をDynamoDBに保存する
- アプリで表示するツイートはDynamoDBから取得する
というものです。
これならTwitter APIの制限も気にすることなく、使うことができます。
問題はDailyで収集し始める前のツイートをどうするかですが、これに関してはAPIではなくTwitterの画面からアーカイブをダウンロードする方法があり、これで取得したJSONファイルから登録する予定です。
実装の事前準備
実装前に必要な環境などの準備です。
基本劇な環境の構築方法は以下の記事で紹介しています。
こちらで作成した環境に対して、追加で必要なことを今回は解説していきます。
Twitter APIの登録とAPIキー、トークンの取得
Twitter APIを使用するにあたって、登録してAPIキーとトークンを取得する必要があります。
以下のサイトなどを参考に、ご自分のTwitterアカウントで登録してください。
DynamoDBのテーブル作成
DynamoDBに、Tweet
という名前で以下のテーブルを作成します。
キーの種類 | 物理名 | 意味 | 型 |
---|---|---|---|
パーティションキー | id | ツイートのID(パーティションキー) | 数値 |
ソートキー | - | - | - |
パーティションキーとしてツイートのIDをidという名前で保持しています。
Twitter APIから取得した各ツイートに振られているLong値のIDになります。
ソートキーはなしです。
中に入るデータとしては、以下のようなデータになります。
物理名 | 意味 | 型 |
---|---|---|
id | ツイートのID | 数値 |
tweet_date | ツイートした年月日 | 文字列 |
tweet_time | ツイートした時分秒 | 文字列 |
tweet_text | ツイートした文章 | 文字列 |
パーティションキーで指定したツイートのIDと、ツイートの年月日と時分秒、ツイートした文章を保存します。
また、グローバルセカンダリインデックスとして、datetime-index
という名前で以下を指定します。
パーティションキー | ソートキー |
---|---|
tweet_date | tweet_time |
日を指定しての検索が必要になるので、日をパーティションキーとし、一意にするために時間をソートキーとして持たせています。
今回の実装はグローバルセカンダリインデックスを使った検索だけになりますが、後でidを指定しての取得も実装する予定なのと、構造として直感的なのでテーブルのパーティションキーはidにしています。
使用するいくつかのライブラリの依存関係を追加
build.gradle.ktsに、以下の依存関係を追加します。
dependencies { implementation("io.kotless", "kotless-lang", "0.1.7-beta-5") implementation("com.amazonaws:aws-java-sdk-dynamodb:1.12.126") implementation("org.twitter4j:twitter4j-core:4.0.7") implementation("dev.akkinoc.util:yaml-resource-bundle:2.1.0") }
今回はKotless DSLを使用します。
その他の追加している依存関係は以下になります。
- aws-java-sdk-dynamodb・・・DynamoDBを扱うためのJavaのSDK
- twitter4j-core・・・Twitter APIを実装するTwitter4Jというライブラリ
- yaml-resource-bundle・・・YAMLで定義した情報をJavaやKotlinのコードにバンドルするためのライブラリ
yaml-resource-bundleは、TwitterのAPIキーなどの認証で使う情報をコードから切り出してYAMLで管理したいため使用しています。
Twitterの認証で使う情報を記述した設定ファイル
src/main/resources
配下に、twitter.yaml
という名前で以下の内容のファイルを作成します。
consume_key: XXXXXXXXXXXXXXXXXXXXX consume_secret: XXXXXXXXXXXXXXXXXXXXX access_token: XXXXXXXXXXXXXXXXXXXXX access_token_secret: XXXXXXXXXXXXXXXXXXXXX account_name: hogehoge
consume_key
〜access_token_secret
は、前述のTwitter APIの登録後に取得した情報を記述してください。
account_name
には、取得したいご自身のTwitterのアカウント名を設定します。
Twitter4JからTwitter APIを実行する際に、これらの情報を使用します。
Dialyでツイートを収集しDynamoDBに登録する関数
ではここからコードの実装に入っていきます。
まずはDailyでツイートを収集してDynamoDBに登録する関数を作っていきます。
src/main/kotlin
配下に任意の名前でKotlinのファイルを作成し、関数を追加していきます。
登録処理の全体は、以下になります。
const val TWITTER_TIME_FORMAT = "\"%d-%02d-%02d_%02d:%02d:%02d_JST\"" const val TABLE_DATE_FORMAT = "%d-%02d-%02d" const val TABLE_TIME_FORMAT = "%02d:%02d:%02d" val twitterConfig = ResourceBundle.getBundle("twitter", YamlResourceBundle.Control) @DynamoDBTable("Tweet", PermissionLevel.ReadWrite) object TweetTable { private val twitterClient = TwitterFactory( ConfigurationBuilder().setDebugEnabled(true) .setOAuthConsumerKey(twitterConfig.getString("consume_key")) .setOAuthConsumerSecret(twitterConfig.getString("consume_secret")) .setOAuthAccessToken(twitterConfig.getString("access_token")) .setOAuthAccessTokenSecret(twitterConfig.getString("access_token_secret")) .build() ).instance fun putTweetList(accountName: String, since: String, until: String): List<Tweet> { val query = Query("from:$accountName since:$since until:$until") val queryResults = twitterClient.search(query).tweets val tweetList = queryResults.map { Tweet(it.id, LocalDateTime.ofInstant(it.createdAt.toInstant(), ZoneId.systemDefault()), it.text) } val client = AmazonDynamoDBClientBuilder.defaultClient() tweetList.forEach { val time = it.time val values = mapOf( "id" to AttributeValue().withN(it.id.toString()), "tweet_date" to AttributeValue().withS( TABLE_DATE_FORMAT.format( time.year, time.month.value, time.dayOfMonth ) ), "tweet_time" to AttributeValue().withS(TABLE_TIME_FORMAT.format(time.hour, time.minute, time.second)), "tweet_text" to AttributeValue().withS(it.text) ) val request = PutItemRequest().withItem(values).withTableName("Tweet") client.putItem(request) } return tweetList } @Scheduled("0 0 1/1 * ? *") private fun putTweetList() { val accountName = twitterConfig.getString("account_name") val lastDate = LocalDateTime.now(ZoneId.of("Asia/Tokyo")).minusDays(1) val year = lastDate.year val month = lastDate.month.value val day = lastDate.dayOfMonth val since = TWITTER_TIME_FORMAT.format(year, month, day, 0, 0, 0) val until = TWITTER_TIME_FORMAT.format(year, month, day, 23, 59, 59) putTweetList(accountName, since, until) } }
上から順に解説していきます。
各種定数とtwitter.yamlのバンドル
まずトップレベルに記述している、定数とtwitter.yamlをバンドルしている部分です。
const val TWITTER_TIME_FORMAT = "\"%d-%02d-%02d_%02d:%02d:%02d_JST\"" const val TABLE_DATE_FORMAT = "%d-%02d-%02d" const val TABLE_TIME_FORMAT = "%02d:%02d:%02d" val twitterConfig = ResourceBundle.getBundle("twitter", YamlResourceBundle.Control)
TWITTER_TIME_FORMAT
は、Twitter APIでツイートを検索する時に使用する日時文字列のフォーマットです。
詳しくは後述しますが、yyyy-MM-dd_HH:mm:ss_JST
の形式で指定します。
TABLE_DATE_FORMAT
とTABLE_TIME_FORMAT
は、DynamoDBのテーブルに登録する年月日と、時分秒のフォーマットです。
そしてtwitterConfig
は、前述のtwitter.yamlで定義した情報をバンドルしています。
第一引数に、バンドルしたいYAMLのファイル名を指定しています。
DynamoDBのテーブルと関連付けるアノテーション
TweetTableというobjectを作り、@DynamoDBTable
というアノテーションを付与しています。
@DynamoDBTable("Tweet", PermissionLevel.ReadWrite) object TweetTable {
これはDynamoDBのテーブル紐付けて権限などの設定を記述するもので、引数にはテーブル名と権限のレベルを書いています。
このアノテーションを付けることにより、作成されるLambdaの関数のIAMロールに、DynamoDBへのアクセスのポリシーを一緒に追加してくれます。
ここではPermissionLevel.ReadWrite
を渡しているので、AmazonDynamoDBFullAccess
のポリシーがされます。
このアノテーションを設定していない場合は、自分でポリシーをアタッチしないとDynamoDBへのアクセスができずエラーになります。
Twitter APIを実行するClientの生成
objectの直下でTwitter APIを実行するClientの生成をしています。
private val twitterClient = TwitterFactory( ConfigurationBuilder().setDebugEnabled(true) .setOAuthConsumerKey(twitterConfig.getString("consume_key")) .setOAuthConsumerSecret(twitterConfig.getString("consume_secret")) .setOAuthAccessToken(twitterConfig.getString("access_token")) .setOAuthAccessTokenSecret(twitterConfig.getString("access_token_secret")) .build() ).instance
Twitter4JのTwitterFactoryというクラスを使い、Twitter APIの各種APIキーやトークンを使用して生成します。
twitterConfig.getString("xxxx")
で取得しているのは、前述のtwitter.yamlで設定した値です。
ファイルのトップレベルのところでバンドルしていたtwitterConfig
から、getString
関数にYAMLのキー名を指定することで取得できます。
ツイートの取得
putTweetList
という関数で、Twitterのアカウント名と検索対象と開始、終了日時を引数に取り、それを使ってTwitter APIを実行しています。
fun putTweetList(accountName: String, since: String, until: String): List<Tweet> { val query = Query("from:$accountName since:$since until:$until") val queryResults = twitterClient.search(query).tweets val tweetList = queryResults.map { Tweet(it.id, LocalDateTime.ofInstant(it.createdAt.toInstant(), ZoneId.systemDefault()), it.text) }
Query
クラスの引数に渡しているのが検索条件のクエリです。
fromでアカウント名を指定、sinceとuntilで検索対象の開始日時と終了日時を指定しています。
Twitterの期間検索には、前述の定数で設定していたyyyy-MM-dd_HH:mm:ss_JST
の形式の日時文字列が必要で、引数のsinceとuntilもこの形式で渡ってくる想定になります。
ちなみにTwitterの検索窓に同様の構文で検索すると、ユーザーと期間でツイートを検索することができます。
例)
from:n_takehata since:2021-12-17_00:00:00_JST until:2021-12-17_23:59:59_JST
そしてsearch
関数の結果のオブジェクトに含まれるtweets
に各ツイートの情報のListが入っています。
この取得結果をmapで前述のTweet
クラスのListに変換しています。
DynamoDBへの登録処理
続いてDynamoDBへの登録処理です。
val client = AmazonDynamoDBClientBuilder.defaultClient() tweetList.forEach { val time = it.time val values = mapOf( "id" to AttributeValue().withN(it.id.toString()), "tweet_date" to AttributeValue().withS( TABLE_DATE_FORMAT.format( time.year, time.month.value, time.dayOfMonth ) ), "tweet_time" to AttributeValue().withS(TABLE_TIME_FORMAT.format(time.hour, time.minute, time.second)), "tweet_text" to AttributeValue().withS(it.text) ) val request = PutItemRequest().withItem(values).withTableName("Tweet") client.putItem(request) } return tweetList
AmazonDynamoDBClientBuilder.defaultClient()
でdefaultのAWSのcredentialsで、DynamoDBへ接続するclientを生成します。
そしてTwitter APIから取得したtweetList
をforEachで回し、順にDynamoDBへ登録していきます。
登録するレコードはKeyがDynamoDBのテーブルのカラム名を指定する文字列、Valueが登録する値を設定したAttributeValue
型のMapで指定します。
PutItemRequest
クラスに登録レコードのMapとテーブル名を指定してrequestを作成し、putItem
メソッドを実行することでDynamoDBのAPIが叩かれ、登録処理が実行されます。
Dailyで前日の日付で登録処理を実行する関数
ここまでで登録処理はできましたが、さらにそれをDailyで定期実行する処理が必要です。
@Scheduled("0 0 1/1 * ? *") private fun putTweetList() { val accountName = twitterConfig.getString("account_name") val lastDate = LocalDateTime.now(ZoneId.of("Asia/Tokyo")).minusDays(1) val year = lastDate.year val month = lastDate.month.value val day = lastDate.dayOfMonth val since = TWITTER_TIME_FORMAT.format(year, month, day, 0, 0, 0) val until = TWITTER_TIME_FORMAT.format(year, month, day, 23, 59, 59) putTweetList(accountName, since, until) }
@Scheduled
アノテーションでAWSのCron形式の文字列を渡すと、その設定でCloudWatch Eventsで関数に時間のトリガーが設定されます。
各フィールドは左から分 時 日 月 曜日 年
を表しています。
ここではday(3番目の数字)に1/1
と指定しています。
*
と?
を渡している箇所はワイルドカードで、全ての値が対象となります。
/
で区切った数値は左が初期値、右が増分値を表していて、この設定では日が1から1ずつの増やした数値のタイミングで、実行されます。
つまり「全ての年月で、1日から1日毎(毎日)の0時0分に実行する」という設定になります。
/
の右側を例えば3にした場合は、1日から3日毎(1日、4日、7日...)という設定になります。
(UTCの0時0分なので、日本時間では毎日9時に実行されます)
Cron形式については以下の記事なども参考に読んでいただくと良いと思います。
@Scheduledの引数は定数が用意されているが、everyDayの値にバグがある
本来KotlessではアノテーションのScheduledクラスに、Cron形式の文字列が定数として定義されており、以下のように指定することができます。
// 毎時実行 @Scheduled(Scheduled.everyHour) // 毎日実行 @Scheduled(Scheduled.everyDay)
しかし現在は以下のようになっていて、今回使いたいeveryDay
の指定が間違っているバグがあります。
@Target(AnnotationTarget.FUNCTION) annotation class Scheduled(val cron: String, val id: String = "") { @Suppress("unused") companion object { const val everyMinute = "0/1 * * * ? *" const val every5Minutes = "0/5 * * * ? *" const val every10Minutes = "0/10 * * * ? *" const val everyHour = "0 0/1 * * ? *" const val every3Hours = "0 0/3 * * ? *" const val everyDay = "0 0 0/1 * ? *" } }
日のフィールドは本来1〜31の数値で指定しないと行けないのですが(0日は存在しないため)、初期値に0を指定してしまっています。
そのため使用するとフォーマットの不正でエラーになります。
Exception in thread "main" java.lang.RuntimeException: CronExpression '0 0 0 0/1 * ? *' is invalid.
現在は一旦PRを送ってみているので、これが取り込まれるか修正が入るかしたら、こちらの定数で指定するように変更したいと思います。 github.com
DynamoDBから特定の日の過去ツイートを取得する関数
続いてはDynamoDBから特定の日のツイートを取得する関数を解説します。
こちらも前述のTweetTable
のobject内に作成します。
全体としては以下になります。
fun getTweetListByMonthDay(month: Int, day: Int): Map<Int, List<GetTweetListResponse>> { val twitterUser = twitterClient.showUser(twitterConfig.getString("account_name")) val startYear = LocalDateTime.ofInstant(twitterUser.createdAt.toInstant(), ZoneId.systemDefault()).year val currentYear = LocalDateTime.now().year val client = AmazonDynamoDBClientBuilder.defaultClient() val table = DynamoDB(client).getTable("Tweet") val index = table.getIndex("datetime-index") val tweetMap = mutableMapOf<Int, List<GetTweetListResponse>>() for (year in startYear..currentYear) { val date = "$year-$month-$day" val query = QuerySpec() .withProjectionExpression("id, tweet_date, tweet_time, tweet_text") .withKeyConditionExpression("tweet_date = :v_date") .withValueMap(ValueMap().withString(":v_date", date)) val queryResults = index.query(query) tweetMap[year] = queryResults.map { GetTweetListResponse( it.getLong("id"), "${it.getString("tweet_date")} ${it.getString("tweet_time")}", it.getString("tweet_text") ) } } return tweetMap }
こちらも順に上から解説していきます。
これと別でレスポンスで使用するデータクラスと、ルーティングする関数が必要になりますが、そちらは後述します。
レスポンスのデータクラス
まず、取得した値をAPIのレスポンスとして返却する際に使用するデータクラスを先に作ります。
data class GetTweetListResponse(val id: Long, val time: String, val text: String)
Tweet
クラスとほぼ同様ですが、timeを表示用の形式の文字列として保持しています。
Twitterの開始年と現在の年を取得
関数の引数としては月と日を受け取り、戻り値の値としてMapを返しています。
fun getTweetListByMonthDay(month: Int, day: Int): Map<Int, List<GetTweetListResponse>> { val twitterUser = twitterClient.showUser(twitterConfig.getString("account_name")) val startYear = LocalDateTime.ofInstant(twitterUser.createdAt.toInstant(), ZoneId.systemDefault()).year val currentYear = LocalDateTime.now().year
過去全ての年で、指定した日のツイートを取得するという仕様なので、年をKey、年毎のツイートのListをValueとしたMapを返す形になります。
対象の年ですが、まずtwitterClient.showUser
でTwitter APIからユーザー情報を取得し、そのレスポンスのcreatedAt
からTwitterを開始した年を取得します。
そして現在日時から、現在の年を取得します。
この「Twitterを開始した年」から「現在の年」までが対象の範囲となります。
DynamoDBのインデックスにクエリを実行するオブジェクトの生成
検索のクエリは、テーブルでなくグローバルセカンダリインデックスに対して実行するので、インデックスに対してアクセスするためのオブジェクトを作成します。
val client = AmazonDynamoDBClientBuilder.defaultClient() val table = DynamoDB(client).getTable("Tweet") val index = table.getIndex("datetime-index")
client
を作るところまでは登録処理と一緒ですが、そこから更にDynamoDB
クラスを使用してテーブル名を取得し、テーブルのオブジェクトからインデックスのオブジェクトを取得しています。
それぞれgetTable
、getIndex
の引数で対象のテーブル名、インデックス名を指定することで取得できます。
年ごとにDynamoDBからデータを取得してMapに追加する処理
インデックスにクエリを実行して、取得した結果で戻り値のMapを作っていきます。
val tweetMap = mutableMapOf<Int, List<GetTweetListResponse>>() for (year in startYear..currentYear) { val date = "$year-$month-$day" val query = QuerySpec() .withProjectionExpression("id, tweet_date, tweet_time, tweet_text") .withKeyConditionExpression("tweet_date = :v_date") .withValueMap(ValueMap().withString(":v_date", date)) val queryResults = index.query(query) tweetMap[year] = queryResults.map { GetTweetListResponse( it.getLong("id"), "${it.getString("tweet_date")} ${it.getString("tweet_time")}", it.getString("tweet_text") ) } } return tweetMap
Twitterを開始した年から現在の年までが対象となるため、startYear..currentYear
でループを回します。
そして引数のmonth
、day
と結合して検索条件用の文字列date
を作ります。
クエリはQuerySpec
を使用して作成します。
withProjectionExpression
では取得するカラム名を指定し(RDBでいうSELECT句に当たる部分)、withKeyConditionExpression
では検索条件を指定します(RDBでいうWHERE句に当たる部分)。
:v_date
としているところはパラメータで、withValueMap
で値を設定することができます。
ValueMap
に対して、今回は引数の方が文字列なのでwithString
を使って指定しています。
そしてインデックスのオブジェクトのquery
関数を、QuerySpec
で作成したクエリのオブジェクトを引数に渡して実行すると、検索結果が取得できます。
検索結果はCollectionとして取得できるので、mapでGetTweetListResponse
に変換し、year
のKeyに対してのValueとしてMapに追加します。
全部の年のツイートをMapに追加したら、それを返却して完了です。
リクエストで月日を受け取ってツイートの取得処理を呼び出すルーティングの関数
検索処理はAPIとして用意するので、最後にルーティングの関数を追加します。
@Get("/find") fun findTweet(month: Int, day: Int) = getTweetListByMonthDay(month, day)
@Get
でGETのAPIとして定義します。
Kotless DSLではルーティングの関数に引数を定義すると、クエリパラメータの値がバインディングされます。
これでクエリパタメータで月、日の値を受け取り、前述のgetTweetListByMonthDay
を実行して返却するAPIができました。
plan、deployを実行して動作確認
アプリケーションの実装は完了したので、plan
を実行して変更内容を確認し、deploy
を実行してアプリケーションをデプロイします。
$ ./gradlew plan
$ ./gradlew deploy
デプロイ後放置すると、DailyでDynamoDBにツイートのデータが積まれていきます。
そして以下のように検索のAPIを月日を指定して実行すると、当該の日のツイートを年毎のListにしたMapが返却されます。
$ curl "https://mdibxq3aai.execute-api.us-west-2.amazonaws.com/1/find?month=12&day=17" {2011=[], 2012=[], 2013=[], 2014=[], 2015=[], 2016=[], 2017=[], 2018=[], 2019=[], 2020=[], 2021=[GetTweetListResponse(id=1471816801135001604, time=2021-12-17 12:17:25, text=満を持して強力なMacBook Proを注文した。メモリ64GBの世界がどんなもんなのか楽しみ。 ただ納品が遠い https://t.co/Q4UeiMPJI2)]}
(まだ最近のデータしか入れていないので、2021年以外は空配列で返ってきています)
改善しようと思っている点
今回のサンプルとしては一旦仕組みは作ったのですが、今後実際に使えるようにするまで以下の点は改善していこうと思っています。
- 年と月日を別のカラムにして、月日だけで検索できるようにする
- 検索のAPI実装はKtor DSLで別Functionとして実装
年毎にループ回して検索する仕様になっていたのですが、今回の実装ならDynamoDBに入れる時点で年と日を分けたカラムにしてしまえば月日の指定で一回のクエリで取れることに気づいたので、修正しようと思っています。
もともとTwitter APIを直接検索かける想定で作り始めていたので、それに引っ張られた仕様になってしまっていました。
そして今回は登録も検索も全部を同じLambdaのFunctionに詰め込んでいるのですが、登録のバッチはこのままKotless DSLで、検索のAPIはKtor DSLを使って別Functionにしようと考えています。
APIの実装などはKtor DSLを使っている方が、書き慣れているのもあり実装しやすいためです(規模が小さいのであんまり変わらないといえば変わらないですが)。
あとはDynamoDBに保存しておくカラムの精査や、いくつかの機能追加はしようと思います。
まとめ
今回はDynamoDB、Twitterと外部連携をしたアプリケーションをKotlessで実装してみました。
Twitter4Jのようなサードパーティライブラリなどを使うのも、Gradleに依存関係を追加するだけで通常のKotlinアプリケーションと同じような感覚で実装でき、Lambdaのアプリケーションであることをあまり意識せず作れるのがとても楽でした。
また、定期実行やIAMロールへのポリシーのアタッチなど、各種設定がKotlinのコード内で完結するのもとても便利です。
まだまだ開発途中のフレームワークですが、今後もKotlessに期待して動向を追っていきたいと思います。
【宣伝】サーバーサイドKotlinの書籍を発売しています!
私の執筆した書籍「Kotlin サーバーサイドプログラミング実践開発」が発売中です!
タイトル通り実践での開発にも持っていける内容になっているので、サーバーサイドKotlinを始めてみたい方はぜひお手にとっていただければと思います。