SEEDS Creator's Blog

Amazon QLDB 楽しいかもというお話

f:id:seeds-std:20191016131408p:plain

はじめに

みなさま、こんにちは。

WEB事業部の李です。 どうぞよろしくお願いいたします。
最近、タピオカにハマっています。
美味しいタピオカを探しております。

さて、Amazon QLDB(以降、単に「QLDB」)が東京リージョンに対応しました。
面白そうなので、ドキュメントを読みつつ、触ってみました。
本日は、QLDBの、この機能いいなと思った感想を書きたいと思います。
※機能面の詳細については、別の機会に書きたいと思います。

QLDBの紹介

まずは、QLDBってなんだ!というところですが、
ざっくり言うと、中央集権的な台帳サービスです。

例えば、データベースに履歴情報を格納して、アプリ側でそれを検索したり、
新たに登録や更新があれば、過去の改竄が無いようにデータを挿入していくといったことをしたい場合があるかと思います。

そんな時に、DB側でその保証を約束してくれるような構造になっていれば楽なのになと、毎回思ったりしますが、
QLDBは、まさにそれを実現してくれるソリューションとして登場したように感じます。

Q: Amazon Quantum Ledger Database とは何ですか?

Amazon Quantum Ledger Database (QLDB) は、台帳管理専用データベースです。
アプリケーションのデータに生じたすべての変更に関する、完全で暗号的に検証可能な履歴を提供します。

よくある質問 - Amazon QLDB | AWS

また、開発者ドキュメントのチュートリアルでは、
自動車とその所有者の所有履歴を管理する台帳を例で挙げられていました。
世の中履歴データだらけなので、自動車以外にも対象はかなり多そうです。

docs.aws.amazon.com

ユースケースとして、金融や小売での利用が紹介されてました。

・金融

銀行では多くの場合、顧客の銀行口座間のクレジットカードおよびデビットカードによる取引などの重要データの追跡に一元管理台帳的なアプリケーションが必要になります。
複雑な監査機能を持つカスタム台帳を構築する代わりに、QLDB ですべての金融トランザクションの正確かつ完全な記録を簡単に保存できます。

・小売 & サプライチェーン

小売業では、製品原産地や出荷製品の品目数、出荷先、出荷担当者など、製品サプライチェーンのあらゆる段階に関する情報にアクセスしなければならないことが多々あります。
QLDB を使用すると、製品がどの物流局面にあっても在庫および物流に関する完全な履歴を確認し追跡できます。

↑どちらも有用そうです。

特徴

さて、QLDBは下記のような、特徴があります。
・フルマネージドであること。かつ、サーバーレス(->自動スケーリング)。
・すべての変更が透過的で、イミュータブルであること。
・トランザクションログが、暗号的に検証可能であること。

aws.amazon.com

アプリ開発の時には、JAVAのドライバーが用意されていますので、下記のチュートリアルをご参考ください。

docs.aws.amazon.com

この機能いいなと思った感想

さて、色々便利そうで、素敵な機能があるのですが、
その中で、個人的にこりゃ便利だなと思った機能を二点あげたいなと思います。

改竄出来ないDBであること

新規だろうが、更新だろうが、削除だろうが、全て履歴に残してくれます。
また、その時々のメタ情報を残してくれますので、アプリ側で云々する必要が無いです。
ちなみにミスった操作も、全て保存され、かつ検証可能です。
※誤操作や誤情報を登録しないようなフローや制限などはアプリ側で考慮は必要です。それらも全て記録されますので、その辺は仕様によります。

PartiQLでクエリ可能であること

sqlチックな言語で、保存されたデータをクエリできちゃいます。
ネストされたデータをクエリできるのは、素敵だなと思います。
※2019/10/17現在、すべての PartiQL オペレーションをサポートしているわけでは無いらしいです。

おわりに

クロスリージョンレプリケーションはサポートされていないなど、
出始めのサービスなので、まだまだ成長段階かと思いますが、
ポテンシャルはめちゃくちゃ大きいような気がします。(=楽できる。 )

Kotlin/JSのAWS Lambda関数でPromiseを使うようにする

f:id:seeds-std:20191010160615p:plain
こんにちは, Web事業部の西村です
私の前回の記事 では dyanmic の利用をなくすようにしました
そして今回は, Callbackの呼び出しをやめて, JavaScriptの非同期処理( Promise )になるよう変更したいと思います
また, Promiseスタンダード なものと Coroutines のものの2種類がありますので, この記事ではその両方を紹介したいと思います

