Sanma Salted

半人前エンジニアの備忘録

Ktorを使おう、という話

こんにちは。 最近アウトプットが足りていないと思っていたところ、4年ほど前に作ったこのブログの存在を思い出したのでなんとなく再開してみます。

復活第一回目はKotlin製のWEBアプリフレームワークであるKtorについて書いてみようと思います。

Ktorとは

もうご存知の方も多いと思いますが、IntelliJ IDEAなどで有名なJetBrains製の軽量WEBフレームワークで、すべてKotlinで作られています。 特徴としてはデフォルトではいわゆる「薄い」フレームワークで、必要な機能(feature)のみを適宜追加していく形で利用します。

なぜKtorを使うのか

1.kotlinが好き

1といいつつ本当はこれがほぼ全てなんですが。

最近はAndroid界隈で一気に広がりを見せつつあるKotlinですが、Androidとは無関係に本当によくできた良い言語だと思います。 Kotlin自体の説明は今更なので割愛しますが、個人的にはJavaRubyのいいとこ取りをしたような言語だと思って使っています。

私自身本業はJavaベースのサーバーサイドで、ここ2年ほどチャンスがあれば業務でKotlinを使うようにしているのですが、とにかくJavaに比べて同じことを書くのが圧倒的に楽。 記述量が少ないので頭の中で考えた仕組みをそのままコードに直感的に落とし込める感じで、これはRubyを書いている時の感覚に非常に似ています。

そしてKotlinの最大の特徴の一つであるNull安全とJava譲りの堅牢な型システムで、非常に安全にコードを組むことができます。 これらの特徴は静的解析が容易という点でIntelliJなどのIDEと非常に相性が良いことを意味しますので、きちんとした道具を使うことで圧倒的な生産性を上げることができます。

Javaも嫌いでは無いんですが、やはりRubyなどに比べるとどうしても冗長なコードになりがちなのが大きなデメリットであると思っているので、 膨大なJava資産や安全性をRubyのような心地よさで書けるKotlinがとても好きなのです。

2.Spring重い

Kotlin(というかJVM言語)でサーバーサイドというと、ほぼ必ずSpringが第一候補に挙がると思います。 Springに関しても今更説明する必要も全く無い素晴らしいフレームワークだと思いますが、多機能な万能フレームワークであるがゆえに学習コストが高いのは事実ですし、 またどうしてもアプリケーション全体がSpringへがっつり依存してしまうというデメリットは避けられません。

Springはアプリケーション自体をSpringに密結合させることで自分で書かなくてはいけないコード量を減らして生産性を高めることができますが、 逆に将来的にフレームワークの移行を考える必要が出た時に非常に大きなコストを伴うことは間違いないです。

これは完全に要件と好みによりますが、APIサーバーに限らず一般的なWEBアプリ用途でもあえて「薄い」フレームワークを選択することもありだと個人的には思っています。

3.正式リリースした

長いこと開発されていたKtorですが、2018年の後半になってようやく、ようやくバージョン1.0が正式にリリースされました。 これに伴ってかつては砂漠のように何も書いていなかった公式のドキュメント類も一気に充実しました。 またネット上の情報量も爆発的に増えているので、業務で使用するのに耐えうる環境は整って来たのかな、という印象です。

Ktorの特徴

KtorはKotlinの特性を生かした非同期処理などユニークな特徴がたくさんあります。

個人的に面白いと思ったのがPipelineという仕組みです。 Pipelineはリクエストを受け取ってからレスポンスを返すまでの一連の処理の流れのことで、 コードブロックやラムダを渡すことで非同期に処理が行われていきます。

Ktorを使ったアプリケーションではこの仕組みを使って処理の流れの中の適当な場所にそのアプリ独自の処理を差し込むことで様々な機能を実装して行く、というのが基本的な形になります。

ここで上の「薄い」フレームワークという話と関わってくるのですが、 極端に言えばKtorを使った開発ではこの「リクエストを受けて返すまでに処理を差し込む」部分でのみKtorに依存すればいいので、その処理の中身はKtorとは完全に分離した形で設計することが容易です。 これは最近みんなが大好きなクリーンアーキテクチャなどの疎結合な設計を採用する時に非常にメリットのある仕組みだと思っています。

まとめ

久しぶりにブログを書いたら結局何がいいたいのかよくわからない記事になってしまいましたが、 とりあえずこれからKotlinとKtorをガンガン使っていきたいんだぞ、とそういうお気持ちを表明する決意表明でした、ということで終わりにしたいと思います。

本当は上記のPipelineのことやKtorのさまざまな基本機能について色々と書きたいのですが、長くなるので次回以降に回したいと思います。

SwiftでSQLiteを使う(SwiftData)

iOSアプリを作るにあたってデータの保存は避けては通れないと思います。

方法はいろいろあるかと思いますが、個人的にはSQLiteを使うのが好みです。

まあ保存できればなんでもいいのが本音なんですが、普段からよくSQLをガリガリ書いているので慣れているから、以外にあまり理由はありませんw

(こんなことじゃいけないとはわかっているので、いずれ他の方法も研究してみます。いずれ。)

というわけでSwiftSQliteを扱えるライブラリを探してみたのですが、

SwiftDataというライブラリが扱いやすそうだったのでこれをつかって実装してみることにしてみました。

ryanfowler/SwiftData · GitHubryanfowler/SwiftData · GitHub


SwiftではObjective-Cのライブラリも普通に使えるのですが、せっかくなのでSwiftで書かれたライブラリを使ってみます。サンプルを見る限り非常に簡単に扱えそうなのもGood。

