Database
 sql >> Base de données >  >> RDS >> Database

Python, Ruby et Golang :une comparaison d'applications de services Web

Après une récente comparaison de Python, Ruby et Golang pour une application en ligne de commande, j'ai décidé d'utiliser le même modèle pour comparer la création d'un service Web simple. J'ai sélectionné Flask (Python), Sinatra (Ruby) et Martini (Golang) pour cette comparaison. Oui, il existe de nombreuses autres options pour les bibliothèques d'applications Web dans chaque langue, mais j'ai trouvé que ces trois options se prêtaient bien à la comparaison.


Aperçus de la bibliothèque

Voici une comparaison de haut niveau des bibliothèques par Stackshare.


Flacon (Python)

Flask est un micro-framework pour Python basé sur Werkzeug, Jinja2 et les bonnes intentions.

Pour des applications très simples, telles que celle présentée dans cette démo, Flask est un excellent choix. L'application Flask de base ne contient que 7 lignes de code (LOC) dans un seul fichier source Python. L'attrait de Flask par rapport aux autres bibliothèques Web Python (telles que Django ou Pyramid) est que vous pouvez commencer petit et développer une application plus complexe selon vos besoins.

from flask import Flask
app = Flask(__name__)

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

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


Sinatra (Rubis)

Sinatra est un DSL permettant de créer rapidement des applications Web en Ruby avec un minimum d'effort.

Tout comme Flask, Sinatra est idéal pour les applications simples. L'application Sinatra de base ne contient que 4 LOC dans un seul fichier source Ruby. Sinatra est utilisé à la place de bibliothèques telles que Ruby on Rails pour la même raison que Flask - vous pouvez commencer petit et étendre l'application selon vos besoins.

require 'sinatra'

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


Martini (Golang)

Martini est un package puissant permettant d'écrire rapidement des applications/services Web modulaires dans Golang.

Martini est livré avec quelques piles de plus que Sinatra et Flask, mais reste très léger pour commencer - seulement 9 LOC pour l'application de base. Martini a fait l'objet de critiques de la part de la communauté Golang, mais possède toujours l'un des projets Github les mieux notés de tous les frameworks Web Golang. L'auteur de Martini a répondu directement à la critique ici. Certains autres frameworks incluent Revel, Gin et même la bibliothèque intégrée net/http.

package main

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

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

Une fois les bases éliminées, créons une application !




Description des services

Le service créé fournit une application de blog très basique. Les routes suivantes sont construites :

  • GET /  :renvoie le blog (en utilisant un modèle pour le rendu).
  • GET /json :renvoie le contenu du blog au format JSON.
  • POST /new  :Ajouter un nouvel article (titre, résumé, contenu) au blog.

L'interface externe du service de blog est exactement la même pour chaque langue. Pour plus de simplicité, MongoDB sera utilisé comme magasin de données pour cet exemple car il est le plus simple à configurer et nous n'avons pas du tout à nous soucier des schémas. Dans une application normale de type "blog", une base de données relationnelle serait probablement nécessaire.


Ajouter une publication

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


Afficher le HTML

GET /



Afficher le JSON

GET /json

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



Structure de candidature

Chaque application peut être décomposée en composants suivants :


Configuration de l'application

  • Initialiser une application
  • Exécuter l'application


Demande

  • Définir les itinéraires sur lesquels un utilisateur peut demander des données (GET)
  • Définir les itinéraires sur lesquels un utilisateur peut soumettre des données (POST)


Réponse

  • Rendre JSON (GET /json )
  • Afficher un modèle (GET / )


Base de données

  • Initialiser une connexion
  • Insérer des données
  • Récupérer des données


Déploiement d'applications

  • Docker !

Le reste de cet article comparera chacun de ces composants pour chaque bibliothèque. Le but n'est pas de suggérer que l'une de ces bibliothèques est meilleure que l'autre, mais de fournir une comparaison spécifique entre les trois outils :

  • Flacon (Python)
  • Sinatra (rubis)
  • Martini (Golang)



Configuration du projet

Tous les projets sont amorcés à l'aide de docker et de docker-compose. Avant de plonger dans la façon dont chaque application est amorcée sous le capot, nous pouvons simplement utiliser docker pour que chacune soit opérationnelle exactement de la même manière - docker-compose up

Sérieusement, c'est ça ! Maintenant pour chaque application il y a un Dockerfile et un docker-compose.yml fichier qui spécifie ce qui se passe lorsque vous exécutez la commande ci-dessus.

Python (flacon) - Dockerfile

FROM python:3.4

