ペンギン村 Tech Blog

技術をこよなく愛するエンジニア集団が在住するペンギン村から、世界へ役立つ(かもしれない)技術情報を発信する技術系ブログです。某アラレちゃんが済む村とは一切関係ありません。んちゃ!

Swift 4.2 で追加されるコンパイラディレクティブ(SE-0196)

何でもは知らない、知ってることだけ。

どうも、最近書き出しの挨拶を考えるのに時間を要する tobi462 です。

今日は SE-0196 で追加されたコンパイラディレクティブの紹介です。

今まで書いた Swift 4.2 の記事は以下です。

いつのまにかシリーズ化してきましたね。 (化物語みたいに)

Tl;Dr

#warning("強制アンラップはやめるべき") // コンパイル時の警告
let message = self.message!

#error("APIのアクセストークンを設定してください") // コンパイルエラー
let API_TOKEN = ""

in Objective-C

Objective-Cでは以下のようなコンパイラディレクティブが利用できました。

#warning 暫定対処、後ほど修正するべき
#error APIトークンを設定する必要あり

それぞれコンパイル時にWarning、Errorとできる仕組みです。

これによって後ほど修正すべき箇所をWarningとして警告しておいたり、ライブラリのテンプレートコード中に埋め込んでおき、実装が必須であることを明示できるというメリットがあります。

これはコメントで代替することも可能ですが、(標準の)コンパイル時において警告またはエラーとして明示することができる点がメリットになります。

in Swift 4.2

Objective-Cでサポートされていたこの機能が、Swift 4.2 でようやくサポートされました。

#warning("暫定対処、後ほど修正するべき")
#error("APIトークンを設定する必要あり")

また、#if のような他のコンパイラディレクティブとも併用して利用することも出来るようです。

#if os(iOS)
#error("macOSはサポート外です")
#endif

上記のように、あるライブラリを作成していたときに特定のプラットフォームだけサポート外としてエラーにしたいということもできるのは便利と言えるでしょう。

まとめ

やれやれ、1記事にするほどの内容でも無かったんじゃないかな、阿良々木くん。

というわけで Swift でもコンパイラディレクティブ(Warning、Error)が使えるようになったという話でした。

また次回?

Swift 4.2 で追加される @dynamicMemberLookup メモ(SE-0195)

全地球100億人のSwifterな皆さん、コンバトラーっ! tobi462 でーっす!

え、いつもとノリが違う?まぁ、そういう日もあるんじゃないでしょうか。

そんなわけで、前回前々回と続き Swift 4.2 の記事です。

今回は、Swift 作者であるラトナーさんの Proposal (SE-0195) のようですよ。

Tl;Dr

@dynamicMemberLookup
struct AnimeCharacter {
    subscript(dynamicMember member: String) -> String {
        let properties = ["name": "阿良々木月火", "music": "白金ディスコ"]
        return properties[member, default: ""]
    }
}

character.name  // => "阿良々木月火"
character.music // => "白金ディスコ"

dynamic?

Swift では Objective-C と異なり、コンパイル時に多くの静的チェックが行われます。

それはメソッド呼び出しやプロパティアクセスについても同様で、コンパイル時に存在しないシンボルへアクセスするようなコードはコンパイルエラーになります。

struct Person {
}

let alice = Person()
alice.name // コンパイルエラー

Swift などの静的型付けに慣れ親しんだ方はこれが極めて自然な挙動に見えるかもしれません。実際、コンパイル時にチェックされることでタイポなどのミスを事前に検出できるという大きなメリットがあります。

しかし、Swift から Python などの動的型付けを呼び出したい、つまり相互運用性を考えた時はどうでしょうか?

以下は Proposal から抜粋したコードです。

// import pickle
let pickle = Python.get(member: "import")("pickle")

// file = open(filename)
let file = Python.get(member: "open")(filename)

// blob = file.read()
let blob = file.get(member: "read")()

これは Swift 4.1 においても正当なコードですが明らかに冗長に感じます。

すなわち以下のように書けたらスマートではないでしょうか?

// import pickle
let pickle = Python.import("pickle")

// file = open(filename)
let file = Python.open(filename)

// blob = file.read()
let blob = file.read()

というのが、この Proposal の発端のようです。

@dynamicMemberLookup

それを実現するために @dynamicMemberLookup というアノテーションが導入されました。

class や struct 、 enum に付けられるもので、あわせて subscript(dynamicMember :xxx) の実装が必要になります(実装しないとコンパイルエラー)。

