モダンな4つの言語で簡単なAPI作ってみた

**タイトル詐欺です**
僕が超偏見と独断で**最近キテる or 流行りそうな予感**な言語4つを選びました✋
## 使用言語
- **Python**
- **Nim**
- **Go**
- **Kotlin**


## 概要
今回は独断と偏見で4つの言語(Python, Nim, Go, Kotlin)の軽量フレームワーク(順にflask,jester,echo,spark)を用いて簡単なAPIを作成し、その手軽さを比較してみました
## 環境
Python 3.6.4
Nim 0.19.2
Go 1.11.4
Kotlin 1.3.21
MySQL 5.7.25
MySQLクライアント
Python: PyMySQL
Nim: db_mysql
Go: go-sql-driver/mysql
Kotlin: kotlin.jdbc

今回はなんとなくORMではなく純粋なMySQLクライアントを使用しました

## 注意
ド素人エンジニアなのでところどころ間違ったことを記している可能性があります.遠慮なくご指摘いただけるとありがたいです！

## データの用意
簡単なデータ処理を行えるように以下のようなデータを用意しました

**tasksテーブル**

|id|name|created_at|
|---|---|---|
|1|aaaa|2019-04-06 23:40:56|

## Python(flask)
[Flask](http://flask.pocoo.org/)を用いて簡単なAPIを作成します
## 手軽さ
★★★★★
３分くらいでAPI叩けるようになります
### 始め方
pythonが入っている前提でお話します

まずはflaskをインストールします

```
pip install flask
```

適当なファイルを作成します

```
mkdir flask_sample
cd flask_sample
touch server.py
```
ルーティングを定義します

```server.py
from flask import Flask
app = Flask(__name__)


@app.route("/")
def hello():
    return "Hello World!"


if __name__ == '__main__':
    app.debug = True
    app.run(host='0.0.0.0')
```

```
python server.py
```
デフォルトが5000番ポートみたいなのでアクセスすればブラウザにHelloWorldが表示されます
http://127.0.0.1:5000/

### DB操作
PyMySQLを使用してデータベースに接続し操作を行います
ルーティングを`tasks/[taskId]`にして指定したIDのデータを取得して返します

```
pip install PyMySQL
```
`server.py` を以下のように変更

```server.py
from flask import Flask, jsonify
import pymysql.cursors
app = Flask(__name__)


def getDBConnection():
    return pymysql.connect(
        host='host',
        port=port, # 文字列ではなくint
        user='user',
        passwd='pass',
        db='db',
        cursorclass=pymysql.cursors.DictCursor
    )

@app.route("/tasks/<int:taskId>")
def hello(taskId):
    task = getTask(taskId)
    return jsonify(task)


def getTask(taskId):
    conn = getDBConnection()
    try:
        with conn.cursor() as cursor:
            # Read a single record
            sql = "SELECT * FROM `tasks` WHERE id = %s"
            cursor.execute(sql, (taskId,))
            result = cursor.fetchone()
            return result
    finally:
        conn.close()
        return result


if __name__ == '__main__':
    app.debug = True
    app.run(host='0.0.0.0') # 127.0.0.1 だとpostmanから叩けません多分…
```

http://localhost:5000/tasks/1
でjsonが帰ってくるはずです

## Nim(jester)
Nimはpythonライクに書けるコンパイラ言語です
処理速度がめちゃ早いらしい
[Nim言語感想&概説](https://qiita.com/snowlt23/items/f50ab84afeab9469e422)
[至高の言語、Nimを始めるエンジニアへ](https://qiita.com/rigani/items/6e87c7cee6903ed65ed2#%E7%92%B0%E5%A2%83%E4%BD%9C%E3%82%8A%E3%82%84%E5%AE%9F%E8%A1%8C%E5%91%A8%E3%82%8A)
[jester](https://github.com/dom96/jester) を使います
### 手軽さ
★★★☆☆
こちらも5分くらいでAPIサーバーが立ちます
がNimの手軽さを考慮して★3つ

### 始め方

webフレームワーク`jester`のインストール

```
nimble install jester
```

適当なファイルを作成

```
touch server.nim
```

中身

```nim:server.nim
import jester

routes:
  get "/":
    resp "Hello world!"
```
実行

```
nim c -r server.nim  
```

http://localhost.5000
で`Hello world!`が表示されます！これまた早い！

### DB操作
db_mysqlを使用してデータベースに接続し操作を行います
ルーティングを`tasks/[taskId]`にして指定したIDのデータを取得して返します

Nimは型定義もできますが、`var`を使用すれば型推論してくれます

```nim:server.nim
import jester, random, strutils, asyncdispatch, json, db_mysql, times

proc getDBConnection(): DbConn =
    open("host:port", "user", "pass", "db")


proc getTask(taskId: int): Row =
    var dbConn: DbConn = getDBConnection()
    var task: Row = dbConn.getRow(sql "SELECT * FROM tasks WHERE id = " & $taskId)
    result = task # resultが返り値になります

routes:
    get "/tasks/@id":
        var task: Row = getTask(parseInt(@"id"))
        var data = %*{"id": task[0], "name": task[1], "created_at": task[2]}
        resp $data, "application/json"

```
http://localhost:5000/tasks/1
でjsonが帰ってくるはずです

## Go(echo)
### 入門手軽さ
★★★★☆
こちらも超軽量フレームワークなので一瞬で立てれます
goのプロジェクト作成とかとか少し面倒？なので4つ
### 始め方
僕はgvmでgoのバージョンを管理しているのでバージョンを指定してGOPATHに入ります

```
gvm use 1.11.4
cd $GOPATH/src
mkdir go_api
cd go_api
```
今回はechoを使用します

```
go get -u github.com/labstack/echo/...
```
ファイルを作成

```
touch server.go
```

```server.go
package main

import (
	"net/http"

	"github.com/labstack/echo"
)

func main() {
	e := echo.New()
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, World!")
	})
	e.Logger.Fatal(e.Start(":1323"))
}
```
実行します

```
go run main.go server.go                                                                                  21:26:02

  ____    __
 / __/___/ /  ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.0.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                   O\
⇨ http server started on [::]:1323
```
http://localhost:1323
でHello, World!が表示されます

### DB操作
今回はgo-sql-driver/mysqlを使用します
goはデータを構造体で定義してそこにマッピングしていくことでができます
構造体を定義する際にjsonで返す時のキーも指定できます

```
go get github.com/go-sql-driver/mysql
```


```go:main.go
package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/labstack/echo"
)

type Task struct {
	ID        int        `json:"id"`
	Name      string     `json:"name"`
	CreatedAt *time.Time `json:"created_at"`
}

func main() {
	e := echo.New()
	e.GET("/tasks/:id", getTask)
	e.Logger.Fatal(e.Start(":1323"))
}

func getDBConnection() *sql.DB {
	db, err := sql.Open("mysql", "username:pasword@tcp(host:port)/dbname?parseTime=true") //parseTime=trueがないとtime.Timeがうまくparseできない
	if err != nil {
		panic(err.Error())
	}
	return db
}

func getTask(c echo.Context) error {
	id, _ := strconv.Atoi(c.Param("id")) // c.Param("id")でクエリパラメータ取得
	task := selectTask(id)
	return c.JSON(http.StatusOK, task) // 200とjsonを返す
}

func selectTask(id int) Task {
	db := getDBConnection() // db接続
	defer db.Close() // 最後にCloseする宣言
	task := Task{} // 構造体の初期化
  // select文の発行と構造体のそれぞれのメンバーにマッピング
	if err := db.QueryRow("SELECT * FROM tasks WHERE id = ?", id).Scan(&task.ID, &task.Name, &task.CreatedAt); err != nil {
		log.Fatal(err)
	}
	return task
}
```

http://localhost:1323/tasks/1
でjsonが帰ってくるはずです

## Kotlin(spark)
### 入門手軽さ
★☆☆☆☆
gradle+sparkでやってますが、HelloWorldを表示させるのも一苦労でした
IDEに頼らずコマンドライン縛りでやったら
2〜3時間かかりました…
が、以下コピペで10分くらいで立ち上げれます
mavenに挫折してgradleにしました
### 始め方
gradleプロジェクトを作成します
使用バージョンは以下です

```
$ gradle -v

------------------------------------------------------------
Gradle 5.3.1
------------------------------------------------------------

Build time:   2019-03-28 09:09:23 UTC
Revision:     f2fae6ba563cfb772c8bc35d31e43c59a5b620c3

Kotlin:       1.3.21
Groovy:       2.5.4
Ant:          Apache Ant(TM) version 1.9.13 compiled on July 10 2018
JVM:          1.8.0_161 (Oracle Corporation 25.161-b12)
OS:           Mac OS X 10.14.3 x86_64
```
gradleプロジェクトを`kotlin-application`を指定して作成します

```
$ gradle init --dsl kotlin

Select type of project to generate:
  1: basic
  2: cpp-application
  3: cpp-library
  4: groovy-application
  5: groovy-library
  6: java-application
  7: java-library
  8: kotlin-application
  9: kotlin-library
  10: scala-library
Enter selection (default: basic) [1..10] 8

Project name (default: api):
Source package (default: api):

BUILD SUCCESSFUL in 7s
2 actionable tasks: 2 executed
```

`build.gradle.kts`にsparkの依存関係と必要な依存関係を追加します

```gradle:build.gradle.kts
/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Kotlin application project to get you started.
 */

plugins {
    // Apply the Kotlin JVM plugin to add support for Kotlin on the JVM.
    id("org.jetbrains.kotlin.jvm").version("1.3.21")

    // Apply the application plugin to add support for building a CLI application.
    application
}

repositories {
    // Use jcenter for resolving your dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
}

dependencies {
    // Use the Kotlin JDK 8 standard library.
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    // Use the Kotlin test library.
    testImplementation("org.jetbrains.kotlin:kotlin-test")

    // Use the Kotlin JUnit integration.
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
    compile("com.sparkjava:spark-core:+")
    compile("org.slf4j:slf4j-log4j12:1.7.21")

}

application {
    // Define the main class for the application.
    mainClassName = "api.AppKt"
}
```

`src/main/kotlin/api/App.kt` を編集します

```kotlin:App.kt
package api

import spark.Spark.*

fun main(args: Array<String>) {
    get("/") { req, res -> "Hello World" }
}
```

gradleを実行します

```
$ gradle run

> Task :run
log4j:WARN No appenders could be found for logger (spark.route.Routes).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
<==========---> 80% EXECUTING [7s]
> :run
```

http://localhost:4567/
これでHello Worldが表示されます

### DB操作
jdbcとjsonのマッパーの依存関係を追加します

```gradle:build.gradle.kts
/*
 * This file was generated by the Gradle 'init' task.
 *
 * This generated file contains a sample Kotlin application project to get you started.
 */

plugins {
    // Apply the Kotlin JVM plugin to add support for Kotlin on the JVM.
    id("org.jetbrains.kotlin.jvm").version("1.3.21")

    // Apply the application plugin to add support for building a CLI application.
    application
}

repositories {
    // Use jcenter for resolving your dependencies.
    // You can declare any Maven/Ivy/file repository here.
    jcenter()
}

dependencies {
    // Use the Kotlin JDK 8 standard library.
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    // Use the Kotlin test library.
    testImplementation("org.jetbrains.kotlin:kotlin-test")

    // Use the Kotlin JUnit integration.
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
    compile("com.sparkjava:spark-core:+")
    compile("org.slf4j:slf4j-log4j12:1.7.21")
    compile("org.jetbrains.kotlin:kotlin-jdbc:0.10.1316")
    compile("joda-time:joda-time:2.10.1")
    compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.8")
	compile("com.fasterxml.jackson.core:jackson-databind:2.8.8.1")
}

application {
    // Define the main class for the application.
    mainClassName = "api.AppKt"
}
```

App.ktを編集します

```kotlin:App.kt
/*
 * This Kotlin source file was generated by the Gradle 'init' task.
 */
package api

import spark.Spark.*
import spark.*
import java.sql.DriverManager
import java.util.*
import java.sql.Connection;
import java.sql.Statement
import java.sql.ResultSet
import org.joda.time.DateTime
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import spark.ResponseTransformer

data class Task(
        var id: Long,
        var name: String,
        // jdbcがDateTimeをうまく扱ってくれないのでString
        var createdAt: String
)

fun main(args: Array<String>) {
    Class.forName("com.mysql.jdbc.Driver");
    val toJson = JsonTransformer(ObjectMapper().registerKotlinModule())
    path("/tasks"){
        get("/:id", getTask(),toJson)
    }
}

fun getTask(): Route = Route { req, _ ->
        findTask(req.params("id").toInt())
}

fun findTask(id: Int): Task {
    lateinit var task: Task
    var statement: Statement? = null
    statement = getDBConnection()?.createStatement()
    var resultSet: ResultSet? = statement?.executeQuery("SELECT * FROM tasks WHERE id = ${id}")
    while(resultSet?.next() ?:false) {
        var sdf :java.text.SimpleDateFormat  = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
        var date = Date(resultSet!!.getTimestamp(3).getTime())
        task = Task(resultSet!!.getLong(1),resultSet!!.getString(2),sdf.format(date))
    }
    resultSet?.close()
    statement.close()
    return task
}

// DB接続
fun getDBConnection(): Connection {
    var conn: Connection = DriverManager.getConnection("jdbc:mysql://host:port/db","username","pass")
    return conn
}

// jsonにマッピング
class JsonTransformer(private val objectMapper: ObjectMapper) : ResponseTransformer {
    override fun render(model: Any?): String =
            objectMapper.writeValueAsString(model)
}
```

実行します

```
gradle run
```

http://localhost:4567/tasks/1
これでjsonが帰ってきます


ちなみにkotlinは入門難易度が高すぎです
このjson返すまで3時間かかりました笑笑笑

## 参考
[[Python] Flask 入門](http://python.zombie-hunting-club.com/entry/2017/11/03/223503)
[Python3からMySQL繋ぐ時は、いろいろあるけどとりあえずPyMySQLにしとこうや](https://qiita.com/mitch0807/items/f9f6efd4e65a022a8ab9)
[Nim 言語で RESTful API 作ってみた](https://enomotodev.hatenablog.com/entry/2017/10/07/131118)
[Nim言語感想&概説](https://qiita.com/snowlt23/items/f50ab84afeab9469e422)
[至高の言語、Nimを始めるエンジニアへ](https://qiita.com/rigani/items/6e87c7cee6903ed65ed2#%E7%92%B0%E5%A2%83%E4%BD%9C%E3%82%8A%E3%82%84%E5%AE%9F%E8%A1%8C%E5%91%A8%E3%82%8A)
[goでtime.Timeをmysqlから読む](https://ota42y.com/blog/2014/10/08/go-mysql-time/)
[Goでmysqlに接続する](https://qiita.com/taizo/items/54f5f49c6102f86194b8)
[Gradle 5.0 で作れるようになった build.gradle.kts と settings.gradle.kts](https://qiita.com/KenjiOtsuka/items/f6595d6557ee270af569)
[Kotlin+Spark FrameworkでAPI作ってみた](https://qiita.com/mooriii/items/c80f9e5cd60f70539c5d)
[GradleでKotlinのプロジェクトを作成する](http://kawaken.hateblo.jp/entry/2018/12/15/001740)

## フレームワーク
[Flask](http://flask.pocoo.org/)
[Jester](https://github.com/dom96/jester)
[Go Echo](https://echo.labstack.com/)
[Spark Framework](http://sparkjava.com/)

## 所属
[レッドインパルス 株式会社](https://www.wantedly.com/projects/300828)ではインターン生を積極的に募集しています！
興味ある方はぜひ！！
[株式会社POL](https://www.wantedly.com/projects/197480)でも募集しています！