Database
 sql >> Datenbank >  >> RDS >> Database

Python, Ruby und Golang:Ein Vergleich von Webdienstanwendungen

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.