@dynamicMemberLookup
struct AnimeCharacter {
    subscript(dynamicMember member: String) -> String {
        let properties = ["name": "阿良々木月火", "music": "白金ディスコ"]
        return properties[member, default: ""]
    }
}

上記は String を受け取る subscript(dynamicMember) を実装していますが、これは以下のように呼び出すことが出来ます。

let character = Character
character.name  // => "阿良々木月火"
character.music // => "白金ディスコ"

このように事前に定義されていないシンボルを呼び出せているように見えるのが、 dynamicMemberLookup のメリットといえるでしょう。

あまり Swifty に感じない人も多いかもしれませんが、これは以下のコードのシンタックスシュガーと思えば良いでしょう。

let character = Character
character[dynamicMember: "name"]  // => "阿良々木月火"
character[dynamicMember: "music"] // => "白金ディスコ"

こう考えると、Dictionary などへのアクセスと同様に感じられるのではないでしょうか。

メソッド呼び出し風の書き方

さて、メンバーへのアクセス風の構文は分かりましたが、メソッド呼び出し風の書き方をしたい場合はどうなるのでしょうか?

それには以下のようにクロージャを返す subscript(dynamicMember: xxx) を宣言します。

@dynamicMemberLookup
struct Person {
    subscript(dynamicMember member: String) -> (_ input: String) -> Void {
        return {
            print("Hello, \($0)")
        }
    }
}

これは以下のように呼び出せます。

let person = Person()
person.hello("Goodbye") // => "Hello, Goodbye"

少し分かりづらいかもしれませんが、.hello でクロージャを取得し、それに対して (“Goodbye”) で呼び出しを行っている感じです。関数型プログラミングに通じている方は「カリー化」されている関数に対する呼び出しと考えると分かりやすいかもしれません。(これがカリー化という訳ではないのですが)

使いどころ?

さて、ここまで @dynamicMemberLookup について見てきましたが、果たしてどういったところに使えるのでしょうか?

お気づきだと思いますが、この機能が有効に適用できる箇所は限られています。

前述したようにコンパイル時にシンボルがあることを検出できるのが静的型付け言語のメリットです。

例えば、すべてのプロパティ・メソッドアクセスをこの機能で置き換えることは可能ですが、それはもはや動的型付け言語と同様「実行しなければ正しく動くかわからない」状態になるでしょう。

1つの例としては前述したPythonなどの動的型付け言語との相互運用性です。

// import pickle
let pickle = Python.import("pickle")

// file = open(filename)
let file = Python.open(filename)

// blob = file.read()
let blob = file.read()

このようにドット記法で呼び出せるのはとてもスマートだと感じます。

もう1つの例として Proposal では JSON のデータ構造へのアクセスが挙げられています。

@dynamicMemberLookup
enum JSON {
    case intValue(Int)
    case stringValue(String)
    case arrayValue(Array<JSON>)
    case dictionaryValue(Dictionary<String, JSON>)
    
    var stringValue: String? {
        if case .stringValue(let str) = self {
            return str
        }
        return nil
    }
    
    subscript(index: Int) -> JSON? {
        if case .arrayValue(let arr) = self {
            return index < arr.count ? arr[index] : nil
        }
        return nil
    }
    
    subscript(key: String) -> JSON? {
        if case .dictionaryValue(let dict) = self {
            return dict[key]
        }
        return nil
    }
    
    subscript(dynamicMember member: String) -> JSON? {
        if case .dictionaryValue(let dict) = self {
            return dict[member]
        }
        return nil
    }
}

最後の subscriptdynamicMemberLookup になっていますが、これを利用しない場合は以下のようになります。

let json = JSON.stringValue("Example")
json[0]?["name"]?["first"]?.stringValue

これは SwiftyJSON/SwiftyJSON ともよく似た見た目かと思います。

これが以下のように書けるようになります。

json[0]?.name?.first?.stringValue

Subscriptのアクセスに[]が不要となっているので、とてもスッキリした見た目になっています。

ご利用は計画的に

このように事前にすべてを定義できないけれども、 . を使ってアクセスしたほうがスッキリするケースで @dynamicMemberLookup を活用できるかと思います。

逆に言えば、それ以外のケースでは利用は慎重になるべきでしょう。

例えば 事前にすべてを定義できない と聞くと、 UserDefaults のラッパーとして利用することを考えることも出来るかもしれません。

@dynamicMemberLookup
struct UserDefaultsWrapper {
    let userDefaults: UserDefaults
    subscript(dynamicMember member: String) -> String? {
        return userDefaults.string(forKey: member)
    }
}

