生涯未熟

プログラミングをちょこちょこと。

チャレンジングなことをしてみたらエッチなクソアプリが誕生した

どうも、この記事は クソアプリアドベントカレンダー の10日目のようです。

さて、今年ひり出したクソアプリは

エッチなやつです

f:id:syossan:20181210005011p:plain

エッチなリンク

業務で触った技術がなんか活かせないかな〜?と考えていたら、何故かエロクソアプリが誕生していました。不思議ですね。

どんなクソなの?

で、どんなアプリなのかというと「サクッと条件に合うAV女優探してサクッとxvideosかDMMに飛べるやつ」です。便利ですね。

これ以上、特に解説することがなくて困ってしまいましたので、技術的な解説に入りましょう。

技術の話

今回は、フロントをElmで、バックをGo + Lambda + S3 Selectで構築しました。

f:id:syossan:20181210011649p:plain

フロントの話

Elmを採用した理由としては、最近Elmを布教する教祖の人に影響を受けてTODOアプリ以外のもの作ってみてーなーということからやってみました。
実際ガッツリやってみると、コアなところはミニマルで言語仕様をサクッと把握出来るので学習コストがかなり低い言語だなーと改めて感じました。

ただ、パッケージによっては結構扱い方がピーキーなものしかなかったりして、まだそこらへんのエコシステム周りが潤ってない感はありました。
ハマりどころはあるものの良い言語です。皆さんもやりましょう。

あと、CSSフレームワークはBulmaを使いました。名前からして今回のアプリにぴったりですね。

S3のWeb Hostingを使ってるので、デプロイも npm run build して生成されたdist fileを放り込むだけで完了。平易。

さて、今回フロント側ではスケベデータ取得用Lambdaに問い合わせた結果を単純に表示しているだけの簡単な作りになっています。
あまり語ることが無いのでバックエンドの話に移りましょう。

フロントのソースコードはこちら

github.com

 バックエンドの話

バックエンドは手に馴染んだGoを使い、Lambdaに乗せてS3 Selectを用いてCSVデータを取得してくるように作りました。
S3 Selectが前から気になってて、やっとこさまともに使ってみたのですがドキュメントに書いてないハマりどころがあり、めちゃくちゃハマってしまいました・・・

その辺の詳しい話はGoアドベントカレンダーの記事で書こうかなと思うのですが、概要だけ書くと

  • メッセージタイプがRecordsの場合、65000バイトのchunk dataで送信される
  • CSV列の途中に欠損データがある場合、欠損データ行までしか比較演算子の対象とならない

という感じで、特に2つ目は1日溶かすレベルで悩みました・・・

そんなS3 Selectですが、1600件ほどのデータから簡単なクエリで引っ張ってくるだけなら300msくらいで返してくれるので、簡単なデータを扱うならこれでもいいのかなーと思える感じでした。

また、コンソールからSQLも叩けるのでデバッグとかに重宝しました。

S3バケット内のCSVにS3 Selectというタブがあるので

f:id:syossan:20181210015244p:plain

そこからSQL実行できます。

f:id:syossan:20181210015333p:plain

バックエンドのソースコードはこちら

github.com

まとめ

作った期間は2日くらいですが、すごく久々にクソをひり出せて満足感が高かったです。
用意したデータ自体の不備が結構あったので、全部で約1600件のデータしか返せないのが残念ですが、暇を見つけて充足させていきたいと思います。

みんな、エロはいいぞ。

現場からは以上です。

GoLandにCassandra対応が来たぞ!!

掲題の通り、GoLand 2018.3でCassandraのDataSourceが使えるようになりましたぞ!

www.jetbrains.com

今までCassandraのGUIツールといえば、DataStax DevCenterくらいしか無かったのですが、これで新しい選択肢が増えましたね。

qiita.com

試しに使ってみたのですが、他のDataSourceと同じように特に違和感なく使うことができます。いやー、嬉しい。

f:id:syossan:20181123183159p:plain

あと、DataGripはEAPになっている2018.3対応でCassandra Supportが来るみたいですね。
Release Noteにも書いてあるように「Thanks to our wonderful colleagues on the DataGrip team」だ!ありがとう!

guregu/nullでぬるぬるする

Goでサーバーサイドの開発を行っているとMySQLなどのRDBに接続して、取得した値をJSONで返す機会は多いはず。
そして大概ハマるのが「NULLを許容してJSONで返すのはどうやればいいんだ・・・」というところでしょう。

今回はそんな話をします。

ノーガード戦法

まずは特に何もしなかったパターンを見てみましょう。

テストデータは以下のようなものを使います。

