Database
 sql >> Base de Dados >  >> RDS >> Database

Python, Ruby e Golang:uma comparação de aplicativos de serviço da Web


Após uma comparação recente de Python, Ruby e Golang para um aplicativo de linha de comando, decidi usar o mesmo padrão para comparar a construção de um serviço da Web simples. Selecionei Flask (Python), Sinatra (Ruby) e Martini (Golang) para esta comparação. Sim, existem muitas outras opções para bibliotecas de aplicativos da Web em cada idioma, mas senti que essas três servem para comparação.

Visão geral da biblioteca


Aqui está uma comparação de alto nível das bibliotecas do Stackshare.

Flask (Python)


Flask é um micro-framework para Python baseado em Werkzeug, Jinja2 e boas intenções.

Para aplicativos muito simples, como o mostrado nesta demonstração, o Flask é uma ótima opção. O aplicativo Flask básico tem apenas 7 linhas de código (LOC) em um único arquivo de origem Python. A vantagem do Flask sobre outras bibliotecas web Python (como Django ou Pyramid) é que você pode começar pequeno e construir uma aplicação mais complexa conforme necessário.
from flask import Flask
app = Flask(__name__)

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

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


Sinatra (Rubi)


Sinatra é uma DSL para criar rapidamente aplicações web em Ruby com o mínimo de esforço.

Assim como o Flask, o Sinatra é ótimo para aplicações simples. A aplicação básica do Sinatra tem apenas 4 LOC em um único arquivo fonte Ruby. Sinatra é usado em vez de bibliotecas como Ruby on Rails pela mesma razão que Flask - você pode começar pequeno e expandir o aplicativo conforme necessário.
require 'sinatra'

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


Martini (Golang)


Martini é um pacote poderoso para escrever rapidamente aplicativos/serviços modulares da Web em Golang.

O Martini vem com mais algumas baterias incluídas do que Sinatra e Flask, mas ainda é muito leve para começar - apenas 9 LOC para a aplicação básica. Martini sofreu algumas críticas da comunidade Golang, mas ainda tem um dos projetos Github mais bem avaliados de qualquer estrutura da web Golang. O autor de Martini respondeu diretamente às críticas aqui. Alguns outros frameworks incluem Revel, Gin e até mesmo a biblioteca interna/http.
package main

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

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

Com o básico fora do caminho, vamos construir um aplicativo!



Descrição do serviço


O serviço criado fornece um aplicativo de blog muito básico. As seguintes rotas são construídas:
  • GET / :retorne o blog (usando um modelo para renderizar).
  • GET /json :retorna o conteúdo do blog no formato JSON.
  • POST /new :adicione uma nova postagem (título, resumo, conteúdo) ao blog.

A interface externa para o serviço de blog é exatamente a mesma para cada idioma. Para simplificar, o MongoDB será usado como armazenamento de dados para este exemplo, pois é o mais simples de configurar e não precisamos nos preocupar com esquemas. Em um aplicativo “tipo blog” normal, um banco de dados relacional provavelmente seria necessário.

Adicionar uma postagem


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


Visualizar o HTML


GET /


Visualizar o JSON


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



Estrutura do aplicativo


Cada aplicativo pode ser dividido nos seguintes componentes:

Configuração do aplicativo

  • Inicializar um aplicativo
  • Execute o aplicativo


Solicitar

  • Defina rotas nas quais um usuário pode solicitar dados (GET)
  • Defina rotas nas quais um usuário pode enviar dados (POST)


Resposta

  • Renderizar JSON (GET /json )
  • Renderizar um modelo (GET / )


Banco de dados

  • Iniciar uma conexão
  • Inserir dados
  • Recuperar dados


Implantação de aplicativo

  • Docker!

O restante deste artigo comparará cada um desses componentes para cada biblioteca. O objetivo não é sugerir que uma dessas bibliotecas seja melhor que a outra - é fornecer uma comparação específica entre as três ferramentas:
  • Flask (Python)
  • Sinatra (Rubi)
  • Martini (Golang)



Configuração do projeto


Todos os projetos são inicializados usando docker e docker-compose. Antes de mergulhar em como cada aplicativo é inicializado, podemos usar o docker para que cada um funcione exatamente da mesma maneira - docker-compose up

Sério, é isso! Agora para cada aplicação existe um Dockerfile e um docker-compose.yml arquivo que especifica o que acontece quando você executa o comando acima.

Python (flask) - Dockerfile
FROM python:3.4

ADD . /app
WORKDIR /app

RUN pip install -r requirements.txt