目次

過去の記事

なぜ非同期に?

非同期処理することにより複数の処理を同時に実行することができます
例えば, 複数のファイルを受け取り, S3に保存する といったことを考えてみます
非同期処理ではない場合, 1ずつしかファイルのアップロードができません
非同期処理の場合, 複数のファイルを同時にアップロードできるようになるので時間の短縮が図れます

注意事項

この記事では Promise の詳しい解説は行いません
詳しく知りたい方は下記の記事を参考にしてください
qiita.com

開発環境

この記事では下記の環境で開発を行っています

  • AdoptOpenJDK 1.8.0_222-b10
  • IntelliJ IDEA Community 2019.2.2
  • Kotlin 1.3.50

プロジェクト

前回の記事 まで作成したプロジェクトを利用します
プロジェクトの構成は下記のようになっています

KotlinLambda
├─.gradle/
├─.idea/
├─build/
├─gradle/
├─src/
│ └─main/
│   └─kotlin/
│     └─jp.co.seeds_std.lambda.kotlin/
│       └─handler.kt
├─build.gradle.kts
├─compress.zip
├─gradlew
├─gradlew.bat
└─settings.gradle.kts

また, handler.ktは下記のようになっています

package jp.co.seeds_std.lambda.kotlin

import kotlin.js.Json
import kotlin.js.Promise
import kotlin.js.json

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit) {
    val response = Response(body = "Hello from Kotlin Class Lambda!")
    callback(null, response)
}

data class Response(
    val statusCode: Int = 200,
    val body: String = "{}",
    val isBase64Encoded: Boolean = false,
    val headers: Json = json(),
    val multiValueHeaders: Json = json()
)

スタンダードなPromise

まずはkotlin-stdlibに実装されている Promise を利用してみます

関数の書き換え

handler 関数を変更します

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit) {
    val response = Response(body = "Hello from Kotlin Class Lambda!")
    callback(null, response)
}

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = Promise<Response> { executor, reject ->
    executor(Response(body = "Hello from Kotlin Async Lambda!"))
}
  • executor は処理成功時に実行する関数で, 引数はジェネリクスで指定したクラス (この記事では Response ) となります
  • reject は処理失敗時に実行する関数で, 引数は Throwable クラスのオブジェクトとなります

動作確認

関数をデプロイし, API GatewayのURLにアクセスし Hello from Kotlin Async Lambda! と表示されれば成功です

もう少し恩恵を受けてみる(スタンダード)

今度はNode.jsの setTimeout を利用して本当に非同期に処理されているかを確認したいと思います
その前に, Kotlin/JSにはNode.jsの setTimeout は定義されていないので追加します
今回追加する場所はコードの最下部に追加します

handler.kt

external fun setTimeout(callback: dynamic, delay: Int, vararg args: Any?): Int
external fun clearTimeout(timeout: Int)
  • external : 外部の関数/変数を利用するために記述します。トランスパイルした場合にそのまま残るようになります
  • callback の部分が dynamic となっていますが, 関数の引数が固定値ではないためやむを得ず dynamic としています

続いて handler 関数を変更します

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = Promise<Response> { executor, reject ->
    val results = (1..10).map {
        Promise<Int> { childExecutor, _ ->
            setTimeout({childExecutor(it)}, 1000)
        }
    }
    Promise.all(results.toTypedArray()).then {
        executor(Response(body = "Result: ${it.sum()} - Standard"))
    }
}

このコードでは 1秒待ってから数字を返す というコードを10回繰り返すものとなっています

動作確認

関数をデプロイし, API GatewayのURLにアクセスし約1秒後に Result: 55 - Standard と表示されれば成功です
※初回の実行では2-3秒ほどかかる場合もあります

通常の処理であれば 1秒待って数字を返す を10回行えば10秒かかるはずですが, 非同期に実行されているため約1秒で完了します

CoroutinesのPromise

標準実装の Promise では見にくく感じませんでしたか?(私は少なからず見にくいなと感じました)
非同期処理を書くのであれば, Node.jsみたいに async で書きたいところです
そこで使うのが kotlinx.coroutines です

依存関係を追加する

kotlinx.coroutines は外部ライブラリなので build.gradle.kts に依存関係を追加する必要があります

build.gradle.kts