+-------+------+
| name  | age  |
+-------+------+
| tarou |   12 |
| NULL  | NULL |
|       |    0 |
+-------+------+

適当な値が入ったレコードと、NULLが入ったレコードと、空文字・0が入ったレコードになります。
これを以下のようなコードで使ってみましょう。

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

type User struct {
    Name string
    Age int
}

func main() {
    db, err := sql.Open("mysql", "root:@/test")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT * FROM user")
    if err != nil {
        return
    }


    for rows.Next() {
        var user User
        rows.Scan(&user.Name, &user.Age)
        JSON, err := json.Marshal(user)
        if err != nil {
            continue
        }
        fmt.Println(string(JSON))
    }
}

これを動かしてみると・・・

{"Name":"tarou","Age":12}
{"Name":"","Age":0}
{"Name":"","Age":0}

おっ、NULLが空文字と0になりましたねー。
このように特に何もしなかった場合は型に応じた初期値になるんですよね。

王道パターン

さて、ここで「ちゃんとNULLで出力出来ないと困るよ!」って場合には以下のようにUser構造体の型を変えることで判別できます。

package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "github.com/k0kubun/pp"
)

type User struct {
    Name sql.NullString
    Age sql.NullInt64
}

func main() {
    db, err := sql.Open("mysql", "root:@/test")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT * FROM user")
    if err != nil {
        return
    }

    for rows.Next() {
        var user User
        rows.Scan(&user.Name, &user.Age)
        pp.Println(user)
    }
}

これを実行すると

{"Name":{"String":"tarou","Valid":true},"Age":{"Int64":12,"Valid":true}}
{"Name":{"String":"","Valid":false},"Age":{"Int64":0,"Valid":false}}
{"Name":{"String":"","Valid":true},"Age":{"Int64":0,"Valid":true}}

のようになります。
Valid というのがNULLかどうかのbool値を持っている構造体が含まれているのが分かりますね。

このままでは使い物にならないので、良い感じにNULLを返すために新たに sql.NULLXXX を含んだ構造体を作成して、ゴニョゴニョやるパターンが多く見受けられます。

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

type (
    User struct {
        Name NullString `json:"name"`
        Age NullInt64 `json:"age"`
    }

    NullString struct {
        sql.NullString
    }
    NullInt64 struct {
        sql.NullInt64
    }
)

func (ns NullString) MarshalJSON() ([]byte, error) {
    if !ns.Valid {
        return []byte("null"), nil
    }
    return json.Marshal(ns.String)
}

func (ni NullInt64) MarshalJSON() ([]byte, error) {
    if !ni.Valid {
        return []byte("null"), nil
    }
    return json.Marshal(ni.Int64)
}

func main() {
    db, err := sql.Open("mysql", "root:@/test")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT * FROM user")
    if err != nil {
        return
    }

    for rows.Next() {
        var user User
        rows.Scan(&user.Name, &user.Age)
        JSON, err := json.Marshal(user)
        if err != nil {
            continue
        }
        fmt.Println(string(JSON))
    }
}

MarshalJSON() ([]byte, error) を持つ構造体を作成することで、 Marshaler Interfaceを満たし json.Marshal 実行時に定義した MarshalJSON を実行してくれます。

これを実行した結果は・・・

{"name":"tarou","age":12}
{"name":null,"age":null}
{"name":"","age":0}

ちゃんとnullが許容されてますね!
さて、これで当初の目的は達成できたわけですが、いちいち sql.NULLXXX を含んだ構造体を作成して MarshalJSON を定義するのはヒジョーに面倒です。

そこで、 guregu/null の出番です。

guregu/null

使い方は非常に簡単。
NULL値が入った時に null.XXX 型にはnullを、 zero.XXX 型には型の初期値に変換されます。

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "gopkg.in/guregu/null.v3"
    "gopkg.in/guregu/null.v3/zero"
)

type (
    User struct {
        Name null.String `json:"name"`
        Age null.Int `json:"age"`
    }
)

func main() {
    db, err := sql.Open("mysql", "root:@/guregu")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT * FROM user")
    if err != nil {
        return
    }

    for rows.Next() {
        var user User
        rows.Scan(&user.Name, &user.Age)
        JSON, err := json.Marshal(user)
        if err != nil {
            continue
        }
        fmt.Println(string(JSON))
    }
}

こうすることで

{"name":"tarou","age":12}
{"name":null,"age":null}
{"name":"","age":0}

ちゃんと null.XXX はnullに、 zero.XXX は型の初期値になってますね。

