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.