implementation(kotlin("stdlib-js"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.2") // 追加

コードを書き換える

まずはただの文字列を返すように handelr 関数を変更します

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = GlobalScope.promise {
    return@promise Response(body = "Hello from Kotlin Coroutines Lambda!")
}

先ほどのコードと比較するとかなり単調なものになりました
※エラーを返す際は throw NullPointerException() のように throw するだけとなります

動作確認

関数をデプロイし, API GatewayのURLにアクセスし Hello from Kotlin Coroutines Lambda! と表示されれば成功です

もう少し恩恵を受けてみる(Coroutines)

先ほどと同じ関数を実装してみます

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json) = GlobalScope.promise {
    val tasks = (1..10).map {
        async {
            delay(1000)
            it
        }
    }
    val results = tasks.awaitAll()
    return@promise Response(body = "Result: ${results.sum()} - Coroutines")
}

先ほどとは異なり, CoroutineScope 内で実行できるため, async 関数を利用できます

動作確認

関数をデプロイし, API GatewayのURLにアクセスし約1秒後に Result: 55 - Coroutines と表示されれば成功です
※初回の実行では2-3秒ほどかかる場合もあります

最後に

いかがでしたでしょうか
非同期処理が行えるとよりできることの幅が増えるかと思います
また、Coroutinesの Promise と標準実装の Promise は互換性があるため外部ライブラリが非同期処理を行う場合にも使え,
よりKotlinらしい書き方ができるようになっていくと思います
次回は kotlinx.serialization を用いてJsonのレスポンスを行う記事を書きたいと思います
ここまで読んでいただきありございました

Kotlin/JSのAWS Lambda関数でJsonを返してみる

開発者やデザイナーは要注意?!シーズ社員達がMac OSをCatalinaへアップデートした体験談。

f:id:seeds-std:20191011110224p:plain

はじめまして!WEB事業部の小川です。

新しいMac OS(Catalina)が登場し、シーズの新しい物好きな社員達(自分を含む)はアップデートしてみました。

更新した感想等を書いていこうと思いますので、アップデートを検討されている方の参考になればと思います。

とりあえず Sidecarが便利

iPadユーザーには朗報であったSidecarが便利で、今まで有料のアプリやハードを使わないと出来なかったiPadのサブディスプレイ化が手軽に出来るのはいいですね!

f:id:seeds-std:20191011110625j:plain
シーズの社員も早速使いこなしておりました
※トレーダーではありません

やっぱりあった Catalinaの問題点とは?

特に開発者に問題がありそうだったのが、Catalina + Google Chromeで発生しているであろう問題点を中心に書いていきたいと思います。

1. input type="number" のstepが実際のstepの2倍になる問題

stepの指定はしていませんが、Catalina + Google Chromeの方は増やしていくと2ずつ増えていきます。問題ない場合は1ずつ増えていきます。

※Chrome Canaryでは修正されて?発生しない様子。
Chromeの対応待ちですかね。

2. ヒラギノ角ゴPro/ProNが無くなった?

Catalina + Google Chromeではヒラギノ角ゴPro/ProN(Sans Serif)が明朝体で表示されてしまう問題が発生しています。
Apple公開しているmacOS 10.15 Catalinaの組み込みフォントリストには存在しているので「ヒラギノ角ゴPro/ProNが無くなった」と言うことでは無さそうです。
デザイナーやフロントエンドエンジニア の方は要注意ですね。

f:id:seeds-std:20191011112730p:plainf:id:seeds-std:20191011112737p:plain
左がChrome 右がSafari

これもChromeの対応待ちですかね。。

3.Gitが使えなくなった?問題

Catalinaにして、エディターを開いてみると。。

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

こんなエラーが出ていました。 エラー文言でググればすぐ解決法はありました。
こちらのコマンドで対応出来ました。

xcode-select --install

4. zipファイルが解凍出来なくなった問題

f:id:seeds-std:20191011115340p:plain

zipファイルが解凍出来ない問題があるようです。 解凍出来ないパターンとして最初に考えられたのは
・日本語ファイルがダメ?
・暗号化されているとダメ?
・日本語のファイル名が含まれてても文字コードがUTF-8ならOK?
 と言うことが考えられました。

圧縮時の文字コードはUTF-8にしている場合が多いと思いますが、Windowsを想定している場合はSJIS-WINに変更している可能性もありますね。

回避策として

「ファイル名をアルファベットにしておく」が1番安全

と言うことになりそうです。

最後に

セキュリティの面やSidecarが便利すぎる点では更新した方がいいのでしょうが、新しいが故の問題点もあるので、注意は必要ですね。

この手のアップデートは時間がかかります。

かったりーな・・・

最後まで読んでいただきありがとうございました。 小川

AWSソリューションアーキテクトプロフェッショナル(SAP-C01) に合格しました

f:id:seeds-std:20191010165532p:plain

クラウド事業部の原口です。

AWSソリューションアーキテクトプロフェッショナル試験を受けてきて一度不合格となった末に合格しました。
合格点は750/1000点と結構お高め…
難しかったです。 勉強を初めてから1ヶ月半ほどかかりました。

一度不合格となってますし偉そうな事は言えないのですが、こんな勉強をしました、という事を書いてみたいと思います。

試験の概要

AWS 認定ソリューションアーキテクト – プロフェッショナル

AWS プラットフォームでの分散アプリケーションおよびシステムを設計するための
高度な技術的スキルと専門知識を認定します。
AWS でのクラウドアーキテクチャの設計およびデプロイに関する 2 年以上の実務経験を持つ方が対象です。

この試験で評価する能力は次のとおりです。
• AWS における、動的なスケーラビリティ、高可用性、フォールトトレランス、
信頼性を備えたアプリケーションの設計およびデプロイ
• 与えられた要件に基づいてアプリケーションを設計およびデプロイするための、適切な AWSサービスの選択
• AWS における複雑な多層アプリケーションの移行
• AWS におけるエンタープライズ全体のスケーラブルな運用の設計およびデプロイ
• コストコントロール戦略の導入

かなりのボリュームの試験です。
AWS 認定ソリューションアーキテクト – アソシエイト の上位版に位置する問題となります。

試験結果は 100~1000 点の範囲のスコアでレポートされます。最低合格スコアは 750 点です。スコ
アによって、試験での全体的な成績と合否がわかります。スケールドスコアモデルは、難易度にわず
かな違いのある複数の試験形式のスコアを平均化するために使用されます。

100点から?という単純な疑問とスケールドスコアモデルというものがわからなかったので調べてみましたが、
あまりよくわからなかったのですが「難易度が異なる試験問題をいい感じに調整」するモデルと解釈しました。
つまり問題によって配点が違ってくる、という事ですかね?

試験時間は170分。問題数は75問ですので1問あたり2~3分程度で解く必要があります。
試験合格の有効期限は3年みたいです。

その他詳細な試験ガイドは以下となります(公式) 試験ガイド

試験勉強

私はAWSを初めて触ってからもう5年くらいは経っていると思うのですが、
業務で触れるサービスが中心となってしまい、どうしてもよくわからないサービスというのは存在しました。
そのため、いったい自分は何が苦手なのか、を把握する事からはじめました

模擬試験

模擬試験を受けて再学習が必要な領域を特定と試験の感じの把握からはじめました
サンプル問題でもわかりますが、言い回しが独特でとにかく文章が長い、という印象です

公式ドキュメントの「よくある質問」

よくある質問を読み込むのを中心に行いました。
サービスの制限など実運用を検討する方へのドキュメントである為、
サービスのネックや落とし穴となりやす部分が集約されているものと感じました。

ホワイトペーパー

DR関連のものとストレージサービス関連のものはよく読みました。 AWS Well-Architectedなどはななめ読みしたくらいです。

blackbelt

よく知らないサービスはblackbeltを見ました。
スライドをぽちぽちーとしてたんですが、ちゃんと動画で見ると理解度が段違いだったのでできれば動画がいいと思います

AWS クラウドサービス活用資料集 | サービス別資料

心構え

膨大な文章量と170分

とにかく文章が長いので単純に疲弊してきます。
集中力を長持ちさせる自分なりの方法を見つける必要がありました
疲れてくると読んでるだけで頭に入ってない事が多くてそういう時に集中しなおせる方法を持ってるといいです
秘訣について「170分くじけない心が必要」と聞いて笑ってたんですが
本当に重要ではないかと思いました

正常性バイアスとの戦い

疲労してくると「正常正バイアス」がかかりやすくなる気がしました。
問題の選択肢はすべてその問題を解決できるものであったり、もっともらしい書き方をされていて
知識が不足していると簡単に迷います。
その状態で出てくるのが以下のような考え。

・実務ではこんなやり方はしないだろう
・こういう時は枯れた技術で望むべき
・問題文に定義されてないからこういう意味に違いない

これらが出てきた時は大体知識不足が原因で明確な説明ができない以上は知識不足であると自分に言い聞かせました。
わりとここで問題の深読みまでしだすと精神的にもハマるし時間も浪費します。

サービスや機能の特性を単語で関連付ける

ストリーミングデータの処理といえばKinesis。 といった形で、仮想的だけど実務的なRFPからAWSにおける提案例を連想できるようにしました。
実際実務として提案を心がけている事もありこの方法は自分に合った勉強法でした。

短期集中で行う

長いスパンで考えるよりも2週間くらいでがーーっとやった方がよいです

その他

試験にはPSIとピアソンVUEがありますが、可能な限りピアソンVUEでの受験をおすすめします!
PSIでは監督員と遠隔でチャットをしながら、カメラで常時監視されながらの試験となり、
「キーボードから手を離してはいけない」などのプレッシャーが半端なかったです笑

ピアソンVUEでも監督員はもちろん居ますが、口元に手をあてたり、頭を掻いたりしても問題はないので
この点は心理的負担がだいぶ減ります!

感想

正直合格の文字を見た時は喜びよりほっとしました。2,3回確認しました。(笑) 社内での人権ができてよかったです。

自信もつき、試験を通じた勉強を行った事で、より深く!AWSの理解が深まったという実感があります。
これからはプロフェッショナル原口としてよりお客様にピッタリのクラウド最適化した提案ができると思います!
お困りの事がありましたら、是非ご相談下さい!!

DevOpsとかの他の試験も受けていくぞー!って思ってたのですが
本当に疲れたので、とりあえずはアイスボーンをやります。
社内でもどんどんAWS認定の取得者を増やしたいですね。
これから試験を受ける方は頑張って下さい!

宣伝枠

AWSの無料相談会というものを行っています! www.seeds-std.co.jp

弊社ではAWSが大好きなエンジニアを募集しています!じょいなす! recruit.seeds-std.co.jp

Kotlin/JSのAWS Lambda関数を便利にしてみる

f:id:seeds-std:20191010160615p:plain

こんにちは, Web事業部の西村です
私の前回の記事 ではKotlin/JSを用いてLambda関数を書いてみました
しかし, dynamic を利用していたため使いにくい部分もあったと思います
今回はその点を改良しより使いやすくなるよう変更したいと思います

目次

過去の記事

開発環境

この記事では下記の環境で開発を行っています

  • AdoptOpenJDK 1.8.0_222-b10
  • IntelliJ IDEA Community 2019.2.2
  • Kotlin 1.3.50

プロジェクト

前回の記事 で作成したプロジェクトを利用します
プロジェクトの構成は下記のようになっています

KotlinLambda
├─.gradle/
├─.idea/
├─build/
├─gradle/
├─src/
│ └─main/
│   └─kotlin/
│     └─jp.co.seeds_std.lambda.kotlin/
│       └─handler.kt
├─build.gradle.kts
├─compress.zip
├─gradlew
├─gradlew.bat
└─settings.gradle.kts

Jsonを使って便利にしてみる

dynamicをなくす

Kotlin/JSには dynamic に似た Json があります
dynamic はJavaScriptのデータであったり, オブジェクトを格納することができますが, Json はKey-Value形式でのみ格納できるものとなっています
handler関数の引数, eventcontext はどちらもJavaScriptのJsonオブジェクトですのでKotlinの Json に変換できます
また, Callbackの第二引数もJsonオブジェクトを設定するためこちらも変更できます

handler.kt

fun handler(event: dynamic, context: dynamic, callback: (Error?, dynamic) -> Unit)

↑ が ↓ に変更できます

fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit)