ADD . /app
WORKDIR /app

RUN pip install -r requirements.txt

Ce Dockerfile dit que nous partons d'une image de base avec Python 3.4 installé, en ajoutant notre application au /app répertoire et en utilisant pip pour installer nos exigences d'application spécifiées dans requirements.txt .

Rubis (sinatra)

FROM ruby:2.2

ADD . /app
WORKDIR /app

RUN bundle install

Ce Dockerfile dit que nous partons d'une image de base avec Ruby 2.2 installé, en ajoutant notre application au /app répertoire et en utilisant bundler pour installer nos exigences d'application spécifiées dans le 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

Ce Dockerfile dit que nous partons d'une image de base avec Golang 1.3 installé, en ajoutant notre application au /go/src/github.com/kpurdon/go-blog répertoire et obtenir toutes nos dépendances nécessaires en utilisant le go get commande.



Initialiser/exécuter une application

Python (flacon) - 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

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


Définir un itinéraire (GET/POST)

Python (flacon)

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

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

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


Rendre une réponse JSON

Python (flacon)

Flask fournit une méthode jsonify() mais comme le service utilise MongoDB, l'utilitaire mongodb bson est utilisé.

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

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


Rendre une réponse HTML (modèle)

Python (flacon)

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>

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


Connexion à la base de données

Toutes les applications utilisent le pilote mongodb spécifique au langage. La variable d'environnement DB_PORT_27017_TCP_ADDR est l'IP d'un conteneur Docker lié (l'IP de la base de données).

Python (flacon)

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

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


Insérer des données à partir d'un POST

Python (flacon)

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

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


Récupérer des données

Python (flacon)

posts = db.blog.find()

Rubis (Sinatra)

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

Golang (Martini)

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


Déploiement d'applications (Docker !)

Une excellente solution pour déployer toutes ces applications consiste à utiliser docker et docker-compose.

Python (flacon)

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

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


Conclusion

Pour conclure, jetons un coup d'œil à ce que je pense être quelques catégories où les bibliothèques présentées se séparent les unes des autres.


Simplicité

Alors que Flask est très léger et se lit clairement, l'application Sinatra est la plus simple des trois à 23 LOC (contre 46 pour Flask et 42 pour Martini). Pour ces raisons, Sinatra est le gagnant dans cette catégorie. Il convient de noter cependant que la simplicité de Sinatra est due à plus de "magie" par défaut - par exemple, un travail implicite qui se produit dans les coulisses. Pour les nouveaux utilisateurs, cela peut souvent prêter à confusion.

Voici un exemple spécifique de "magie" à Sinatra :

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

Et le code Flask équivalent :

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

Pour les débutants en programmation, Flask et Sinatra sont certainement plus simples, mais pour un programmeur expérimenté ayant passé du temps dans d'autres langages à typage statique, Martini fournit une interface assez simpliste.



Documents

La documentation Flask était la plus simple à rechercher et la plus accessible. Bien que Sinatra et Martini soient tous deux bien documentés, la documentation elle-même n'était pas aussi accessible. Pour cette raison, Flask est le gagnant dans cette catégorie.



Communauté

Flask est le gagnant haut la main dans cette catégorie. La communauté Ruby est le plus souvent dogmatique sur le fait que Rails est le seul bon choix si vous avez besoin de quelque chose de plus qu'un service de base (même si Padrino l'offre en plus de Sinatra). La communauté Golang est encore loin d'un consensus sur un (ou même quelques) frameworks Web, ce qui est normal car le langage lui-même est si jeune. Python a cependant adopté un certain nombre d'approches de développement Web, notamment Django pour des applications Web complètes prêtes à l'emploi et Flask, Bottle, CheryPy et Tornado pour une approche de micro-framework.




Détermination finale

Notez que le but de cet article n'était pas de promouvoir un seul outil, mais plutôt de fournir une comparaison impartiale de Flask, Sinatra et Martini. Cela dit, je sélectionnerais Flask (Python) ou Sinatra (Ruby). Si vous venez d'un langage comme C ou Java, peut-être que la nature typée statiquement de Golang peut vous intéresser. Si vous êtes un débutant, Flask pourrait être le meilleur choix car il est très facile à démarrer et à utiliser et il y a très peu de "magie" par défaut. Ma recommandation est que vous soyez flexible dans vos décisions lors de la sélection d'une bibliothèque pour votre projet.

Des questions? Retour? Veuillez commenter ci-dessous. Merci !

N'hésitez pas non plus à nous faire savoir si vous aimeriez voir quelques points de repère.