let wrapper = UserDefaultsWrapper(userDefaults: UserDefaults.standard)
let userName = wrapper.userName

しかし、これは明らかにアンチパターンです。

なぜなら userName とタイプすべきところを userNeme とタイポしてもコンパイラは何の警告も出さないためです。

さらに . アクセスされているので、一見するとこのコードはコンパイル時に静的に解決されているように見えてしまいます。

このように呼び出し時のタイプ量を減らすことを目的に利用するのは明らかに間違いでしょう。

おわり

というわけで、今回は @dynamicMemberLookup の紹介というかメモでした。

実のところ、私は最初にこの機能を知った時に Swift らしくないと感じました。静的にシンボルを解決するというSwiftの思想と相反すると感じた為です。

しかし、「Swifty な呼び出しを実現するためのシンタックスシュガー」と捉え直すと、まぁ使い方さえ間違えなければトレードオフとしてはありかな、という意見に落ち着きました(なんだか上から目線?)。

わたしの知らない Swift な物語はまだまだ続くのでしょう。

それでは。

【iOS】Hyperion-iOSもあるんだよ!

かむいです。今月転職しました。

転職活動も忙しかったりして1ヶ月ぶりの投稿となります。
時間過ぎるのホント早い!

Hyperion-Androidが話題に

先週 Android界隈で話題になったコレ。 こりゃすげぇ!てなりましたよね。

qiita.com

てっきりAndroidだけかと思っててんですけどちゃんとiOSもあったんですよ!

github.com

僕はもうかよちんの「さすがです!」LINEスタンプを開発者に送りつけたい気持ちになったのですが、サンプルコードがあったのでまずは動かしてみたわけです。

そしたら実際に動かしらもっと感動しちゃったので、そのことについて書きます。

導入の仕方

CocoaPodsかCarthegeで導入できます。

詳細はREADME.mdがわかりやすいのでそれ見てもらえれば早いのですが、CocoaPodsで言うと:configurations => ['Debug'] と設定することでデバックビルドでだけこれが見れるよという訳。なるほどね!

実際に動かして見た。いや、動かしてみて

Gifや画像を見て納得して終わらず、是非ともサンプルコードを起動するだけで良いのでご自身で触って見て頂きたいです。 zipでソース一式を落としてくればものの2分もせずに体感出来ます。

凄いことその1 : Zeplinとかで見ながら確認してたアレやコレを実行したアプリの中で見れる!

デザイナーに作ってもらったワイヤーフレームをZeplinなどのツールを使ってUIの情報をアプリに落とし込みますよね。

・テキストの配置位置
・テキストのフォントサイズ
・文字色、ボタン色
・セルのサイズ
etc...

それらを実行したアプリ上で確認できるんです。なんなら画面拡大も出来ちゃう
これマジ

https://camo.githubusercontent.com/ec23fbb49d169c85a5d881e35be0c16902c652bf/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f6c34456f4e4f494c72354f6676677973772f67697068792e676966

https://camo.githubusercontent.com/9641f342cee745d6d05c03cc100841be575738be/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f7854315239486639333833576a75636f6d492f67697068792e676966

凄いことその2: アニメーションを細かくチェックすることが出来る

画面遷移やカスタムアニメーション処理とか、実際に想定した動きになっているか、実は早過ぎてよくわかってない動きも実装の中にはあると思います。 それが0.5倍速とか0.25倍速に設定できて、実際の動きをゆっくり確認出来ちゃいます。
これマジ

https://camo.githubusercontent.com/5d18c81486e741052e51c384495b48ddecbfb8da/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f323646655a634e463944627138394d42692f67697068792e676966

凄いことその3: 皆んなでサードパーティプラグイン作ろ!作成ガイド鋭意製作中!

Calling all developers!!! Be one of the first to create a third-party plugin.   
The plugin creation guide is a work in progress, but if you are feeling ambitious  
 you can reference the plugins we have already created along with our documentation.

開発者の方も書いておりますが、新たなプラグインの製作者を募集しております! まだ機能が少ないのでむしろ俺が望むデバッグ機能はコレや!というのがあれば是非すでに書かれているHyperionCore Referenceを読んで見てはいかがでしょうか。

最後に

何か話題になるOSSがあったとして、それが Android界隈だけで盛り上がっていたらiOS側もあるのではと動いてみること、そういう気持ちでAndroid界隈の動向も追うのは大事だなと今更ながら痛感した次第です(・ω・)

Swift 4.2 では boolean の反転(toggle)ができるようになる(SE-0196)