Este Dockerfile diz que estamos começando a partir de uma imagem base com o Python 3.4 instalado, adicionando nosso aplicativo ao /app diretório e usando pip para instalar nossos requisitos de aplicativo especificados em requirements.txt .

Rubi (sinatra)
FROM ruby:2.2

ADD . /app
WORKDIR /app

RUN bundle install

Este Dockerfile diz que estamos começando a partir de uma imagem base com Ruby 2.2 instalado, adicionando nosso aplicativo ao /app diretório e usando o bundler para instalar nossos requisitos de aplicativo especificados no 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 diz que estamos começando a partir de uma imagem base com o Golang 1.3 instalado, adicionando nosso aplicativo ao /go/src/github.com/kpurdon/go-blog e obtendo todas as dependências necessárias usando o go get comando.


Iniciar/executar um aplicativo


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

Ruby (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 uma rota (GET/POST)


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

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

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


Renderizar uma resposta JSON


Python (frasco)

O Flask fornece um método jsonify(), mas como o serviço está usando o MongoDB, o utilitário mongodb bson é usado.
from bson.json_util import dumps
return dumps(posts) # posts is a list of dicts [{}, {}]

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


Renderizar uma resposta HTML (modelagem)


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>

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


Conexão de banco de dados


Todos os aplicativos estão usando o driver mongodb específico para o idioma. A variável de ambiente DB_PORT_27017_TCP_ADDR é o IP de um contêiner docker vinculado (o IP do banco de dados).

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

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


Inserir dados de um 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)

Rubi (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 dados


Python (frasco)
posts = db.blog.find()

Rubi (Sinatra)
@posts = client[:posts].find.to_a

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


Implantação de aplicativo (Docker!)


Uma ótima solução para implantar todos esses aplicativos é usar o docker e o 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

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


Conclusão


Para concluir vamos dar uma olhada no que acredito serem algumas categorias onde as bibliotecas apresentadas se separam umas das outras.

Simplicidade


Enquanto o Flask é muito leve e lê claramente, o aplicativo Sinatra é o mais simples dos três em 23 LOC (em comparação com 46 para Flask e 42 para Martini). Por estas razões Sinatra é o vencedor nesta categoria. Deve-se notar, no entanto, que a simplicidade de Sinatra se deve a mais “mágica” padrão - por exemplo, trabalho implícito que acontece nos bastidores. Para novos usuários, isso geralmente pode causar confusão.

Aqui está um exemplo específico de “magia” em Sinatra:
params # the "request.form" logic in python is done "magically" behind the scenes in Sinatra.

E o código Flask equivalente:
from flask import request
params = {
    'title': request.form['title'],
    'summary': request.form['summary'],
    'content': request.form['content']
}

Para iniciantes em programação, Flask e Sinatra são certamente mais simples, mas para um programador experiente com tempo gasto em outras linguagens de tipo estático, o Martini fornece uma interface bastante simplista.


Documentação


A documentação do Flask foi a mais simples de pesquisar e mais acessível. Embora Sinatra e Martini estejam bem documentados, a documentação em si não era tão acessível. Por esta razão Flask é o vencedor nesta categoria.


Comunidade


Flask é o vencedor nesta categoria. A comunidade Ruby costuma ser dogmática sobre Rails ser a única boa escolha se você precisar de algo mais do que um serviço básico (mesmo que Padrino ofereça isso em cima do Sinatra). A comunidade Golang ainda está longe de um consenso sobre um (ou mesmo alguns) frameworks da web, o que é esperado, já que a linguagem em si é tão jovem. O Python, no entanto, adotou várias abordagens para o desenvolvimento da Web, incluindo Django para aplicativos da Web prontos para uso e Flask, Bottle, CheryPy e Tornado para uma abordagem de micro-framework.



Determinação Final


Observe que o objetivo deste artigo não era promover uma única ferramenta, mas fornecer uma comparação imparcial de Flask, Sinatra e Martini. Com isso dito, eu selecionaria Flask (Python) ou Sinatra (Ruby). Se você vem de uma linguagem como C ou Java, talvez a natureza estaticamente tipada de Golang possa lhe agradar. Se você é um iniciante, o Flask pode ser a melhor escolha, pois é muito fácil de colocar em funcionamento e há muito pouca “mágica” padrão. Minha recomendação é que você seja flexível em suas decisões ao selecionar uma biblioteca para seu projeto.

Questões? Comentários? Por favor, comente abaixo. Obrigada!

Além disso, informe-nos se estiver interessado em ver alguns comparativos de mercado.