Po nedávném srovnání Pythonu, Ruby a Golangu pro aplikaci příkazového řádku jsem se rozhodl použít stejný vzor k porovnání vytváření jednoduché webové služby. Pro toto srovnání jsem vybral Flask (Python), Sinatra (Ruby) a Martini (Golang). Ano, existuje mnoho dalších možností pro knihovny webových aplikací v každém jazyce, ale cítil jsem, že tyto tři se hodí ke srovnání.
Přehledy knihoven
Zde je srovnání knihoven na vysoké úrovni od Stackshare.
Baňka (Python)
Flask je mikrorámec pro Python založený na Werkzeug, Jinja2 a dobrých úmyslech.
Pro velmi jednoduché aplikace, jako je ta ukázaná v této ukázce, je Flask skvělou volbou. Základní aplikace Flask má pouze 7 řádků kódu (LOC) v jediném zdrojovém souboru Pythonu. Výhodou Flasku oproti jiným webovým knihovnám Pythonu (jako je Django nebo Pyramid) je to, že můžete začít v malém a podle potřeby vytvořit složitější aplikaci.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
Sinatra (Ruby)
Sinatra je DSL pro rychlé vytváření webových aplikací v Ruby s minimálním úsilím.
Stejně jako Flask je i Sinatra skvělá pro jednoduché aplikace. Základní aplikace Sinatra má pouze 4 LOC v jediném zdrojovém souboru Ruby. Sinatra se používá místo knihoven, jako je Ruby on Rails, ze stejného důvodu jako Flask – můžete začít v malém a aplikaci rozšířit podle potřeby.
require 'sinatra'
get '/hi' do
"Hello World!"
end
Martini (Golang)
Martini je výkonný balíček pro rychlé psaní modulárních webových aplikací/služeb v Golangu.
Martini je dodáváno s několika bateriemi více než Sinatra a Flask, ale je stále velmi lehké na začátek - pouze 9 LOC pro základní aplikaci. Martini se stal terčem kritiky ze strany komunity Golang, ale stále má jeden z nejlépe hodnocených projektů Github ze všech webových rámců Golang. Na kritiku zde reagoval přímo autor Martini. Některé další rámce zahrnují Revel, Gin a dokonce i vestavěnou knihovnu net/http.
package main
import "github.com/go-martini/martini"
func main() {
m := martini.Classic()
m.Get("/", func() string {
return "Hello world!"
})
m.Run()
}
Se základy z cesty, pojďme vytvořit aplikaci!
Popis služby
Vytvořená služba poskytuje velmi základní blogovou aplikaci. Jsou vytvořeny následující trasy:
GET /
:Vraťte blog (k vykreslení použijte šablonu).GET /json
:Vraťte obsah blogu ve formátu JSON.POST /new
:Přidejte na blog nový příspěvek (název, shrnutí, obsah).
Externí rozhraní k blogovací službě je pro každý jazyk naprosto stejné. Pro jednoduchost bude pro tento příklad použit jako úložiště dat MongoDB, protože je nejjednodušší na nastavení a nemusíme se vůbec starat o schémata. V normální „blogové“ aplikaci by pravděpodobně byla nezbytná relační databáze.
Přidat příspěvek
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
Zobrazit HTML
GET /
Zobrazit soubor JSON
GET /json
[
{
content:"Lorem ipsum dolor sit amet, consectetur ...",
title:"Test Post 1",
_id:{
$oid:"558329927315660001550970"
},
summary:"The First Test Post"
}
]
Struktura aplikace
Každou aplikaci lze rozdělit na následující součásti:
Nastavení aplikace
- Inicializujte aplikaci
- Spusťte aplikaci
Požadavek
- Definujte trasy, na kterých může uživatel požadovat data (GET)
- Definujte trasy, na kterých může uživatel odesílat data (POST)
Odpověď
- Vykreslit JSON (
GET /json
) - Vykreslení šablony (
GET /
)
Databáze
- Inicializovat připojení
- Vložte data
- Načíst data
Nasazení aplikace
- Docker!
Zbytek tohoto článku porovná každou z těchto komponent pro každou knihovnu. Účelem není naznačovat, že jedna z těchto knihoven je lepší než druhá – jde o poskytnutí konkrétního srovnání mezi těmito třemi nástroji:
- Baňka (Python)
- Sinatra (Ruby)
- Martini (Golang)
Nastavení projektu
Všechny projekty jsou bootstrapped pomocí docker a docker-compose. Než se ponoříme do toho, jak je každá aplikace zaváděna pod kapotou, stačí použít docker, aby se každá zprovoznila přesně stejným způsobem - docker-compose up
Vážně, to je ono! Nyní pro každou aplikaci existuje Dockerfile
a docker-compose.yml
soubor, který určuje, co se stane, když spustíte výše uvedený příkaz.
Python (flask) – Dockerfile
FROM python:3.4
ADD . /app
WORKDIR /app
RUN pip install -r requirements.txt
Tento Dockerfile
říká, že začínáme od základního obrazu s nainstalovaným Pythonem 3.4 a přidáváme naši aplikaci do /app
adresář a pomocí pip nainstalujte naše požadavky na aplikaci specifikované v requirements.txt
.
Ruby (sinatra)
FROM ruby:2.2
ADD . /app
WORKDIR /app
RUN bundle install
Tento Dockerfile
říká, že začínáme od základního obrazu s nainstalovaným Ruby 2.2 a přidáváme naši aplikaci do /app
adresář a pomocí bundleru nainstalujte naše požadavky na aplikaci uvedené v Gemfile
.
Golang (martini)
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
Tento Dockerfile
říká, že začínáme od základního obrazu s nainstalovaným Golangem 1.3 a přidáváme naši aplikaci na /go/src/github.com/kpurdon/go-blog
adresář a získání všech našich nezbytných závislostí pomocí go get
příkaz.
Inicializovat/spustit aplikaci
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 (Sinatra) – app.rb
# initialize application
require 'sinatra'
$ ruby app.rb
Golang (Martini) – 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
Definovat trasu (GET/POST)
Python (Flask)
# get
@app.route('/') # the default is GET only
def blog():
# ...
#post
@app.route('/new', methods=['POST'])
def new():
# ...
Ruby (Sinatra)
# get
get '/' do
# ...
end
# post
post '/new' do
# ...
end
Golang (Martini)
// 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) {
// ...
}
Vykreslení odpovědi JSON
Python (Flask)
Flask poskytuje metodu jsonify(), ale protože služba používá MongoDB, používá se nástroj mongodb bson.
from bson.json_util import dumps
return dumps(posts) # posts is a list of dicts [{}, {}]
Ruby (Sinatra)
require 'json'
content_type :json
posts.to_json # posts is an array (from mongodb)
Golang (Martini)
r.JSON(200, posts) // posts is an array of Post{} structs
Vykreslení HTML odpovědi (šablona)
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>
Ruby (Sinatra)
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>
Golang (Martini)
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>
Připojení k databázi
Všechny aplikace používají ovladač mongodb specifický pro daný jazyk. Proměnná prostředí DB_PORT_27017_TCP_ADDR
je IP propojeného kontejneru dockeru (ip databáze).
Python (Flask)
from pymongo import MongoClient
client = MongoClient(os.environ['DB_PORT_27017_TCP_ADDR'], 27017)
db = client.blog
Ruby (Sinatra)
require 'mongo'
db_ip = [ENV['DB_PORT_27017_TCP_ADDR']]
client = Mongo::Client.new(db_ip, database: 'blog')
Golang (Martini)
import "gopkg.in/mgo.v2"
session, _ := mgo.Dial(os.Getenv("DB_PORT_27017_TCP_ADDR"))
db := session.DB("blog")
defer session.Close()
Vložit data z 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)
Ruby (Sinatra)
client[:posts].insert_one(params) # params is a hash generated by sinatra
Golang (Martini)
db.C("posts").Insert(post) // post is an instance of the Post{} struct
Načíst data
Python (Flask)
posts = db.blog.find()
Ruby (Sinatra)
@posts = client[:posts].find.to_a
Golang (Martini)
var posts []Post
db.C("posts").Find(nil).All(&posts)
Nasazení aplikace (Docker!)
Skvělým řešením pro nasazení všech těchto aplikací je použití dockeru a 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
Ruby (Sinatra)
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
Golang (Martini)
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
Závěr
Na závěr se pojďme podívat na to, o čem se domnívám, že je to několik kategorií, kde se prezentované knihovny od sebe oddělují.
Jednoduchost
Zatímco Flask je velmi lehký a čte se jasně, aplikace Sinatra je nejjednodušší ze tří na 23 LOC (ve srovnání s 46 pro Flask a 42 pro Martini). Z těchto důvodů je Sinatra vítězem v této kategorii. Je však třeba poznamenat, že Sinatrova jednoduchost je způsobena výchozím „kouzlem“ – například implicitní prací, která se odehrává v zákulisí. Pro nové uživatele to může často vést ke zmatku.
Zde je konkrétní příklad „magie“ v Sinatrovi:
params # the "request.form" logic in python is done "magically" behind the scenes in Sinatra.
A ekvivalentní kód baňky:
from flask import request
params = {
'title': request.form['title'],
'summary': request.form['summary'],
'content': request.form['content']
}
Pro začátečníky v programování jsou Flask a Sinatra jistě jednodušší, ale pro zkušeného programátora, který tráví čas v jiných staticky typovaných jazycích, Martini poskytuje poměrně zjednodušující rozhraní.
Dokumentace
Dokumentace k baňce byla nejjednodušší na vyhledávání a nejpřístupnější. Zatímco Sinatra a Martini jsou oba dobře zdokumentované, samotná dokumentace nebyla tak přístupná. Z tohoto důvodu je Flask vítězem v této kategorii.
Komunita
Flask je vítězem této kategorie. Komunita Ruby je často dogmatická o tom, že Rails je jedinou dobrou volbou, pokud potřebujete něco víc než základní službu (i když Padrino to nabízí nad Sinatrou). Komunita Golangu se stále ani zdaleka neblíží shodě ohledně jednoho (nebo dokonce několika) webových rámců, což lze očekávat, protože jazyk samotný je tak mladý. Python však přijal řadu přístupů k vývoji webu, včetně Django pro předpřipravené plnohodnotné webové aplikace a Flask, Bottle, CheryPy a Tornado pro mikrorámcový přístup.
Konečné určení
Všimněte si, že smyslem tohoto článku nebylo propagovat jediný nástroj, ale poskytnout nezaujaté srovnání Flask, Sinatra a Martini. Vzhledem k tomu bych si vybral Flask (Python) nebo Sinatra (Ruby). Pokud pocházíte z jazyka jako C nebo Java, možná vás osloví staticky typická povaha Golangu. Pokud jste začátečník, může být Flask tou nejlepší volbou, protože je velmi snadné jej uvést do provozu a je zde velmi málo výchozího „kouzla“. Moje doporučení je, abyste byli flexibilní ve svých rozhodnutích při výběru knihovny pro váš projekt.
otázky? Zpětná vazba? Prosím o komentář níže. Děkuji!
Dejte nám také vědět, pokud byste měli zájem o nějaké srovnávací hodnoty.