どうも。ルパン三世 Part 5 の続きが楽しみな tobi462 です。

さて前回のenumの新機能に続き、また Swift 4.2 の話題です。

といっても、今回は驚くほど短いです(汗)

Tl;Dr

// before
messageLabel.isHidden = !messageLabel.isHidden

// after
messageLabel.isHidden.toggle()

toggle()

SE-0196 にて Bool の extension として toggle メソッドが用意されました。

冒頭に書いたとおり、いままで foo.bar = !foo.bar というコードを書かなければならなかったところが foo.bar.toggle() と書けるようになるわけです。

まぁ、自前で実装していた方も多いかもしれませんが。

実装

具体的な実装は以下のようになっているようです。

extension Bool {
  @_inlineable
  /// Toggles the value of the Boolean. 
  ///
  /// Calling this method sets the variable to `true` if it was `false`,
  /// and sets it to `false` if it was `true`. For example:
  ///
  ///    var bools = [true, false]
  ///
  ///    bools[0].toggle()
  ///    // bools now contains [false, false]
  public mutating func toggle() {
    self = !self
  }
}

swift/Bool.swift at swift-4.2-branch · apple/swift · GitHub

まぁ・・・まんまですね(笑)

ちなみに2行目の @_inlineable は初めて知ったのですが、SE-0193 で既に実装されていたようです。

どの Swift バージョンで入ったかはすぐに終えなかったのですが、とりあえず Swift 4.2 では利用できました。

関数のインライン化はパフォーマンス的に有利な面もあると思いますが、多くのケースでは過度な最適化にあたると思うので、普段のiOSアプリ開発では利用しないほうが良いのでしょう。(きっと)

閑話休題。

おわり

foo.bar = !foo.bar はある意味、DRY原則にも反していると言えます。

今後は foo.bar.toggle() というスッキリした書き方ができて良いですね。

Swift 4.2 では enum の件数がとれるようになる(SE-0194)

どうも。赤ワインが一番美味しい季節は秋だと思うのですが、この初春という時期もなかなか乙だと感じる tobi462 です。

今日は Swift 4.2 で追加される SE-0194 について軽くメモです。

ちなみに執筆時点で Xcode 9.4 beta 1 がリリースされていますが、こちらには Swift 4.2 は含まれていないので、現時点では自前でビルドする必要があります。

追記(2018/06/03):
現在は Swift 公式サイトのDownloads の「Swift 4.2 Development」から Xcode のツールチェーンをダウンロードすることができるので、そちらを利用するのが楽でしょう。 f:id:yu_dotnet2004:20180603082228p:plain 追記(ここまで)

今回、Swiftのビルドにあたっては以下の記事を参考にさせていただきました。 https://qiita.com/rintaro/items/2047a9b88d9249459d9aqiita.com

Tl;Dr

enum にCaseIterableを適合することで.allCasesでcase値の一覧が取得できます。

enum Fruits: CaseIterable {
    case apple, orange, banana
}

Fruits.allCases.count // => 3
Fruits.allCases // => [Fruits.apple, Fruits.orange, Fruits.banana]

これまで

これまでは enum の件数を取得したり、すべての値を列挙する方法はありませんでした。

そのため以下のように自前で実装する必要がありました。

enum Fruits {
    case apple, orange, banana
}

extension Fruits {
    static var count: Int {
        return all.count
    }
    static var all: [Fruits] {
        return [.apple, .orange, banana]
    }
}

Fruits.count // => 3
Fruits.all   // => [.apple, .orange, .banana]

これは実装が面倒であるという以上の問題があり、 enum に値が追加された場合に修正を忘れる可能性があります。

以下ではgrapeを追加していますが、allプロパティの修正を忘れています。しかし、Swiftコンパイラはこのミスを検出できません。

enum Fruits {
    case apple, orange, banana, grape
}

extension Fruits {
    static var count: Int {
        return all.count
    }
    static var all: [Fruits] {
        return [.apple, .orange, .banana] // .grape を追加し忘れている
    }
}

Swift 4.2 から

冒頭でも書きましたがCaseIterableに適合することで、自動的に.allCasesが使えるようになります。

enum Fruits: CaseIterable {
    case apple, orange, banana
}

Fruits.allCases.count // => 3

for fruit in Fruits.allCases {
    print("I like \(fruit).")
}
// => I like apple.
// => I like orange.
// => I like banana.

これはCodableと同じようにコンパイル時に自動的にコードを生成する仕組みのようです。