このJsonオブジェクトへのアクセスはMapのように利用してアクセスすることができます

event["body"] // こちらか
event.get("body") // こちらでデータの取得ができます

Callbackに渡すのJsonを関数で作れるようにする

API Gatewayでは下記のJsonを処理するようになっています *1

{
    "isBase64Encoded": true|false,
    "statusCode": httpStatusCode,
    "headers": { "headerName": "headerValue", ... },
    "multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
    "body": "..."
}

こちらをもとに引数が5つの関数を作成したいと思います
記述する場所はhandler関数の下になります

handler.kt

fun responseJson(statusCode: Int = 200, body: String = "{}", isBase64Encoded: Boolean = false, headers: Json = json(), multiValueHeaders: Json = json()) = json(
    "statusCode" to statusCode,
    "body" to body,
    "isBase64Encoded" to isBase64Encoded,
    "headers" to headers,
    "multiValueHeaders" to multiValueHeaders
)
  • json() Jsonを作成する関数です/引数は vararg Pair<String, Any?> となっており json() と書くと空のJsonを作ることができます

Callbackに渡すよう変更する

現在handler関数は下記のようになっていると思います

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
    val response: dynamic = object {}
    response["statusCode"] = 200
    response.body = "Hello from Kotlin Lambda!"
    callback(null, response)
}

