Nach einem kürzlichen Vergleich von Python, Ruby und Golang für eine Befehlszeilenanwendung habe ich mich entschieden, dasselbe Muster zu verwenden, um den Aufbau eines einfachen Webdienstes zu vergleichen. Ich habe Flask (Python), Sinatra (Ruby) und Martini (Golang) für diesen Vergleich ausgewählt. Ja, es gibt viele andere Optionen für Webanwendungsbibliotheken in jeder Sprache, aber ich fand, dass diese drei sich gut zum Vergleich eignen.
Bibliotheksübersichten
Hier ist ein High-Level-Vergleich der Bibliotheken von Stackshare.
Kolben (Python)
Flask ist ein Mikro-Framework für Python, das auf Werkzeug, Jinja2 und guten Absichten basiert.
Für sehr einfache Anwendungen, wie sie in dieser Demo gezeigt werden, ist Flask eine gute Wahl. Die grundlegende Flask-Anwendung besteht aus nur 7 Codezeilen (LOC) in einer einzigen Python-Quelldatei. Der Vorteil von Flask gegenüber anderen Python-Webbibliotheken (wie Django oder Pyramid) besteht darin, dass Sie klein anfangen und je nach Bedarf zu einer komplexeren Anwendung aufbauen können.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
Sinatra (Rubin)
Sinatra ist eine DSL zum schnellen Erstellen von Webanwendungen in Ruby mit minimalem Aufwand.
Genau wie Flask eignet sich Sinatra hervorragend für einfache Anwendungen. Die grundlegende Sinatra-Anwendung ist nur 4 LOC in einer einzigen Ruby-Quelldatei. Sinatra wird aus dem gleichen Grund wie Flask anstelle von Bibliotheken wie Ruby on Rails verwendet - Sie können klein anfangen und die Anwendung nach Bedarf erweitern.
require 'sinatra'
get '/hi' do
"Hello World!"
end
Martini (Golang)
Martini ist ein leistungsstarkes Paket zum schnellen Schreiben modularer Webanwendungen/-dienste in Golang.
Martini wird mit ein paar mehr Batterien geliefert als Sinatra und Flask, ist aber zunächst immer noch sehr leicht - nur 9 LOC für die grundlegende Anwendung. Martini wurde von der Golang-Community kritisiert, hat aber immer noch eines der am höchsten bewerteten Github-Projekte aller Golang-Webframeworks. Der Autor von Martini hat hier direkt auf die Kritik reagiert. Einige andere Frameworks umfassen Revel, Gin und sogar die eingebaute net/http-Bibliothek.
package main
import "github.com/go-martini/martini"
func main() {
m := martini.Classic()
m.Get("/", func() string {
return "Hello world!"
})
m.Run()
}
Lassen Sie uns mit den Grundlagen eine App erstellen!
Dienstbeschreibung
Der erstellte Dienst bietet eine sehr einfache Blog-Anwendung. Die folgenden Routen werden konstruiert:
GET /
:Gibt den Blog zurück (unter Verwendung einer Vorlage zum Rendern).GET /json
:Gibt den Blog-Inhalt im JSON-Format zurück.POST /new
:Einen neuen Beitrag (Titel, Zusammenfassung, Inhalt) zum Blog hinzufügen.
Die externe Schnittstelle zum Blog-Dienst ist für jede Sprache genau gleich. Der Einfachheit halber wird MongoDB als Datenspeicher für dieses Beispiel verwendet, da es am einfachsten einzurichten ist und wir uns überhaupt nicht um Schemata kümmern müssen. In einer normalen „blogähnlichen“ Anwendung wäre wahrscheinlich eine relationale Datenbank erforderlich.
Beitrag hinzufügen
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 anzeigen
GET /
JSON anzeigen
GET /json
[
{
content:"Lorem ipsum dolor sit amet, consectetur ...",
title:"Test Post 1",
_id:{
$oid:"558329927315660001550970"
},
summary:"The First Test Post"
}
]
Anwendungsstruktur
Jede Anwendung kann in die folgenden Komponenten unterteilt werden:
Anwendungs-Setup
- Eine Anwendung initialisieren
- Führen Sie die Anwendung aus
Anfrage
- Definieren Sie Routen, auf denen ein Benutzer Daten anfordern kann (GET)
- Definieren Sie Routen, auf denen ein Benutzer Daten übermitteln kann (POST)
Antwort
- JSON rendern (
GET /json
) - Rendern Sie eine Vorlage (
GET /
)
Datenbank
- Eine Verbindung initialisieren
- Daten einfügen
- Daten abrufen
Anwendungsbereitstellung
- Docker!
Der Rest dieses Artikels vergleicht jede dieser Komponenten für jede Bibliothek. Der Zweck besteht nicht darin, vorzuschlagen, dass eine dieser Bibliotheken besser als die andere ist, sondern es soll ein spezifischer Vergleich zwischen den drei Tools bereitgestellt werden:
- Kolben (Python)
- Sinatra (Rubin)
- Martini (Golang)
Projekteinrichtung
Alle Projekte werden mit docker und docker-compose gebootstrapped. Bevor wir uns damit befassen, wie jede Anwendung unter der Haube gebootet wird, können wir einfach docker verwenden, um sie auf genau die gleiche Weise zum Laufen zu bringen - docker-compose up
Im Ernst, das ist es! Nun gibt es für jede Anwendung ein Dockerfile
und eine docker-compose.yml
Datei, die angibt, was passiert, wenn Sie den obigen Befehl ausführen.
Python (Kolben) - Dockerfile
FROM python:3.4
ADD . /app
WORKDIR /app
RUN pip install -r requirements.txt
Dieses Dockerfile
sagt, dass wir von einem Basis-Image mit installiertem Python 3.4 ausgehen und unsere Anwendung zu /app
hinzufügen Verzeichnis und Verwendung von pip zur Installation unserer Anwendungsanforderungen, die in requirements.txt
angegeben sind .
Rubin (sinatra)
FROM ruby:2.2
ADD . /app
WORKDIR /app
RUN bundle install
Dieses Dockerfile
sagt, dass wir von einem Basis-Image mit installiertem Ruby 2.2 ausgehen und unsere Anwendung zu /app
hinzufügen Verzeichnis und Verwenden von Bundler zum Installieren unserer Anwendungsanforderungen, die im Gemfile
angegeben sind .
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
Dieses Dockerfile
sagt, dass wir von einem Basis-Image mit installiertem Golang 1.3 ausgehen und unsere Anwendung zu /go/src/github.com/kpurdon/go-blog
hinzufügen Verzeichnis und Abrufen aller unserer notwendigen Abhängigkeiten mit go get
Befehl.
Eine Anwendung initialisieren/ausführen
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
Rubin (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
Eine Route definieren (GET/POST)
Python (Kolben)
# get
@app.route('/') # the default is GET only
def blog():
# ...
#post
@app.route('/new', methods=['POST'])
def new():
# ...
Rubin (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) {
// ...
}
Rendere eine JSON-Antwort
Python (Kolben)
Flask stellt eine jsonify()-Methode bereit, aber da der Dienst MongoDB verwendet, wird das Dienstprogramm mongodb bson verwendet.
from bson.json_util import dumps
return dumps(posts) # posts is a list of dicts [{}, {}]
Rubin (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
Rendere eine HTML-Antwort (Templating)
Python (Kolben)
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>
Rubin (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>
Datenbankverbindung
Alle Anwendungen verwenden den für die Sprache spezifischen Mongodb-Treiber. Die Umgebungsvariable DB_PORT_27017_TCP_ADDR
ist die IP eines verknüpften Docker-Containers (die Datenbank-IP).
Python (Kolben)
from pymongo import MongoClient
client = MongoClient(os.environ['DB_PORT_27017_TCP_ADDR'], 27017)
db = client.blog
Rubin (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()
Daten aus einem POST einfügen
Python (Kolben)
from flask import request
post = {
'title': request.form['title'],
'summary': request.form['summary'],
'content': request.form['content']
}
db.blog.insert_one(post)
Rubin (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
Daten abrufen
Python (Kolben)
posts = db.blog.find()
Rubin (Sinatra)
@posts = client[:posts].find.to_a
Golang (Martini)
var posts []Post
db.C("posts").Find(nil).All(&posts)
Anwendungsbereitstellung (Docker!)
Eine großartige Lösung für die Bereitstellung all dieser Anwendungen ist die Verwendung von docker und docker-compose.
Python (Kolben)
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
Rubin (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
Schlussfolgerung
Werfen wir zum Abschluss einen Blick auf einige meiner Meinung nach einige Kategorien, in denen sich die vorgestellten Bibliotheken voneinander unterscheiden.
Einfachheit
Während Flask sehr leicht ist und sich gut lesen lässt, ist die Sinatra-App mit 23 LOC die einfachste der drei (im Vergleich zu 46 für Flask und 42 für Martini). Aus diesen Gründen ist Sinatra der Gewinner in dieser Kategorie. Es sollte jedoch beachtet werden, dass Sinatras Einfachheit auf eher standardmäßige „Magie“ zurückzuführen ist – z. B. implizite Arbeit, die hinter den Kulissen stattfindet. Bei neuen Benutzern kann dies oft zu Verwirrung führen.
Hier ist ein konkretes Beispiel für „Magie“ in Sinatra:
params # the "request.form" logic in python is done "magically" behind the scenes in Sinatra.
Und der entsprechende Flask-Code:
from flask import request
params = {
'title': request.form['title'],
'summary': request.form['summary'],
'content': request.form['content']
}
Für Programmieranfänger sind Flask und Sinatra sicherlich einfacher, aber für einen erfahrenen Programmierer, der Zeit mit anderen statisch typisierten Sprachen verbracht hat, bietet Martini eine ziemlich einfache Schnittstelle.
Dokumentation
Die Flask-Dokumentation war am einfachsten zu durchsuchen und am zugänglichsten. Während Sinatra und Martini beide gut dokumentiert sind, war die Dokumentation selbst nicht so zugänglich. Aus diesem Grund ist Flask der Gewinner in dieser Kategorie.
Gemeinschaft
Flask ist der Gewinner in dieser Kategorie. Die Ruby-Community ist oft dogmatisch, dass Rails die einzig gute Wahl ist, wenn Sie mehr als einen Basisservice benötigen (obwohl Padrino dies zusätzlich zu Sinatra anbietet). Die Golang-Community ist sich noch lange nicht einig über ein (oder sogar einige wenige) Web-Frameworks, was zu erwarten ist, da die Sprache selbst so jung ist. Python hat jedoch eine Reihe von Ansätzen für die Webentwicklung übernommen, darunter Django für sofort einsatzbereite Webanwendungen mit vollem Funktionsumfang und Flask, Bottle, CheryPy und Tornado für einen Mikro-Framework-Ansatz.
Endgültige Feststellung
Beachten Sie, dass der Zweck dieses Artikels nicht darin bestand, ein einzelnes Tool zu bewerben, sondern einen unvoreingenommenen Vergleich von Flask, Sinatra und Martini zu bieten. Vor diesem Hintergrund würde ich Flask (Python) oder Sinatra (Ruby) auswählen. Wenn Sie aus einer Sprache wie C oder Java kommen, wird Ihnen vielleicht die statisch typisierte Natur von Golang gefallen. Wenn Sie ein Anfänger sind, ist Flask möglicherweise die beste Wahl, da es sehr einfach zum Laufen zu bringen ist und es nur sehr wenig Standard-„Magie“ gibt. Meine Empfehlung ist, dass Sie flexibel in Ihren Entscheidungen sind, wenn Sie eine Bibliothek für Ihr Projekt auswählen.
Fragen? Rückmeldung? Bitte kommentieren Sie unten. Vielen Dank!
Teilen Sie uns auch mit, wenn Sie an einigen Benchmarks interessiert sind.