ちなみに私自身は利用したことが無いのですが、Sourceryでも同等のことが出来るようです。

ただし、Sourceryの場合はSwiftソースが生成されるのに対し、CaseIterableではコンパイラによって自動的に生成されるので、生成後のコード管理などについて意識する必要がありません。(これもCodableと同様ですね)

自前で実装する

さて、enumに関連値(Associated-Value)があった場合はどうなるのでしょうか?

結論から言うとコンパイルエラーとなります。

enum Maybe: CaseIterable {
    case nothing
    case some(Bool)
}
// => error: type 'Maybe' does not conform to protocol 'CaseIterable'

こういった自動生成されないケースでは、static var allCasesを自前で実装する必要があります。

enum Maybe: CaseIterable {
    static var allCases: [Maybe] {
        return [.nothing, .some(true), .some(false)] // 網羅的でなくてもOK
    }
    case nothing
    case some(Bool)
}

ちなみにenum in enumといったケースでは、(理論的には)コンパイラが網羅的に列挙できますが、関連値がある場合は自前で実装が必要になります。

enum Food: CaseIterable {
    static var allCases: [Food] {
        return [.drink] + Fruits.allCases.map(Food.fruit)
    }
    case drink
    case fruit(Fruits)
}

Food.allCases.forEach { print("\($0)") }
// => drink
// => fruit(Fruits.apple)
// => fruit(Fruits.orange)
// => fruit(Fruits.banana)

個人的にはこれは妥当な仕様だと感じます。

CaseIterable vs Sourcery

実行時に動的なアプローチが行えないSwiftについて、Sourceryなどは静的メタプログラミングという手段を使って冗長さを防ぐのは良いアプローチだと感じます。

しかし、静的メタプログラミングと言えば聞こえはいいですが、古来からあるコード自動生成のアプローチなので、テンプレート言語を覚えたりコード生成・管理というワンクッションの手間はかかります。

そうしたちょっとした手間を防ぐという意味で、言語自体にそうしたコード生成の仕組みが用意されるのは良い方針だと個人的には感じます。(そのブラックボックスさに微妙さを感じる人もいるかもしれませんが、言語仕様として明確化されていれば問題ないかなと思います)

おわり

Codableもそうですが、最近のSwiftではコンパイラによるコード自動生成というアプローチが増えてきたなという印象があります。

こうしたSwiftコンパイラによる自動生成で便利にする仕様は今後も増えていくのではないでしょうか?(と、Swift Evolutionも追ってないのに勝手に予測してみます)

P.S.
気が向いたら他の Swift 4.2 の機能も記事にしてみたいと思います。

「がんばるぞいbot」を作った話、そしてAWS Lambdaに移行しようという話

どうも、最近週末に映画を観るのが楽しみなtobi462です。ちなみにAmazon Prime Videoです。

今日はプライベートで作ったシステムの話、というか物語を書いてみたいと思います。

がんばるぞいbotとは

Slackで「がんばるぞい!」と書き込んだら、キャラクターが応援してくれるBotです。

ちょうど以下のような感じですね。 f:id:yu_dotnet2004:20180409230924p:plain

NEWGAME!という漫画の第一巻で有名になった「がんばるぞい」を活用したBotですね。

開発のキッカケ

開発のキッカケはシンプルで、 仕事前とか勉強前にがんばるって書き込んだら応援して欲しいな というものでした。

ペンギン村の住人でSlackのワークスペースを持っているのですが、そこの「newgame」チャンネルで活躍しています。

残念ながらSlackAppとして公開はしていませんが悪しからず。

歴史を振り返る

そんな(最初は)シンプルなBotだったのですが、システム構成やら機能やら様々な変化がありました。

実は今度「AWS Lambda」に移行しようと思っているのですが、その前に歴史を振り返ってみよう、というのが今回の記事です。

0. はじまり - Android / Kotlin アプリ時代

Slack用のBotのはずなのに、最初はなぜかAndroidアプリからのスタートでした。

当初、私はAndroid/Kotlinを勉強しようという意気込みにあふれており、せっかくなのでAndroidアプリからSlackにPostしてみようというノリでした。

ユーザ名などを設定画面で入力して「がんばるぞい」ボタンを押したらSlackにポストするという単純なものでしたが、記念すべき「がんばるぞいbot」の開発の始まりでもありました。

f:id:yu_dotnet2004:20180409234436p:plain

記念すべき最初の実装なので個人的にもとても懐かしいものです。

開発したアプリは alpha 版として公開することで、ペンギン村の住人に利用してもらえる状態にしました。

意外な問題