この関数を先ほど作成した関数を用いた形に変更します

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
    val response = responseJson(body = "Hello from Kotlin Json Lambda!")
    callback(null, response)
}

関数をデプロイする

前回と同様の手順を用いて関数をデプロイします
画面右側の Gradle タブを開き, kotlin_lambda -> Tasks -> other の順に開き, その中の compress をダブルクリックして実行します
ビルドに成功するとプロジェクトのディレクトリに compress.zip というファイルが更新されます
この生成されたファイルをコンソールからアップロードし, 保存します

動作確認

あらかじめ作成しておいたAPI GatewayのURLにアクセスし Hello from Kotlin Json Lambda! と表示されれば成功です

レスポンスをクラスに変更する

レスポンスはクラスを用いても影響が少ないためクラスに置き換えます
リクエストにクラスを利用しない理由については蛇足をご覧ください

まずは responseJson関数 を変更します

handler.kt

fun responseJson(statusCode: Int = 200, body: String = "{}", isBase64Encoded: Boolean = false, headers: Json = json(), multiValueHeaders: Json = json()) = json(
    "statusCode" to statusCode,
    "body" to body,
    "isBase64Encoded" to isBase64Encoded,
    "headers" to headers,
    "multiValueHeaders" to multiValueHeaders
)

data class Response(
    val statusCode: Int = 200,
    val body: String = "{}",
    val isBase64Encoded: Boolean = false,
    val headers: Json = json(),
    val multiValueHeaders: Json = json()
)

