sql >> Base de Datos >  >> RDS >> Database

Python, Ruby y Golang:una comparación de aplicaciones de servicios web

Después de una comparación reciente de Python, Ruby y Golang para una aplicación de línea de comandos, decidí usar el mismo patrón para comparar la creación de un servicio web simple. He seleccionado Flask (Python), Sinatra (Ruby) y Martini (Golang) para esta comparación. Sí, hay muchas otras opciones para bibliotecas de aplicaciones web en cada idioma, pero creo que estas tres se prestan bien a la comparación.


Vistas generales de la biblioteca

Aquí hay una comparación de alto nivel de las bibliotecas de Stackshare.


Frasco (Python)

Flask es un micromarco para Python basado en Werkzeug, Jinja2 y buenas intenciones.

Para aplicaciones muy simples, como la que se muestra en esta demostración, Flask es una excelente opción. La aplicación Flask básica tiene solo 7 líneas de código (LOC) en un solo archivo fuente de Python. El atractivo de Flask sobre otras bibliotecas web de Python (como Django o Pyramid) es que puede comenzar poco a poco y desarrollar una aplicación más compleja según sea necesario.

from flask import Flask
app = Flask(__name__)

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

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


Sinatra (Rubí)

Sinatra es un DSL para crear rápidamente aplicaciones web en Ruby con un mínimo esfuerzo.

Al igual que Flask, Sinatra es excelente para aplicaciones simples. La aplicación básica de Sinatra es solo 4 LOC en un solo archivo fuente de Ruby. Sinatra se usa en lugar de bibliotecas como Ruby on Rails por la misma razón que Flask:puede comenzar poco a poco y expandir la aplicación según sea necesario.

require 'sinatra'

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


Martini (Golang)

Martini es un poderoso paquete para escribir rápidamente servicios/aplicaciones web modulares en Golang.

Martini viene con algunas baterías más incluidas que Sinatra y Flask, pero sigue siendo muy liviano para empezar:solo 9 LOC para la aplicación básica. Martini ha sido objeto de algunas críticas por parte de la comunidad de Golang, pero aún tiene uno de los proyectos de Github mejor calificados de cualquier marco web de Golang. El autor de Martini respondió directamente a las críticas aquí. Algunos otros marcos incluyen Revel, Gin e incluso la biblioteca net/http incorporada.

package main

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

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

Con los conceptos básicos fuera del camino, ¡construyamos una aplicación!




Descripción del servicio

El servicio creado proporciona una aplicación de blog muy básica. Se construyen las siguientes rutas:

  • GET / :Devolver el blog (usando una plantilla para renderizar).
  • GET /json :Devuelve el contenido del blog en formato JSON.
  • POST /new :agregue una nueva publicación (título, resumen, contenido) al blog.

La interfaz externa del servicio de blog es exactamente la misma para cada idioma. Para simplificar, se usará MongoDB como almacén de datos para este ejemplo, ya que es el más simple de configurar y no necesitamos preocuparnos por los esquemas en absoluto. En una aplicación normal "similar a un blog", probablemente sería necesaria una base de datos relacional.


Añadir una publicación

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


Ver el HTML

GET /



Ver el JSON

GET /json

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



Estructura de la aplicación

Cada aplicación se puede dividir en los siguientes componentes:


Configuración de la aplicación

  • Inicializar una aplicación
  • Ejecutar la aplicación


Solicitud

  • Definir rutas en las que un usuario puede solicitar datos (GET)
  • Definir rutas en las que un usuario puede enviar datos (POST)


Respuesta

  • Renderizar JSON (GET /json )
  • Renderizar una plantilla (GET / )


Base de datos

  • Inicializar una conexión
  • Insertar datos
  • Recuperar datos


Despliegue de aplicaciones

  • Docker!

El resto de este artículo comparará cada uno de estos componentes para cada biblioteca. El propósito no es sugerir que una de estas bibliotecas sea mejor que la otra, sino proporcionar una comparación específica entre las tres herramientas:

  • Frasco (Python)
  • Sinatra (Rubí)
  • Martini (Golang)



Configuración del proyecto

Todos los proyectos se inician utilizando docker y docker-compose. Antes de profundizar en cómo se arranca cada aplicación bajo el capó, podemos usar la ventana acoplable para ponerlas en funcionamiento exactamente de la misma manera:docker-compose up

¡En serio, eso es todo! Ahora para cada aplicación hay un Dockerfile y un docker-compose.yml archivo que especifica lo que sucede cuando ejecuta el comando anterior.

Python (frasco) - Dockerfile

FROM python:3.4