さて、初期バージョンを開発したのは良かったのですがすぐに課題がみつかりました。 それはペンギン村の住人でこのBotを使いたいユーザは全員iPhoneユーザということでした。

というわけで、初期のAndroid/Kotlinバージョンはあっという間に闇に消えました。

悲しいぞい・・・

1. 始動 - Swift / Vapor / Heroku 時代

そんなわけで、すぐに次の開発をスタートしました。 もはやモバイルを捨て、サーバサイドへ旅立つ必要がありました。

仕事でSwiftを多く触っていた私は、サーバサイドSwiftでやろうと決めました。

Let's Server Side Swift

構成は以下のとおりでした。

  • Swift 4.x
  • Vapor
  • Heroku

サーバを自分で用意・管理するのが面倒だったので、PaaSであるHerokuを利用しました。

Vaporを選んだ強い理由はありませんが、過去にVaporとKituraを軽く触った時に、Vaporの方がシンプルだなと感じたのが大きいかもしれません。

分かっていた問題

さてHerokuは無料プランで使いました。

Herokuをご存知の方は、何が問題になるかよくご存知かと思います。 そう「スピンダウン」です。

Herokuは一定時間アクセスがないとインスタンスが止まってしまう(スピンダウン)ため、次回アクセス時に起動するまでに時間が掛かる(スピンアップ)という問題でした。

以前、Herokuを使ったことがあったので、この問題は事前によく分かっていました。

そこでクラウドのIaaSである「Digital Ocean」にサーバを立て、そこから定期的にアクセスするようにしました。Digital Oceanは、全てSSDで値段も安いと聞いたことがあったので、AWSやGCPではなくDigital Oceanを使ってみることにしてみました。

  • DigitalOcean
  • Ubuntu
  • Digdag

定期アクセスであればcronで十分かと思ったのですが、仕事でDigdagを利用していたこともあり、せっかくなので使ってしまおうというノリでした。

Herokuを無料で利用するために$5/moなインスタンスを契約するというなんだかおかしな状況 になりましたが、問題は完璧に解決しました。

2. 激動 - Kotlin / Spark / Docker 時代

さて、こうして出来上がったがんばるぞいBotはなかなか人気で、毎朝「がんばるぞい」と書き込まれる毎日が続きました。

私自身もアクティブユーザとして、今日はどんなキャラが応援してくれるか楽しみにしていました。

がんばったぞいBot

さて、そうこうするうちに新しい機能が欲しくなりました。

「がんばるぞい」も良いけど、がんばったら褒めてほしくない? という単純な動機により「がんばったぞいbot」を作ることにしました。

f:id:yu_dotnet2004:20180410000832p:plain

いやはや我ながら素晴らしい機能だと思いました。