続いて, Callbackの引数を変更します

fun handler(event: dynamic, context: dynamic, callback: (Error?, Json?) -> Unit)

fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit)

最後にレスポンスを変更します

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
    val response = responseJson(body = "Hello from Kotlin Json Lambda!")
    callback(null, response)
}

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit) {
    val response = Response(body = "Hello from Kotlin Class Lambda!")
    callback(null, response)
}

この変更ができたらデプロイし動作を確認します
Hello from Kotlin Class Lambda! と表示されれば成功です

最後に

いかがでしたでしょうか Kotlinを用いていると dynamic では少し取り扱いにくく感じてしまいます
しかし Json にしてしまうことで,Mapのように利用でき, より使いやすいのではないかと思います
次回は非同期処理にする記事を書きたいと思います
ここまで読んでいただきありございました

Kotlin/JSのAWS Lambda関数でPromiseを使うようにする


蛇足

リクエストのほうもクラス使ったらだめなの?

注意事項はありますが、クラスを利用しても問題はないです

handler.kt

package jp.co.seeds_std.lambda.kotlin

import kotlin.js.Json
import kotlin.js.json

@JsExport
@JsName("handler")
fun handler(event: Event, context: Json, callback: (Error?, Response?) -> Unit) {
    println("body: ${event.body} isBase64Encoded: ${event.isBase64Encoded}")
    // event.copy() // Error!
    val response = Response(body = "Hello from Kotlin Json Lambda!")
    callback(null, response)
}

data class Event(val body: String?, val isBase64Encoded: Boolean)
data class Response(
    val statusCode: Int = 200,
    val body: String = "{}",
    val isBase64Encoded: Boolean = false,
    val headers: Json = json(),
    val multiValueHeaders: Json = json()
)

この時ログに bodyisBase64Encoded の出力が行われます
その後 copy() を実行しようとした場合, 変数 eventEventクラスではなく Json となっていますので, 関数が見つからず実行時にエラーが出てしまいます
また, 注意点として private な変数はトランスパイルした際に変数名が変更されてしまうためうまく利用できません
そのため, リクエストの eventcontext はJsonで, レスポンスはKotlinのクラスを利用していくことがいいのかなと私は思います
蛇足も読んでいただきありございました

Bitbucket PipelinesでDocker Composeプロジェクトをインテグレーションテストする

はじめまして。小國です。

今回は、Docker Compose で構築されたプロジェクトを、Bitbucket Pipelines を使ってインテグレーションテストする方法をご紹介したいと思います。

はじめに

シーズのプロジェクトの開発環境・手法は、主に以下のような構成となっています。

  • VCS に Git を使い、Bitbucket にホスティングしている
  • 各プロジェクトは Docker Compose を使い、ローカル環境を構築し、開発を行っている
  • PHPUnit などを使って、テストケースを書いている

