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

Python, Ruby e Golang:un confronto tra applicazioni di servizi Web

Dopo un recente confronto tra Python, Ruby e Golang per un'applicazione a riga di comando, ho deciso di utilizzare lo stesso schema per confrontare la creazione di un semplice servizio web. Ho selezionato Flask (Python), Sinatra (Ruby) e Martini (Golang) per questo confronto. Sì, ci sono molte altre opzioni per le librerie di applicazioni web in ogni lingua, ma ho sentito che queste tre si prestano bene al confronto.


Panoramica della libreria

Ecco un confronto di alto livello delle librerie di Stackshare.


Boccetta (Pitone)

Flask è un micro-framework per Python basato su Werkzeug, Jinja2 e buone intenzioni.

Per applicazioni molto semplici, come quella mostrata in questa demo, Flask è un'ottima scelta. L'applicazione di base Flask contiene solo 7 righe di codice (LOC) in un unico file sorgente Python. Il vantaggio di Flask rispetto ad altre librerie Web Python (come Django o Pyramid) è che puoi iniziare in piccolo e creare un'applicazione più complessa secondo necessità.

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()


Sinatra (Rubino)

Sinatra è un DSL per creare rapidamente applicazioni web in Ruby con il minimo sforzo.

Proprio come Flask, Sinatra è ottimo per applicazioni semplici. L'applicazione di base Sinatra è solo 4 LOC in un unico file sorgente Ruby. Sinatra viene utilizzato al posto di librerie come Ruby on Rails per lo stesso motivo di Flask:puoi iniziare in piccolo ed espandere l'applicazione secondo necessità.

require 'sinatra'

get '/hi' do
  "Hello World!"
end


Martini (Golang)

Martini è un potente pacchetto per scrivere rapidamente applicazioni/servizi web modulari in Golang.

Martini viene fornito con alcune batterie in più incluse rispetto a Sinatra e Flask, ma è ancora molto leggero per cominciare - solo 9 LOC per l'applicazione di base. Martini è stato oggetto di alcune critiche da parte della comunità di Golang, ma ha ancora uno dei progetti Github più apprezzati di qualsiasi framework web Golang. L'autore di Martini ha risposto qui direttamente alla critica. Alcuni altri framework includono Revel, Gin e persino la libreria net/http integrata.

package main

import "github.com/go-martini/martini"

func main() {
  m := martini.Classic()
  m.Get("/", func() string {
    return "Hello world!"
  })
  m.Run()
}

Con le basi fuori mano, costruiamo un'app!




Descrizione del servizio

Il servizio creato fornisce un'applicazione blog molto semplice. Vengono costruiti i seguenti percorsi:

  • GET / :Restituisci il blog (usando un modello per il rendering).
  • GET /json :restituisce il contenuto del blog in formato JSON.
  • POST /new :aggiungi un nuovo post (titolo, riepilogo, contenuto) al blog.

L'interfaccia esterna al servizio blog è esattamente la stessa per ogni lingua. Per semplicità MongoDB verrà utilizzato come archivio dati per questo esempio poiché è il più semplice da configurare e non dobbiamo preoccuparci affatto degli schemi. In una normale applicazione "simile a un blog" sarebbe probabilmente necessario un database relazionale.


Aggiungi un post

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


Visualizza l'HTML

GET /



Visualizza il JSON

GET /json

[
   {
      content:"Lorem ipsum dolor sit amet, consectetur ...",
      title:"Test Post 1",
      _id:{
         $oid:"558329927315660001550970"
      },
      summary:"The First Test Post"
   }
]



Struttura dell'applicazione

Ogni applicazione può essere suddivisa nei seguenti componenti:


Configurazione dell'applicazione

  • Inizia un'applicazione
  • Esegui l'applicazione


Richiesta

  • Definire percorsi su cui un utente può richiedere dati (GET)
  • Definire percorsi su cui un utente può inviare dati (POST)


Risposta

  • Renderizza JSON (GET /json )
  • Renderizzare un modello (GET / )


Banca dati

  • Inizia una connessione
  • Inserisci dati
  • Recupera dati


Distribuzione dell'applicazione

  • Docker!

Il resto di questo articolo confronterà ciascuno di questi componenti per ciascuna libreria. Lo scopo non è suggerire che una di queste librerie sia migliore dell'altra, ma fornire un confronto specifico tra i tre strumenti:

  • Boccetta (Pitone)
  • Sinatra (Rubino)
  • Martini (Golang)



Impostazione progetto

Tutti i progetti vengono avviati utilizzando docker e docker-compose. Prima di approfondire il modo in cui ogni applicazione viene avviata sotto il cofano, possiamo semplicemente usare la finestra mobile per farla funzionare esattamente allo stesso modo - docker-compose up

Seriamente, questo è tutto! Ora per ogni applicazione c'è un Dockerfile e un docker-compose.yml file che specifica cosa succede quando esegui il comando precedente.

Python (flask) - File Docker

FROM python:3.4

ADD . /app
WORKDIR /app

RUN pip install -r requirements.txt

Questo Dockerfile dice che stiamo partendo da un'immagine di base con Python 3.4 installato, aggiungendo la nostra applicazione a /app directory e utilizzando pip per installare i requisiti dell'applicazione specificati in requirements.txt .