既存のサーバサイドSwiftに機能追加しても良かったのですが、その頃Kotlinに興味を持っており(ry

ということで以下のような構成でシステムを立ち上げました。

  • Kotlin
  • Spark
  • MongoDB
  • MongoExpress
  • Docker (DockerCompose)
  • Ubuntu
  • DigitalOcean

ここで流行りのDockerも触ってみることにしました。勉強がてらというところです。

MongoDBにすべき強い理由はなかったのですが、なんとなくKVSやってみようくらいのノリでした。

CI/CD・さらなる機能追加

さて、ここまできたら自動ビルド・デプロイもしたくなりました。

ここまできたら全部マニュアルな感じてやってみよう、ということでJenkinsを使った自動ビルド・自動デプロイを実現しました。

自動デプロイはmasterブランチにマージされたら、それをトリガーにデプロイされる感じです。(Blue/Greenデプロイまではやりませんでした) f:id:yu_dotnet2004:20180410001558p:plain

と、書くと簡単にできたようにも見えるのですが、まぁそのいろいろ苦労しました・・・あまり慣れていなかったこともあり。

そして「ゆんのティータイム」(15:00になったらティータイムに誘ってくれる)という機能を追加するため、こちらにもDigdagが導入されました。

追加されたコンポーネント:

  • Jenkins
  • Digdag

システム統合

さてこのあたりでペンギン村の住人の一人が開発メンバーに加わることになりました。

そして紆余曲折あり 「がんばるぞいbot」と「がんばったぞいbot」を統合する ことになりました。

まぁ元々別システムにしている理由がなかったので、統合できてよかったかと思います。

これにより2号である「Swift / Vapor / Heroku」によるBotは幕を閉じました。お疲れ様でした。

S3連携、さらなる機能追加

さて、もうここまできたらイケイケな感じです。

今時、画像データはS3に置くべきでしょ ということでAWSも使うことになりました。

そして、機能も思うがままに開発・デプロイし、利用者のフィードバックを楽しみにしていました。

そう・・・あの事件が起こるまでは・・・

AYBABTU

ある日、MongoExpressにアクセスしてみると奇妙な状況になっていました。

DBのデータは消えており、何やら怪しいデータが作成されています。 そして、データに残された英文を見ると、「お前たちのデータは頂いた。返してほしくば xx BTCを」と書かれています。

・・・ ランサム被害やんけ!

そしてデータはローカルに残っていたものを除いて失われてしまいました。 幸い、そこまで失われたデータは多くなかったのですが、個人的にショックでした。

原因

原因を調べたところ、犯人は意外なものでした。

Firewall(UFW)は設定しており、きちんとブロックされるはずのアクセスがなぜ通ってしまっていたのか。

端的にいうと以下の問題でした。 ngzm.hateblo.jp

個人的に手痛い失敗でしたが、多くを学ぶことが出来ました。

  • 稼働後に外からアクセス出来ないことを確認すべき
  • クラウドのFirewallも活用すべき
  • 大事なデータはバックアップすること

まぁ当たり前の話かもしれません。 おそらくこれが業務であればいずれも実行したと思います。

しかし、プライベートでこういった事態に遭遇することが出来て、かえって良い経験ができた。そんな風に今は思っています。

自動バックアップ、管理画面追加、最終的なシステム構成

ここから先はそこまで大きな話はありません。

まずJenkinsのJobでMongoDBのデータを自動バックアップし、S3にアップするようにしました。

データの追加が面倒なので、Railsを使った管理画面も追加しました。なぜかこれはHerokuにデプロイしたのですが。

そして最終的なシステム構成は以下のようになりました。

  • AWS
    • S3
  • DigitalOcean
    • Kotlin / Spark
    • MongoDB / MongoExpress
    • Digdag
    • Docker / DockerCompose
    • Jenkins
    • Ubuntu
  • Heroku
    • Ruby / Rails

メモリが不足する度にDigitalOceanのインスタンスをスケールアップした為、 $20/mo のインスタンスになっていました。

そこで気づきます。 これただのSlackのBotだよね? と。

3. 未来へ - AWS Lambda 時代?

自分で一からインフラも含めてやってみようということで、いろいろ試したシステムでしたが、 さすがにお金をかけすぎている とあらためて思いました。 もちろんそれが楽しかったので、良かったのですが。

さてBotという要件を考えると、リクエストが要求された時などごく短時間だけ計算リソースがあれば十分です。

そもそもBotの利用者も少なく、一日のうち利用している計算リソースの時間はおそらく30秒程度でしょう。(デプロイまわりはさておき)

そのために$20/moなインスタンスを24h稼働しているのはあまりにも無駄です。

なんだか結論ありきな書き方をしてしまいましたが、 そこで「AWS Lambda」ですよ というお話です。

新規に開発メンバーも一人加わって、今はAWS Lambda移行を進めているというか、これから始まるというか、まぁそんな状況です。

おわりに

なんだか無駄なことばっかりしているプライベート開発だったような気もしますが、こうして自分でいろいろ試してみるのは面白く、勉強になるとあらためて思いました。

AWS Lambda への移行でも、いろいろと学べる部分があると思うので今から楽しみです。

そんなわけで「がんばるぞいBot」の始まりから今後の未来まで書いてみました。 物語はこれからも続きます。

「今日も1日がんばるぞい!」

自作アプリにキャラクターを登場させてみた

f:id:kamui_project_tony:20180310104210p:plain

はじめに

そだねー。カムイです。
平昌で日本代表が頑張っていた2/19、クラスメソッド株式会社で主催されたSwift勉強会『AKIBA.swift』にて登壇をして参りました。

classmethod.connpass.com

注) アニメ版権絵多用したため公開できる資料はありません。🙇‍♂️

話したことについて

日課メーターのver2.0.0でキャラクターを登場させたので、その機能紹介と合わせて外注でイラストや声を依頼した時のことについて発表してきました。

Q: Swift関係なく無い?
A: モルゲッソヨ

ではなくて、今回発表テーマが「フリートーーーク!」だったので、AKIBA.swiftにはAKIBA枠というアニメ好きな人のための枠もあるくらいだし、みんな2次元好きやろ?という思惑のもとで発表をさせて頂いた次第です。


主催者のお二方からも良いリアクション頂きましたb

内容

