Edited at

モダンな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を用いて簡単なAPIを作成します


手軽さ

★★★★★

3分くらいで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言語感想&概説

至高の言語、Nimを始めるエンジニアへ

jester を使います


手軽さ

★★★☆☆

こちらも5分くらいでAPIサーバーが立ちます

がNimの手軽さを考慮して★3つ


始め方

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

nimble install jester

適当なファイルを作成

touch server.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を使用すれば型推論してくれます


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


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の依存関係と必要な依存関係を追加します


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 を編集します


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のマッパーの依存関係を追加します


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を編集します


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 入門

Python3からMySQL繋ぐ時は、いろいろあるけどとりあえずPyMySQLにしとこうや

Nim 言語で RESTful API 作ってみた

Nim言語感想&概説

至高の言語、Nimを始めるエンジニアへ

goでtime.Timeをmysqlから読む

Goでmysqlに接続する

Gradle 5.0 で作れるようになった build.gradle.kts と settings.gradle.kts

Kotlin+Spark FrameworkでAPI作ってみた

GradleでKotlinのプロジェクトを作成する


フレームワーク

Flask

Jester

Go Echo

Spark Framework


所属

レッドインパルス 株式会社ではインターン生を積極的に募集しています!

興味ある方はぜひ!!

株式会社POLでも募集しています!