Efter en nyligen jämförelse av Python, Ruby och Golang för en kommandoradsapplikation bestämde jag mig för att använda samma mönster för att jämföra att bygga en enkel webbtjänst. Jag har valt Flask (Python), Sinatra (Ruby) och Martini (Golang) för denna jämförelse. Ja, det finns många andra alternativ för webbapplikationsbibliotek på varje språk, men jag tyckte att dessa tre lämpar sig bra för jämförelse.
Bibliotekets översikter
Här är en jämförelse på hög nivå av biblioteken av Stackshare.
Kolv (Python)
Flask är ett mikroramverk för Python baserat på Werkzeug, Jinja2 och goda avsikter.
För mycket enkla applikationer, som den som visas i denna demo, är Flask ett utmärkt val. Den grundläggande Flask-applikationen är endast 7 rader kod (LOC) i en enda Python-källfil. Draget av Flask framför andra Python-webbbibliotek (som Django eller Pyramid) är att du kan börja i det små och bygga upp till en mer komplex applikation efter behov.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
Sinatra (Ruby)
Sinatra är en DSL för att snabbt skapa webbapplikationer i Ruby med minimal ansträngning.
Precis som Flask är Sinatra bra för enkla applikationer. Den grundläggande Sinatra-applikationen är bara 4 LOC i en enda Ruby-källfil. Sinatra används istället för bibliotek som Ruby on Rails av samma anledning som Flask - du kan börja smått och utöka applikationen efter behov.
require 'sinatra'
get '/hi' do
"Hello World!"
end
Martini (Golang)
Martini är ett kraftfullt paket för att snabbt skriva modulära webbapplikationer/tjänster i Golang.
Martini kommer med några fler batterier inkluderade än både Sinatra och Flask men är fortfarande väldigt lätt till att börja med - endast 9 LOC för grundapplikationen. Martini har fått en del kritik av Golang-communityt men har fortfarande ett av de högst rankade Github-projekten i något Golang-webbramverk. Författaren till Martini svarade direkt på kritiken här. Några andra ramverk inkluderar Revel, Gin och till och med det inbyggda net/http-biblioteket.
package main
import "github.com/go-martini/martini"
func main() {
m := martini.Classic()
m.Get("/", func() string {
return "Hello world!"
})
m.Run()
}
Med grunderna ur vägen, låt oss bygga en app!
Tjänstebeskrivning
Tjänsten som skapas ger en mycket grundläggande bloggapplikation. Följande rutter är konstruerade:
GET /
:Returnera bloggen (med en mall för att rendera).GET /json
:Returnera blogginnehållet i JSON-format.POST /new
:Lägg till ett nytt inlägg (titel, sammanfattning, innehåll) i bloggen.
Det externa gränssnittet till bloggtjänsten är exakt detsamma för varje språk. För enkelhetens skull kommer MongoDB att användas som datalager för detta exempel eftersom det är det enklaste att ställa in och vi behöver inte oroa oss för scheman alls. I en normal "bloggliknande" applikation skulle troligen en relationsdatabas vara nödvändig.
Lägg till ett inlägg
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
Visa HTML
GET /
Visa JSON
GET /json
[
{
content:"Lorem ipsum dolor sit amet, consectetur ...",
title:"Test Post 1",
_id:{
$oid:"558329927315660001550970"
},
summary:"The First Test Post"
}
]
Applikationsstruktur
Varje applikation kan delas upp i följande komponenter:
Programinställningar
- Initiera ett program
- Kör programmet
Begäran
- Definiera rutter på vilka en användare kan begära data (GET)
- Definiera rutter på vilka en användare kan skicka data (POST)
Svar
- Gör JSON (
GET /json
) - Gör en mall (
GET /
)
Databas
- Initiera en anslutning
- Infoga data
- Hämta data
Applikationsdistribution
- Docker!
Resten av den här artikeln kommer att jämföra var och en av dessa komponenter för varje bibliotek. Syftet är inte att antyda att ett av dessa bibliotek är bättre än det andra - det är att ge en specifik jämförelse mellan de tre verktygen:
- Kolv (Python)
- Sinatra (Ruby)
- Martini (Golang)
Projektinställning
Alla projekt är bootstrappade med docker och docker-compose. Innan vi dyker in i hur varje applikation är stängd under huven kan vi bara använda docker för att få igång var och en på exakt samma sätt - docker-compose up
Seriöst, det är det! Nu finns det en Dockerfile
för varje applikation och en docker-compose.yml
fil som anger vad som händer när du kör kommandot ovan.
Python (flaska) - Dockerfile
FROM python:3.4
ADD . /app
WORKDIR /app
RUN pip install -r requirements.txt
Denna Dockerfile
säger att vi utgår från en basavbildning med Python 3.4 installerat och lägger till vår applikation i /app
katalog och använda pip för att installera våra applikationskrav specificerade i requirements.txt
.
Ruby (sinatra)
FROM ruby:2.2
ADD . /app
WORKDIR /app
RUN bundle install
Denna Dockerfile
säger att vi utgår från en basavbildning med Ruby 2.2 installerad och lägger till vår applikation i /app
katalog och använda bundler för att installera våra applikationskrav specificerade i 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
Denna Dockerfile
säger att vi utgår från en basavbildning med Golang 1.3 installerat och lägger till vår applikation i /go/src/github.com/kpurdon/go-blog
katalogen och få alla våra nödvändiga beroenden med hjälp av go get
kommando.
Initiera/kör en applikation
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
Definiera en rutt (GET/POST)
Python (kolv)
# 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) {
// ...
}
Gör ett JSON-svar
Python (kolv)
Flask tillhandahåller en jsonify()-metod men eftersom tjänsten använder MongoDB används mongodb bson-verktyget.
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
Gör ett HTML-svar (mall)
Python (kolv)
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>
Databasanslutning
Alla applikationer använder mongodb-drivrutinen som är specifik för språket. Miljövariabeln DB_PORT_27017_TCP_ADDR
är IP-adressen för en länkad dockningsbehållare (databasens ip).
Python (kolv)
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()
Infoga data från en POST
Python (kolv)
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
Hämta data
Python (kolv)
posts = db.blog.find()
Ruby (Sinatra)
@posts = client[:posts].find.to_a
Golang (Martini)
var posts []Post
db.C("posts").Find(nil).All(&posts)
Application Deployment (Docker!)
En bra lösning för att distribuera alla dessa applikationer är att använda docker och docker-compose.
Python (kolv)
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
Slutsats
Låt oss avslutningsvis ta en titt på vad jag tror är några kategorier där de presenterade biblioteken skiljer sig från varandra.
Enkelhet
Medan Flask är väldigt lätt och läser tydligt, är Sinatra-appen den enklaste av de tre vid 23 LOC (jämfört med 46 för Flask och 42 för Martini). Av dessa skäl är Sinatra vinnaren i denna kategori. Det bör dock noteras att Sinatras enkelhet beror på mer standard "magi" - t.ex. implicit arbete som sker bakom kulisserna. För nya användare kan detta ofta leda till förvirring.
Här är ett specifikt exempel på "magi" i Sinatra:
params # the "request.form" logic in python is done "magically" behind the scenes in Sinatra.
Och motsvarande kolvkod:
from flask import request
params = {
'title': request.form['title'],
'summary': request.form['summary'],
'content': request.form['content']
}
För nybörjare är det verkligen enklare att programmera Flask och Sinatra, men för en erfaren programmerare som spenderar tid på andra statiskt skrivna språk ger Martini ett ganska förenklat gränssnitt.
Dokumentation
Flask-dokumentationen var den enklaste att söka och mest lättillgänglig. Även om Sinatra och Martini båda är väldokumenterade, var själva dokumentationen inte lika lättillgänglig. Av denna anledning är Flask vinnaren i denna kategori.
Community
Flask är vinnaren i den här kategorin. Ruby-gemenskapen är oftare dogmatisk om att Rails är det enda bra valet om du behöver något mer än en grundläggande tjänst (även om Padrino erbjuder detta ovanpå Sinatra). Golang-gemenskapen är fortfarande inte i närheten av en konsensus om ett (eller till och med några) webbramverk, vilket kan förväntas eftersom språket i sig är så ungt. Python har dock anammat ett antal tillvägagångssätt för webbutveckling, inklusive Django för färdiga webbapplikationer med alla funktioner och Flask, Bottle, CheryPy och Tornado för ett mikroramverk.
Slutlig avgörande
Observera att poängen med den här artikeln inte var att marknadsföra ett enda verktyg, snarare att tillhandahålla en opartisk jämförelse av Flask, Sinatra och Martini. Med det sagt skulle jag välja Flask (Python) eller Sinatra (Ruby). Om du kommer från ett språk som C eller Java kanske den statiskt skrivna naturen hos Golang tilltalar dig. Om du är nybörjare kan Flask vara det bästa valet eftersom det är väldigt lätt att komma igång och det finns väldigt lite standard "magi". Min rekommendation är att du är flexibel i dina beslut när du väljer bibliotek för ditt projekt.
Frågor? Respons? Kommentera gärna nedan. Tack!
Meddela oss också om du är intresserad av att se några riktmärken.