アジェンダは以下の通り
1. どこで頼めるの?
2. どのようにお願いするの?
3. いくらかかるの?
4. 何日くらいで出来るの?
5. ここに詰まったからおまいらも気をつけろ!
6. まとめ(登場させてみた結果www)

どこで頼めるの?

アウトソーシングサービスは色々ありますが、上げさせて頂いたのは以下の通りです。 f:id:kamui_project_tony:20180310095455p:plain

下の3つは声優さん専用のサービスです。
ボイスサンプルが用意されており、声を直接聴いて気になった人を選定できます。

僕は今回ランサーズと萌えボイスを利用しました。

www.lancers.jp
www.moe-v.net

どのようにお願いするの?

各サービスによって変わる点がありますが、主に2タイプあります。

コンペ方式

算と仕事内容を提出し公募。不特定多数のランサー(作れる人)の提案の中から、その中から気になったランサーをこちら側が選定出来るスタイル。

プロジェクト方式

ランサー・予算を先に決めて提出。ランサーが承諾してくれたら作業を開始するスタイル

プロジェクト方式が一般的なイメージかと思いますが、どんな人がいるのかわからない人にはコンペ方式がオススメ。 僕は依頼したいランサーが予め決まっていたので、プロジェクト方式のスタイルでやりました。

いくらかかるの?

ピンキリです。。

イラストで言えばデザイナーの知名度や熟練度, 依頼する枚数, 依頼する内容により差が出ます。
1枚XX円からという説明をして頂ける方もいますが、内容によりけりです。
もちろん仕事開始前に依頼内容を打ち合わせし、そこで前もって見積もって頂けます。

声では文字数*X円 + 声優さんの知名度に紐づくチップ みたいな計算方式になっているらしく、台本ベースになると結構な額になると思います。日課メーターでは一言ボイスを10パタンほどだったのでそこまででは無かったのですが、あまりに文字数が少ないとギャラも少額になるため、仕事を受け付けられないといったケースもあるようです。

何日くらいで出来るの?

これも依頼するボリュームによりけりなのですが、日課メーターを参考にまとめます。

イラスト: 日課メーターでは200x200サイズで上半身のみのデフォルメキャラクターデザインを5パタン依頼。キャラクターのイメージやポージングもデザイナーと打ち合わせしながら進めたため、割と時間がかかりました。最終的に納品頂けたのは1ヶ月ほどかかりました。

声: 一言ボイスを10パタンほどだったので、1週間ほどでご用意頂けました。ただ人気な方ですと仕事が立て込んでいるため、事前に遅れる旨を報告頂けたりします(これはデザイナーの方にも言えるのですが)。

ここに詰まったからおまいらも気をつけろ!

色々あったのですが、発表では2つお話をしました。

絵・声を並行で進めるとどちらかで「あれ?」が出たら調整が難しいゾ!

僕の場合、絵の仕様がざっくりで打ち合わせしながらだったせいもあるのですが、いざ形になったものを見て、それと声とを当て込んで見ると「あ、合わない…」てケースが出てしまいました。
デザイナーの方に調整をお願い事なきを得ましたが、最悪時間とお金をかけて解決しないといけなくなるところでした。

出来る事なら事前に仕様を固めて依頼を出す、またはどちらかを先に納品してから、それに合わせて依頼を修正するみたいなやり方が出来ると良いかもしれません。

キャラクターデザインはユーザー指向で!

僕が単にアニメ好きだったので実装したい機能、実装したかったキャラデザや声で進めた話だったのですが、主に日課メーターのユーザーって20〜30代のデキる女性が多いんですよ…ダイエットなど美容に努める女性の方々からご好評頂けております。

そのためこんな萌え萌えしてるものが果たしてウケるかというと微妙でした。
ユーザーのことを思えば、むしろイケメンにすれば良かったですね笑
幸いレビューで厳しいお言葉は頂けて降りませんが、例え表示をOFFにできる機能でも星1をつけられる可能性はあるので、キャラクターデザインは慎重に進めるべきだと思いました。

まとめ(登場させてみた結果www)

f:id:kamui_project_tony:20180310103535p:plain

さいごに

かなりマニアックな発表内容となってしまいましたが、良い評価も多く頂けたので良かったです。 もしキャラクターコンテンツを自作アプリに入れて見たいという方がいらっしゃれば、参考にしてみてはいかがでしょうか。

最後に、今回日課メーターの褒めキャラにご協力頂いた珠川まほろさん、寿松木明日花さん、本当にありがとうございました!

twitter.com
twitter.com