コマンドラインアプリケーションのPython、Ruby、Golangを最近比較した後、同じパターンを使用して単純なWebサービスの構築を比較することにしました。この比較には、Flask(Python)、Sinatra(Ruby)、およびMartini(Golang)を選択しました。 はい、各言語のWebアプリケーションライブラリには他にも多くのオプションがありますが、これら3つは比較に適していると感じました。
ライブラリの概要
Stackshareによるライブラリの概要は次のとおりです。
フラスコ(Python)
Flaskは、Werkzeug、Jinja2、および善意に基づくPython用のマイクロフレームワークです。
このデモに示されているような非常に単純なアプリケーションの場合、Flaskは最適な選択肢です。基本的なFlaskアプリケーションは、単一のPythonソースファイルに含まれるわずか7行のコード(LOC)です。他のPythonWebライブラリ(DjangoやPyramidなど)に対するFlaskの利点は、小規模から始めて、必要に応じてより複雑なアプリケーションを構築できることです。
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
シナトラ(ルビー)
Sinatraは、最小限の労力でRubyでWebアプリケーションをすばやく作成するためのDSLです。
Flaskと同様に、Sinatraは単純なアプリケーションに最適です。基本的なSinatraアプリケーションは、単一のRubyソースファイルに含まれる4つのLOCのみです。 Flaskと同じ理由で、Ruby on Railsなどのライブラリの代わりにSinatraが使用されます。小規模から始めて、必要に応じてアプリケーションを拡張できます。
require 'sinatra'
get '/hi' do
"Hello World!"
end
マティーニ(ゴラン)
Martiniは、GolangでモジュラーWebアプリケーション/サービスをすばやく作成するための強力なパッケージです。
マティーニには、シナトラとフラスコの両方よりもいくつかのバッテリーが含まれていますが、それでも最初は非常に軽量です-基本的なアプリケーションではわずか9LOCです。 Martiniは、Golangコミュニティからいくつかの批判を受けていますが、GolangWebフレームワークの中で最も評価の高いGithubプロジェクトの1つです。マティーニの作者はここでの批判に直接応えました。他のフレームワークには、Revel、Gin、さらには組み込みのnet/httpライブラリが含まれます。
package main
import "github.com/go-martini/martini"
func main() {
m := martini.Classic()
m.Get("/", func() string {
return "Hello world!"
})
m.Run()
}
基本がわからなくなったら、アプリを作成しましょう!
サービスの説明
作成されたサービスは、非常に基本的なブログアプリケーションを提供します。次のルートが構築されます:
-
GET /
:ブログを返します(テンプレートを使用してレンダリングします)。 -
GET /json
:ブログのコンテンツをJSON形式で返します。 -
POST /new
:ブログに新しい投稿(タイトル、概要、コンテンツ)を追加します。
ブログサービスへの外部インターフェースは、各言語でまったく同じです。簡単にするために、この例のデータストアとしてMongoDBを使用します。これは、セットアップが最も簡単で、スキーマについてまったく心配する必要がないためです。通常の「ブログのような」アプリケーションでは、リレーショナルデータベースが必要になる可能性があります。
投稿を追加
POST /new
$ curl --form title='Test Post 1' \
--form summary='The First Test Post' \
--form content='Lorem ipsum dolor sit amet, consectetur ...' \
http://[IP]:[PORT]/new
HTMLを表示
GET /
JSONを表示
GET /json
[
{
content:"Lorem ipsum dolor sit amet, consectetur ...",
title:"Test Post 1",
_id:{
$oid:"558329927315660001550970"
},
summary:"The First Test Post"
}
]
アプリケーション構造
各アプリケーションは、次のコンポーネントに分類できます。
アプリケーションのセットアップ
- アプリケーションを初期化します
- アプリケーションを実行する
リクエスト
- ユーザーがデータをリクエストできるルートを定義する(GET)
- ユーザーがデータを送信できるルートを定義する(POST)
応答
- JSONをレンダリングします(
GET /json
) - テンプレートをレンダリングします(
GET /
)
データベース
- 接続を開始します
- データを挿入
- データを取得する
アプリケーションの展開
- Docker!
この記事の残りの部分では、ライブラリごとにこれらの各コンポーネントを比較します。 目的は、これらのライブラリの1つが他のライブラリよりも優れていることを示唆することではなく、3つのツール間の特定の比較を提供することです。
- フラスコ(Python)
- シナトラ(ルビー)
- マティーニ(ゴラン)
プロジェクトの設定
すべてのプロジェクトは、dockerおよびdocker-composeを使用してブートストラップされます。各アプリケーションが内部でブートストラップされる方法に飛び込む前に、dockerを使用して、まったく同じ方法で各アプリケーションを起動して実行することができます-docker-compose up
真剣に、それだけです!これで、アプリケーションごとにDockerfile
があります。 およびdocker-compose.yml
上記のコマンドを実行したときに何が起こるかを指定するファイル。
Python(フラスコ)- Dockerfile
FROM python:3.4
ADD . /app
WORKDIR /app
RUN pip install -r requirements.txt
このDockerfile
Python 3.4がインストールされたベースイメージから開始し、アプリケーションを/app
に追加するとします。 ディレクトリとpipを使用して、requirements.txt
で指定されたアプリケーション要件をインストールします 。
ルビー(シナトラ)
FROM ruby:2.2
ADD . /app
WORKDIR /app
RUN bundle install
このDockerfile
Ruby 2.2がインストールされたベースイメージから開始し、アプリケーションを/app
に追加するとします。 ディレクトリとbundlerを使用して、Gemfile
で指定されたアプリケーション要件をインストールします 。
ゴラン(マティーニ)
FROM golang:1.3
ADD . /go/src/github.com/kpurdon/go-blog
WORKDIR /go/src/github.com/kpurdon/go-blog
RUN go get github.com/go-martini/martini && \
go get github.com/martini-contrib/render && \
go get gopkg.in/mgo.v2 && \
go get github.com/martini-contrib/binding
このDockerfile
Golang 1.3がインストールされたベースイメージから開始し、アプリケーションを/go/src/github.com/kpurdon/go-blog
に追加するとします。 ディレクトリを作成し、go get
を使用して必要なすべての依存関係を取得します コマンド。
アプリケーションの初期化/実行
Python(Flask)- app.py
# initialize application
from flask import Flask
app = Flask(__name__)
# run application
if __name__ == '__main__':
app.run(host='0.0.0.0')
$ python app.py
Ruby(シナトラ)- app.rb
# initialize application
require 'sinatra'
$ ruby app.rb
ゴラン(マティーニ)- app.go
// initialize application
package main
import "github.com/go-martini/martini"
import "github.com/martini-contrib/render"
func main() {
app := martini.Classic()
app.Use(render.Renderer())
// run application
app.Run()
}
$ go run app.go
ルートの定義(GET / POST)
Python(Flask)
# get
@app.route('/') # the default is GET only
def blog():
# ...
#post
@app.route('/new', methods=['POST'])
def new():
# ...
ルビー(シナトラ)
# get
get '/' do
# ...
end
# post
post '/new' do
# ...
end
ゴラン(マティーニ)
// define data struct
type Post struct {
Title string `form:"title" json:"title"`
Summary string `form:"summary" json:"summary"`
Content string `form:"content" json:"content"`
}
// get
app.Get("/", func(r render.Render) {
// ...
}
// post
import "github.com/martini-contrib/binding"
app.Post("/new", binding.Bind(Post{}), func(r render.Render, post Post) {
// ...
}
JSON応答をレンダリングする
Python(Flask)
Flaskはjsonify()メソッドを提供しますが、サービスはMongoDBを使用しているため、mongodbbsonユーティリティが使用されます。
from bson.json_util import dumps
return dumps(posts) # posts is a list of dicts [{}, {}]
ルビー(シナトラ)
require 'json'
content_type :json
posts.to_json # posts is an array (from mongodb)
ゴラン(マティーニ)
r.JSON(200, posts) // posts is an array of Post{} structs
HTML応答のレンダリング(テンプレーティング)
Python(Flask)
return render_template('blog.html', posts=posts)
<!doctype HTML>
<html>
<head>
<title>Python Flask Example</title>
</head>
<body>
{% for post in posts %}
<h1> {{ post.title }} </h1>
<h3> {{ post.summary }} </h3>
<p> {{ post.content }} </p>
<hr>
{% endfor %}
</body>
</html>
ルビー(シナトラ)
erb :blog
<!doctype HTML>
<html>
<head>
<title>Ruby Sinatra Example</title>
</head>
<body>
<% @posts.each do |post| %>
<h1><%= post['title'] %></h1>
<h3><%= post['summary'] %></h3>
<p><%= post['content'] %></p>
<hr>
<% end %>
</body>
</html>
ゴラン(マティーニ)
r.HTML(200, "blog", posts)
<!doctype HTML>
<html>
<head>
<title>Golang Martini Example</title>
</head>
<body>
{{range . }}
<h1>{{.Title}}</h1>
<h3>{{.Summary}}</h3>
<p>{{.Content}}</p>
<hr>
{{ end }}
</body>
</html>
データベース接続
すべてのアプリケーションは、その言語に固有のmongodbドライバーを使用しています。環境変数DB_PORT_27017_TCP_ADDR
リンクされたDockerコンテナのIP(データベースIP)です。
Python(Flask)
from pymongo import MongoClient
client = MongoClient(os.environ['DB_PORT_27017_TCP_ADDR'], 27017)
db = client.blog
ルビー(シナトラ)
require 'mongo'
db_ip = [ENV['DB_PORT_27017_TCP_ADDR']]
client = Mongo::Client.new(db_ip, database: 'blog')
ゴラン(マティーニ)
import "gopkg.in/mgo.v2"
session, _ := mgo.Dial(os.Getenv("DB_PORT_27017_TCP_ADDR"))
db := session.DB("blog")
defer session.Close()
POSTからデータを挿入
Python(Flask)
from flask import request
post = {
'title': request.form['title'],
'summary': request.form['summary'],
'content': request.form['content']
}
db.blog.insert_one(post)
ルビー(シナトラ)
client[:posts].insert_one(params) # params is a hash generated by sinatra
ゴラン(マティーニ)
db.C("posts").Insert(post) // post is an instance of the Post{} struct
データの取得
Python(Flask)
posts = db.blog.find()
ルビー(シナトラ)
@posts = client[:posts].find.to_a
ゴラン(マティーニ)
var posts []Post
db.C("posts").Find(nil).All(&posts)
アプリケーションのデプロイ(Docker!)
これらすべてのアプリケーションをデプロイするための優れたソリューションは、dockerとdocker-composeを使用することです。
Python(Flask)
Dockerfile
FROM python:3.4
ADD . /app
WORKDIR /app
RUN pip install -r requirements.txt
docker-compose.yml
web:
build: .
command: python -u app.py
ports:
- "5000:5000"
volumes:
- .:/app
links:
- db
db:
image: mongo:3.0.4
command: mongod --quiet --logpath=/dev/null
ルビー(シナトラ)
Dockerfile
FROM ruby:2.2
ADD . /app
WORKDIR /app
RUN bundle install
docker-compose.yml
web:
build: .
command: bundle exec ruby app.rb
ports:
- "4567:4567"
volumes:
- .:/app
links:
- db
db:
image: mongo:3.0.4
command: mongod --quiet --logpath=/dev/null
ゴラン(マティーニ)
Dockerfile
FROM golang:1.3
ADD . /go/src/github.com/kpurdon/go-todo
WORKDIR /go/src/github.com/kpurdon/go-todo
RUN go get github.com/go-martini/martini && go get github.com/martini-contrib/render && go get gopkg.in/mgo.v2 && go get github.com/martini-contrib/binding
docker-compose.yml
web:
build: .
command: go run app.go
ports:
- "3000:3000"
volumes: # look into volumes v. "ADD"
- .:/go/src/github.com/kpurdon/go-todo
links:
- db
db:
image: mongo:3.0.4
command: mongod --quiet --logpath=/dev/null
結論
結論として、提示されたライブラリが互いに分離しているいくつかのカテゴリであると私が信じているものを見てみましょう。
シンプルさ
Flaskは非常に軽量で読みやすくなっていますが、Sinatraアプリは23 LOCの3つのうちで最も単純です(Flaskの46とMartiniの42と比較して)。これらの理由から、シナトラはこのカテゴリーの勝者です。ただし、Sinatraの単純さは、よりデフォルトの「魔法」(たとえば、舞台裏で行われる暗黙の作業)によるものであることに注意してください。新規ユーザーの場合、これはしばしば混乱を招く可能性があります。
シナトラの「魔法」の具体例を次に示します。
params # the "request.form" logic in python is done "magically" behind the scenes in Sinatra.
そして同等のFlaskコード:
from flask import request
params = {
'title': request.form['title'],
'summary': request.form['summary'],
'content': request.form['content']
}
プログラミングの初心者にとって、FlaskとSinatraは確かに簡単ですが、他の静的に型付けされた言語に時間を費やした経験豊富なプログラマーにとって、Martiniはかなり単純なインターフェースを提供します。
ドキュメント
Flaskのドキュメントは、検索が最も簡単で、最も親しみやすいものでした。 SinatraとMartiniはどちらも十分に文書化されていますが、文書自体はそれほど親しみやすいものではありませんでした。このため、Flaskがこのカテゴリの勝者です。
コミュニティ
Flaskは、このカテゴリの勝者です。 Rubyコミュニティは、基本的なサービス以外のものが必要な場合に、Railsが唯一の良い選択であることに独断的であることがよくあります(PadrinoはSinatraの上にこれを提供していますが)。 Golangコミュニティは、言語自体が非常に若いため、1つ(またはいくつか)のWebフレームワークに関するコンセンサスにはまだほど遠いです。ただし、Pythonは、すぐに使用できるフル機能のWebアプリケーション用のDjangoや、マイクロフレームワークアプローチ用のFlask、Bottle、CheryPy、Tornadoなど、Web開発への多くのアプローチを採用しています。
最終決定
この記事のポイントは、単一のツールを宣伝することではなく、Flask、Sinatra、およびMartiniの公平な比較を提供することであることに注意してください。そうは言っても、Flask(Python)またはSinatra(Ruby)を選択します。 CやJavaのような言語を使用している場合は、静的に型付けされたGolangの性質が魅力的かもしれません。初心者の場合は、起動と実行が非常に簡単で、デフォルトの「魔法」がほとんどないため、Flaskが最適な選択肢となる可能性があります。プロジェクトのライブラリを選択するときは、柔軟に決定することをお勧めします。
質問?フィードバック?以下にコメントしてください。ありがとうございます!
また、いくつかのベンチマークをご覧になりたい場合はお知らせください。