Rubino (sinatra)

FROM ruby:2.2

ADD . /app
WORKDIR /app

RUN bundle install

Questo Dockerfile dice che stiamo partendo da un'immagine di base con Ruby 2.2 installato, aggiungendo la nostra applicazione a /app directory e utilizzando bundler per installare i requisiti dell'applicazione specificati nel 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

Questo Dockerfile dice che stiamo partendo da un'immagine di base con Golang 1.3 installato, aggiungendo la nostra applicazione al /go/src/github.com/kpurdon/go-blog directory e ottenere tutte le nostre dipendenze necessarie utilizzando go get comando.



Inizializza/Esegui un'applicazione

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

Rubino (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


Definisci un percorso (GET/POST)

Python (boccetta)

# get
@app.route('/')  # the default is GET only
def blog():
    # ...

#post
@app.route('/new', methods=['POST'])
def new():
    # ...

Rubino (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) {
  // ...
}


Esegui il rendering di una risposta JSON

Python (boccetta)

Flask fornisce un metodo jsonify() ma poiché il servizio utilizza MongoDB viene utilizzata l'utilità mongodb bson.

from bson.json_util import dumps
return dumps(posts) # posts is a list of dicts [{}, {}]

Rubino (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


Renderizzare una risposta HTML (modello)

Python (boccetta)

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>

Rubino (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>


Connessione al database

Tutte le applicazioni utilizzano il driver mongodb specifico per la lingua. La variabile di ambiente DB_PORT_27017_TCP_ADDR è l'IP di un contenitore Docker collegato (l'IP del database).

Python (boccetta)

from pymongo import MongoClient
client = MongoClient(os.environ['DB_PORT_27017_TCP_ADDR'], 27017)
db = client.blog

Rubino (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()


Inserisci dati da un POST

Python (boccetta)

from flask import request
post = {
    'title': request.form['title'],
    'summary': request.form['summary'],
    'content': request.form['content']
}
db.blog.insert_one(post)

Rubino (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


Recupera dati

Python (boccetta)

posts = db.blog.find()

Rubino (Sinatra)

@posts = client[:posts].find.to_a

Golang (Martini)

var posts []Post
db.C("posts").Find(nil).All(&posts)


Distribuzione dell'applicazione (Docker!)

Un'ottima soluzione per distribuire tutte queste applicazioni consiste nell'usare la finestra mobile e la composizione mobile.

Python (boccetta)

File Docker

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

Rubino (Sinatra)

File Docker

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)

File Docker

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


Conclusione

Per concludere, diamo un'occhiata a quelle che ritengo siano alcune categorie in cui le biblioteche presentate si separano l'una dall'altra.


Semplicità

Mentre Flask è molto leggero e si legge chiaramente, l'app Sinatra è la più semplice delle tre a 23 LOC (rispetto a 46 per Flask e 42 per Martini). Per questi motivi Sinatra è il vincitore in questa categoria. Va notato, tuttavia, che la semplicità di Sinatra è dovuta a una "magia" più predefinita, ad esempio il lavoro implicito che avviene dietro le quinte. Per i nuovi utenti questo può spesso creare confusione.

Ecco un esempio specifico di "magia" in Sinatra:

params # the "request.form" logic in python is done "magically" behind the scenes in Sinatra.

E il codice Flask equivalente:

from flask import request
params = {
    'title': request.form['title'],
    'summary': request.form['summary'],
    'content': request.form['content']
}

Per i principianti la programmazione di Flask e Sinatra è certamente più semplice, ma per un programmatore esperto con tempo speso in altri linguaggi tipizzati staticamente Martini fornisce un'interfaccia abbastanza semplicistica.



Documentazione

La documentazione di Flask era la più semplice da cercare e la più accessibile. Sebbene Sinatra e Martini siano entrambi ben documentati, la documentazione stessa non era così accessibile. Per questo Flask è il vincitore in questa categoria.



Comunità

Flask è il vincitore senza dubbio in questa categoria. La comunità di Ruby è il più delle volte dogmatica sul fatto che Rails sia l'unica buona scelta se hai bisogno di qualcosa di più di un servizio di base (anche se Padrino offre questo oltre a Sinatra). La comunità di Golang non è ancora lontana dal consenso su uno (o anche solo pochi) framework web, il che è prevedibile dato che la lingua stessa è così giovane. Python, tuttavia, ha adottato una serie di approcci allo sviluppo web, tra cui Django per applicazioni web complete e pronte all'uso e Flask, Bottle, CheryPy e Tornado per un approccio micro-framework.




Determinazione finale

Si noti che lo scopo di questo articolo non era promuovere un singolo strumento, ma fornire un confronto imparziale tra Flask, Sinatra e Martini. Detto questo, sceglierei Flask (Python) o Sinatra (Ruby). Se provieni da un linguaggio come C o Java, forse la natura staticamente tipizzata di Golang potrebbe attirarti. Se sei un principiante, Flask potrebbe essere la scelta migliore in quanto è molto facile da usare e c'è pochissima "magia" predefinita. Il mio consiglio è di essere flessibile nelle decisioni quando selezioni una libreria per il tuo progetto.

Domande? Risposta? Si prega di commentare di seguito. Grazie!

Inoltre, facci sapere se sei interessato a vedere alcuni benchmark.