ちなみ配布ライセンスはMITライセンスなので、商用でも使えますね。ありがたい。

まだ日本語の情報はあまりありませんが(記事執筆時点で1件だけ見つけました)、

そもそも使い方は簡単ですし公式のドキュメントに全て書いてあるのであまり問題はないかと思います。



インストール

導入は特に特別なこともなく

  1. GithubからもってきたSwiftData.swiftをプロジェクトにコピー
  2. ナビゲータのProjectセッティング→Build PhasesのLink Binary with Librariesからlibsqlite3.dylibをadd
  3. Bulid SettingsのObjective-C Bridging Headerで指定したヘッダーファイルに
#import "sqlite3.h"

を記述。これだけです。簡単ですね。



使ってみる

なにはともあれとりあえず使ってみることにします。
ちなみにデータベースファイルはSwiftData.sqliteというファイルを勝手に作成してくれるのでとっても楽チンです。
ここではSwiftDataSampleというラッパークラスを作っていきます。

class SwiftDataSample {
    
    // コンストラクタ
    init(){
        if(!self.isExistsDataBase()){
            // 基本テーブルがなかったら作る
            let(result, msg) = self.create_basic_tables();
        }

    }

    //  テーブルの存在確認
    func isExistsDataBase() ->Bool{
        let (tb, err) = SwiftData.existingTables();
        if(!contains(tb,"hoge_mst")){
            //  hoge_mstがない
            return false;
        }else{
            return true;
        }
    
    }

    // テーブルを作る
    func create_basic_tables() ->(Bool, String){
        
        //  hoge_mstを作る
        if let err = SwiftData.createTable("hoge_mst", withColumnNamesAndTypes: ["name" : .StringVal, "created_on":.DateVal]){
            //  エラー発生
            println(SwiftData.errorMessageForCode(err!));
            return(false, "error ocured in creating_hoge_mst");
        }
        
        return (true,"schema initialize succeeded");
    }
    
}


ひとまずデータベースを初期化する処理ができました。
hoge_mstというテーブルが存在するか確認して、存在しなければ新たに作成します。


テーブルの作成は普通にCREATE TABLEを実行してもいいんですが、 SwiftDataが便利なメソッドを提供してくれているのでこちらを使用してみました。

    SwiftData.createTable("hoge_mst", withColumnNamesAndTypes: ["name" : .StringVal, "created_on":.DateVal])

この部分がそうですが、このメソットを使うと name列(String型)、createt_on列(Date型)の他に、
自動的にID列(Integer AUTOINCREMENT PRIMARY KEY)も作成してくれます。どこまでも親切です。


あとはデータベースにアクセスしたい場所で

let SDS = SwiftDataSample()

インスタンス化してやるだけです。

とはいえまだテーブルを作っただけなので、さきほどのクラスにデータを追加して取得するメソッドを追加していきます。

    
    // nameを受け取ってinsertするメソッド
    func Add(title_in :String) ->Bool{
        // sqlを準備
        let sql = "INSERT INTO hoge_mst (name, created_on) VALUES (?, current_date)";
        
        // ?に入る変数をバインドして実行
        if let err = SwiftData.executeChange( sql, withArgs: [title_in]) {
            //エラーが発生した時の処理
            let msg = SwiftData.errorMessageForCode(err);
            print(msg)
            return false; 
        } else {
            return true;
        }
    }

    //  データを全件抜き出してdictionaryの配列で返す
    func SelectAll() ->[<String, String>]{
        let sql = "Select ID as id, name as name from hoge_mst";
        var result: [Dictionary<String, String>] = [];

        let (resultSet, err) = SwiftData.executeQuery(sql);
        if err != nil {
            //there was an error during the query, handle it here
            var msg = SwiftData.errorMessageForCode(err!);
            println(msg)

        } else {
            for row in resultSet {
                //  取得したリザルトセットからデータを抜き出して配列に追加していく
                var name:String? = row["name"]?.asString()
                var id:Int? = row["id"]?.asInt()

                result.append(["name":name!, "id":String(id!)])
            }
        }
        return result;

    }


以上でデータの追加と取得ができるようになりました。

let SDS = SwiftDataSample()
SDS.Add("funga");
let data = SDS.SelectAll();

for d in data{
    //....
}

てな感じで使います。あとは画面に出すなり UITableViewのデータソースに使うなり、ご自由にお使いください。


うっかりしそうな点としてはdelete、insert,updateなんかのデータを操作する系のSQL

    SwiftData.executeChange( sql, withArgs: [title_in])

Select文は

    SwiftData.executeQuery(sql)

を使うこと、くらいでしょうか。


ちなみに上記のやり方だとSQLごとにオートコミットされてしまいます。
トランザクション処理をしたい場合はクロージャを使って

        let task = {() ->Bool in
            /*トランザクションで行いたい一連の処理 */
        }

        var err = SwiftData.transaction(task)

のように書けばOKです。


余談ですが、 SwiftDataは構造体として定義されているのですが、デフォルトでSDというエイリアスが設定されています

public typealias SD = SwiftData

なのでSwiftDataのメソッドを使うときは

    SD.executeQuery(sql)

とだけ書けばいい(というか公式のサンプルはそうなってる)のですが、なんか"SD"ってシンプルすぎてうっかりすると他で使っちゃいそう
なので私はSwiftDataの名称をそのまま使っております。特に問題はないはず。

ちょっと長くなってしまいましたが、せっかくの新言語なので新言語で書かれたライブラリを使ってみました。
本当に扱いやすくて必要十分な機能を持っていますので、SwiftSQLiteを使ってみたい方はぜひ一度お試しください。