Bitbucket Pipelines で Docker Compose を使うには

Bitbucket には Pipelines という CI/CD サービスがあり、Docker を使ったテストが行なえます。

ですが、Pipelines で Docker Compose を使うためには、自身で Docker Compose バイナリを作成しなければなりません。

参考: https://ja.confluence.atlassian.com/bitbucket/run-docker-commands-in-bitbucket-pipelines-879254331.html

今回は、Docker Compose バイナリの作成・公開し、サンプルプロジェクトを使って、Pipelines で PHPUnit のテストを実行するまでを行います。

やってみましょう

主な流れは以下のようになります。

  1. Docker Compose バイナリの作成と、作成したバイナリを Docker Hub に公開
  2. サンプルプロジェクトの作成
  3. bitbucket-pipelines.yml の作成
  4. Bitbucket で Pipelines の有効化

Docker Compose バイナリの作成と、作成したバイナリを Docker Hub に公開

なお、https://hub.docker.com/r/seedsstd/seeds_bitbucket_pipelines にほぼ同様のイメージを公開していますので、こちらを使用する方は、このステップは不要です。

  • Docker Compose バイナリの作成
$ mkdir seeds_bitbucket_pipelines && cd seeds_bitbucket_pipelines
$ cat <<EOF > Dockerfile
FROM docker:stable

# Add python pip and bash
RUN apk add --no-cache py-pip
RUN apk add --no-cache python-dev libffi-dev openssl-dev gcc libc-dev make
RUN apk add --no-cache bash

# Install docker-compose via pip
RUN pip install --no-cache-dir docker-compose
EOF
  • 作成したバイナリを Docker Hub に公開
docker build -t <YOUR_ACCOUNT>/seeds_bitbucket_pipelines:stable .
docker push <YOUR_ACCOUNT>/seeds_bitbucket_pipelines:stable

<YOUR_ACCOUNT> には、自身の Docker Hub アカウント、またはオーガニゼーションを指定してください。

docker build -t seedsstd/seeds_bitbucket_pipelines:stable .
docker push seedsstd/seeds_bitbucket_pipelines:stable

サンプルプロジェクトの作成

サンプルプロジェクトを作成ます。ファイル構成、内容は以下のとおりです。

$ tree
.
├── composer.json
├── composer.lock
├── composer.phar
├── docker-compose.yml
├── php-apache
│   └── Dockerfile
└── tests
    └── SampleTest.php

2 directories, 6 files
  • composer.json
{
    "name": "seeds-std/blog_bitbucket_pipelines",
    "authors": [
        {
            "name": "SEEDS Co.,Ltd",
            "email": "info@seeds-std.co.jp"
        }
    ],
    "require": {},
    "require-dev": {
        "phpunit/phpunit": "^8.3"
    }
}
  • docker-compose.yml
version: "3"

services:
  web:
    build: php-apache # php7.2-apache に git が入っていなかったため作成
    volumes:
      - ./:/var/www/html
  • php-apache/Dockerfile
FROM php:7.2-apache

RUN apt-get update -y && apt-get install -y git
  • tests/SampleTest.php
<?php

class SampleTest extends \PHPUnit\Framework\TestCase
{
    /**
     * @return void
     */
    public function testTrueIsTrue()
    {
        $this->assertTrue(true);
    }
}

ここでのポイントは docker-compose run --rm web bash -c "php composer.phar install && vendor/bin/phpunit tests/SampleTest.php" というような形で、ホスト側から PHPUnit のテストが実行できることです。

ためしに、実行してテストが通ることを確認しましょう。

$ docker-compose run web /bin/bash -c "php composer.phar install && vendor/bin/phpunit tests/SampleTest.php"
Do not run Composer as root/super user! See https://getcomposer.org/root for details
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Nothing to install or update
Generating autoload files
PHPUnit 8.3.5 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 545 ms, Memory: 4.00 MB

OK (1 test, 1 assertion)

良さそうですね。

bitbucket-pipelines.yml の作成

以下のような bitbucket-pipelines.yml を作成し、リポジトリの直下に含めます。

内容は、上記で作成した Docker Compose バイナリを使い、そのホストからコンテナを起動しテストする内容になっています。

image: seedsstd/seeds_bitbucket_pipelines:stable

options:
  docker: true

default_script: &default_script
  - docker-compose run --rm web /bin/bash -c "php composer.phar install && vendor/bin/phpunit tests/SampleTest.php"
  - docker-compose down