単純にnullにしたい場合は便利なライブラリですね。
ただ、UnmarshalやMarshal時に特殊な処理をしたい場合は sql.NULLXXX を使った方法を使うのがベターでしょう。

S3にファイルがアップロードされた時にCloudFrontのキャッシュを削除してみる

掲題の通りのことが社で必要になったのでGoでやってみました。

やりたいこと

S3に置いた静的HTMLをCloudFrontを通してアクセスする、という形で配信しているシステムがあり、運用する中で静的HTMLを置き換えた時にすぐに切り替わるようにしたいということで、CloudFrontのキャッシュを都度削除する必要がありました。
ただし、毎日切り替わる代物なのでその都度手動でCloudFrontの特定Bucketに対してInvalideするのは面倒だなーと思ったので、それならファイルを置いた時に自動的にInvalidateが実行されるようにしたいというのが目的です。

どうやるか

さて、やり方ですが今回はLambdaを使って実現しました。

Lambdaのイベント発火条件として「S3のObjectCreatedByPut」を使います。
これでS3の指定したBucketにObjectが生成・更新された際にLambdaの関数が動いてくれます。

そして、以下のようなLambda関数を作成します。

簡単に説明すると、発火したイベント詳細を引数として受け、そこからバケット名を取得してCloudFrontのDistoributionのドメイン名と合致するものを探します。

見つかれば、そのDistributionに向かって CreateInvalidation 関数を使ってinvalidationを実行するだけです。非常に簡単ですね。

こんなにも簡単に作成出来てしまうのだからLambdaの力は凄いですね。
贅沢を言うとGoの設計テンプレートが全く無いので、AWSの中の人には頑張って追加して欲しいところ・・・🙏

Elmハンズオンに行ってきた

最近ある方がElmを猛プッシュしているので、Elmハンズオンに行ってきました。

elm-jp.connpass.com

f:id:syossan:20180923143335j:plain

togetter.com

Elm is 何?

Elmはウェブブラウザベースのグラフィカルユーザインタフェースを宣言的に作成するためのドメイン固有プログラミング言語である。Elmは純粋関数型言語であり、ユーザビリティ・パフォーマンス・堅牢性を重視して開発されている。静的かつ強力な型検査によって「事実上一切の実行時例外が起こらない」ことを売りにしている。 - wikipediaより引用

ってなわけで、WebアプリケーションにおけるUIを担う純粋関数型言語ですね。

The Elm Architecture

ElmにはThe Elm Architectureというアーキテクチャで動いています。

f:id:syossan:20180923142523p:plain

ハンズオンではこれをカウンタアプリケーションだとこう動くよって感じで示してくれました。

f:id:syossan:20180923142939p:plain

UpdateでModelの状態を変更し、変更されたModelを基にViewでHTMLを表示する、って感じですね。

ちなみにこのアーキテクチャを参考にReduxとかも作られたらしいです、しゅごい。

純粋関数型言語

Elmは純粋関数型言語のため、副作用を生むことなくアプリケーションを作成することが出来ます。
初めて純粋関数型言語を触ったんですが、変数を使わず関数のみで書くのはなかなか不思議な体験でした。

例えば、ハンズオン中でも説明があったのですが、 |> という要素も関数というので驚きでした。

ハンズオンの感想

というわけで、ハンズオンですが物凄く丁寧な説明と複数人のメンターの方がいて、初心者の私でもElmが理解できるくらい手厚いハンズオンでした。
資料自体もかなりわかりやすく、これだけ読んでもアプリケーションを作れるくらいに仕上がっています。

gitpitch.com

今回のハンズオンではインクリメンタルサーチを作ってみたのですが、少し悩みながら1h30ほどで動くものが作れました。
早い人では15分ほどで完成しており、元々関数型言語に造形の深い方はスッと慣れることが出来る言語なんじゃないかと。

あと、このハンズオンだけでElmのほとんどの基礎を網羅しており、残りはif文くらいの要素を学べば「Elm完全に理解した」と言えるくらいの学習コストの低さにも驚かされました。

最後に有識者のLTがあったのですが、Elmを使ってこんなのが作れるんだー!という気持ちになれたので最高でした。
飛び入りのLTもあったのですが、まさかの海外Elm勢で更にsoundcloudのエンジニアさんで、しかも物理演算を使ったプログラムを紹介していて3重に驚きでした・・・しゅごい。
その方の発表の中でPrometheusでElmが使われているということを知り、もっと有名なソフトウェアでElmが使われれば流行ってくるのかな?というお気持ちになりました。

github.com

というわけで、めちゃくちゃ勉強になったハンズオンでした!
運営の方々、ありがとうございました!!