ADD . /app
WORKDIR /app

RUN pip install -r requirements.txt

Este Dockerfile dice que estamos comenzando desde una imagen base con Python 3.4 instalado, agregando nuestra aplicación a /app directorio y usando pip para instalar los requisitos de nuestra aplicación especificados en requirements.txt .

Rubí (sinatra)

FROM ruby:2.2

ADD . /app
WORKDIR /app

RUN bundle install

Este Dockerfile dice que estamos comenzando desde una imagen base con Ruby 2.2 instalado, agregando nuestra aplicación a /app directorio y usando el paquete para instalar los requisitos de nuestra aplicación especificados en el 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

Este Dockerfile dice que estamos comenzando desde una imagen base con Golang 1.3 instalado, agregando nuestra aplicación a /go/src/github.com/kpurdon/go-blog directorio y obteniendo todas nuestras dependencias necesarias usando el go get comando.



Inicializar/Ejecutar una aplicación

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

Rubí (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


Definir una ruta (GET/POST)

Python (frasco)

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

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

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


Procesar una respuesta JSON

Python (frasco)

Flask proporciona un método jsonify() pero como el servicio usa MongoDB, se usa la utilidad mongodb bson.

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

Rubí (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


Representar una respuesta HTML (plantillas)

Python (frasco)

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>

Rubí (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>


Conexión de base de datos

Todas las aplicaciones utilizan el controlador mongodb específico para el idioma. La variable de entorno DB_PORT_27017_TCP_ADDR es la IP de un contenedor docker vinculado (la ip de la base de datos).

Python (frasco)

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

Rubí (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()


Insertar datos desde un POST

Python (frasco)

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

Rubí (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


Recuperar datos

Python (frasco)

posts = db.blog.find()

Rubí (Sinatra)

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

Golang (Martini)

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


Despliegue de aplicaciones (Docker!)

Una excelente solución para implementar todas estas aplicaciones es usar docker y docker-compose.

Python (frasco)

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

Rubí (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


Conclusión

Para concluir, echemos un vistazo a lo que creo que son algunas categorías en las que las bibliotecas presentadas se separan entre sí.


Simplicidad

Si bien Flask es muy liviano y se lee con claridad, la aplicación Sinatra es la más simple de las tres con 23 LOC (en comparación con 46 para Flask y 42 para Martini). Por estas razones Sinatra es el ganador en esta categoría. Sin embargo, debe tenerse en cuenta que la simplicidad de Sinatra se debe a una "magia" más predeterminada, por ejemplo, el trabajo implícito que sucede detrás de escena. Para los nuevos usuarios, esto a menudo puede generar confusión.

Aquí hay un ejemplo específico de "magia" en Sinatra:

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

Y el código Flask equivalente:

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

Para los principiantes en la programación, Flask y Sinatra son ciertamente más simples, pero para un programador experimentado con tiempo dedicado a otros lenguajes de escritura estática, Martini proporciona una interfaz bastante simple.



Documentación

La documentación de Flask fue la más sencilla de buscar y la más accesible. Si bien Sinatra y Martini están bien documentados, la documentación en sí no era tan accesible. Por esta razón, Flask es el ganador en esta categoría.



Comunidad

Flask es el ganador indiscutiblemente en esta categoría. La comunidad de Ruby suele ser dogmática acerca de que Rails es la única buena opción si necesita algo más que un servicio básico (aunque Padrino ofrece esto además de Sinatra). La comunidad de Golang aún no está cerca de un consenso sobre uno (o incluso algunos) marcos web, lo cual es de esperar ya que el lenguaje en sí es muy joven. Python, sin embargo, ha adoptado una serie de enfoques para el desarrollo web, incluido Django para aplicaciones web listas para usar con todas las funciones y Flask, Bottle, CheryPy y Tornado para un enfoque de micromarco.




Determinación definitiva

Tenga en cuenta que el objetivo de este artículo no era promover una sola herramienta, sino proporcionar una comparación imparcial de Flask, Sinatra y Martini. Dicho esto, seleccionaría Flask (Python) o Sinatra (Ruby). Si proviene de un lenguaje como C o Java, quizás la naturaleza de tipo estático de Golang pueda resultarle atractiva. Si es un principiante, Flask podría ser la mejor opción, ya que es muy fácil de poner en marcha y hay muy poca "magia" predeterminada. Mi recomendación es que seas flexible en tus decisiones a la hora de seleccionar una biblioteca para tu proyecto.

¿Preguntas? ¿Comentario? Por favor comente abajo. ¡Gracias!

Además, infórmenos si le interesaría ver algunos puntos de referencia.