pipelines:
  default:
    - step:
        script: *default_script

Pipelines の有効化

最後に、Bitbucket の画面から Pipelines を有効化します。

f:id:seeds-std:20190927100459p:plain

Pipelines を有効化すると、以下のように bitbucket-pipelines.yml で指定したテストが実行されます。

f:id:seeds-std:20190927100819p:plain

テストが全て正常に終わり、以下のようになればおkです。

f:id:seeds-std:20190927101038p:plain

最後に

Bitbucket Pipelines で Docker Compose を使用したプロジェクトのテストを実行することができました。

LaravelのPivotのincrementingがfalseになっていた件

f:id:seeds-std:20191011113251p:plain

みなさま、初めまして。

WEB事業部の李です。 どうぞよろしくお願いいたします。
最近、カップヌードルの味噌味にハマっています。
昼は基本おむすびです。

www.nissin.com

本題

さて、本日は、 Laravelを5.6から5.8にアップグレードした際に、 Pivotクラスのidをデフォルトでは取得できなかった話を書きたいと思います。

結論

先に結論ですが、LaravelのPivotのincrementingが5.8からdefaultでfalseに設定されていたためでした。
trueにオーバーライドすると取れました。
オーバーライドもどうだろうというのはありますが。。

経緯など詳細

Laravelのバージョンが5.6の時に、中間テーブルを複数またぐような構造のデータを作成する必要がありました。
実装としては、pivotクラスのormを利用して登録後のidを取得し、さらに中間テーブルを作成するというプログラムが既に作られていました。

モデル->Hoge(※中間テーブル)->Hoge2(※中間テーブル)->….

上の例で、Hoge2に、Hogeのidを登録するという流れですが、
5.8へアップグレードするとhogeのレコードを作成後idが取れなくなっていました。。。
※Hogeのidは、auto_incrementで定義されています。

$hoge = Hoge::create($data); // $hoge->id 取れない。。

$hoge = new Hoge;
・
・
・
$hoge->save();  // $hoge->id 取れない。。

まじかよ。。と、もう一度アップグレードガイドを見たりしたのですが、
特に記述はなく、あの手この手と試してみたりしてたのですが、やはり取れず。。
まさかのlastInsertIdメソッド記述しないといけないのかと若干気持ちが沈んでいた頃、
あるエンジニアの方から、もしかしてこれじゃないですか?と指摘を受け、

venderの方のpivotクラスをのぞいてみると、

public $incrementing = false;

の記述がありました。 そこで、Modelクラスのsaveメソッドを少し追いかけてみると、ありました。

performInsertメソッド!これやん!!と。。

この中で、下記のような記述がありました。

//・・・省略

// If the model has an incrementing key, we can use the "insertGetId" method on
// the query builder, which will give us back the final inserted ID for this
// table from the database. Not all tables have to be incrementing though.
$attributes = $this->getAttributes();

if ($this->getIncrementing()) {
    $this->insertAndSetId($query, $attributes);
}

// If the table isn't incrementing we'll simply insert these attributes as they
// are. These attribute arrays must contain an "id" column previously placed
// there by the developer as the manually determined key for these models.
else {
    if (empty($attributes)) {
        return true;
    }

    $query->insert($attributes);
}



//・・・省略

“which will give us back the final inserted ID”<-まさにですね。

getIncrementingメソッドは下記のような記述でした。

public function getIncrementing()
{
    return $this->incrementing;
}

念の為、Laravelframeworkリポジトリで、バージョン5.6のpivotクラスを見てみると、
incrementingプロパティの記述がありません。
PivotクラスはModelクラスを継承しており、Modelクラスのプロパティには、$incrementing=trueとあったので、
バージョン5.6では、idを取得出来ていたのですね。

github.com

ちなみに、create()メソッドは、Illuminate\Database\Eloquent\Builderクラスのcreateメソッドで、結局saveを使っているみたいです。

public function create(array $attributes = [])
{
    return tap($this->newModelInstance($attributes), function ($instance) {
        $instance->save();
    });
}

というわけで、問題のHogeクラスに、

public $incrementing = true;

と、オーバーライドするとidが取れるようになりました。

frameworkでの詳しい経緯は下記のPRご参考ください。

github.com

以上です。
すでに6.xがLTSとなってますが、Pivotクラスのincrementingプロパティのデフォルトはfalseになっていますので、
この記事が皆様のご参考になればと思います。