След скорошно сравнение на Python, Ruby и Golang за приложение от команден ред реших да използвам същия модел, за да сравня изграждането на проста уеб услуга. Избрах Flask (Python), Sinatra (Ruby) и Martini (Golang) за това сравнение. Да, има много други опции за библиотеки на уеб приложения на всеки език, но смятам, че тези три са подходящи за сравнение.
Прегледи на библиотеката
Ето сравнение на високо ниво на библиотеките от Stackshare.
Flask (Python)
Flask е микро-рамка за Python, базирана на Werkzeug, Jinja2 и добри намерения.
За много прости приложения, като това, показано в тази демонстрация, Flask е чудесен избор. Основното приложение на Flask е само 7 реда код (LOC) в един изходен файл на Python. Привличането на Flask пред други уеб библиотеки на Python (като Django или Pyramid) е, че можете да започнете с малко и да надградите до по-сложно приложение, ако е необходимо.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
Синатра (Рубин)
Sinatra е DSL за бързо създаване на уеб приложения в Ruby с минимални усилия.
Точно като Flask, Sinatra е чудесен за прости приложения. Основното приложение на Sinatra е само 4 LOC в един изходен файл на Ruby. Sinatra се използва вместо библиотеки като Ruby on Rails по същата причина като Flask – можете да започнете с малко и да разширите приложението според нуждите.
require 'sinatra'
get '/hi' do
"Hello World!"
end
Мартини (Голанг)
Martini е мощен пакет за бързо писане на модулни уеб приложения/услуги на Golang.
Martini се предлага с няколко батерии повече, отколкото както Sinatra, така и Flask, но все още е много лек за начало - само 9 LOC за основното приложение. Martini е подложен на известна критика от общността на Golang, но все още има един от най-високо оценените проекти на Github от всяка уеб рамка на Golang. Авторът на Martini отговори директно на критиките тук. Някои други рамки включват Revel, Gin и дори вградената net/http библиотека.
package main
import "github.com/go-martini/martini"
func main() {
m := martini.Classic()
m.Get("/", func() string {
return "Hello world!"
})
m.Run()
}
Като изключим основите, нека създадем приложение!
Описание на услугата
Създадената услуга предоставя много основно приложение за блог. Изградени са следните маршрути:
GET /
:Върнете блога (с помощта на шаблон за изобразяване).GET /json
:Върнете съдържанието на блога във формат JSON.POST /new
:Добавете нова публикация (заглавие, резюме, съдържание) към блога.
Външният интерфейс към услугата на блога е абсолютно еднакъв за всеки език. За простота MongoDB ще се използва като хранилище на данни за този пример, тъй като е най-простото за настройка и изобщо не е нужно да се притесняваме за схеми. В нормално „подобно на блог“ приложение вероятно ще е необходима релационна база данни.
Добавяне на публикация
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
Преглед на HTML
GET /
Преглед на JSON
GET /json
[
{
content:"Lorem ipsum dolor sit amet, consectetur ...",
title:"Test Post 1",
_id:{
$oid:"558329927315660001550970"
},
summary:"The First Test Post"
}
]
Структура на приложението
Всяко приложение може да бъде разделено на следните компоненти:
Настройка на приложението
- Инициализирайте приложение
- Стартирайте приложението
Заявка
- Определете маршрути, по които потребителят може да иска данни (GET)
- Определете маршрути, по които потребителят може да изпраща данни (POST)
Отговор
- Изобразяване на JSON (
GET /json
) - Изобразете шаблон (
GET /
)
База данни
- Инициализирайте връзка
- Вмъкване на данни
- Извличане на данни
Разгръщане на приложение
- Docker!
В останалата част от тази статия ще се сравни всеки от тези компоненти за всяка библиотека. Целта не е да се подскаже, че една от тези библиотеки е по-добра от другата – тя е да предостави конкретно сравнение между трите инструмента:
- Flask (Python)
- Синатра (Рубин)
- Мартини (Голанг)
Настройка на проекта
Всички проекти се зареждат с помощта на docker и docker-compose. Преди да се потопим в това как всяко приложение се стартира под капака, можем просто да използваме docker, за да стартираме всяко едно и също по същия начин - docker-compose up
Сериозно, това е! Сега за всяко приложение има Dockerfile
и docker-compose.yml
файл, който указва какво се случва, когато изпълните горната команда.
Python (колба) - Dockerfile
FROM python:3.4
ADD . /app
WORKDIR /app
RUN pip install -r requirements.txt
Този Dockerfile
казва, че започваме от основно изображение с инсталиран Python 3.4, добавяйки нашето приложение към /app
директория и използване на pip за инсталиране на изискванията на нашето приложение, посочени в requirements.txt
.
Рубин (синатра)
FROM ruby:2.2
ADD . /app
WORKDIR /app
RUN bundle install
Този Dockerfile
казва, че започваме от основно изображение с инсталиран Ruby 2.2, добавяйки нашето приложение към /app
директория и използвайки bundler за инсталиране на изискванията на нашето приложение, посочени в Gemfile
.
Голанг (мартини)
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
Този Dockerfile
казва, че започваме от основно изображение с инсталиран Golang 1.3, добавяйки нашето приложение към /go/src/github.com/kpurdon/go-blog
директория и получаване на всичките ни необходими зависимости с помощта на go get
команда.
Инициализирайте/изпълнете приложение
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
Руби (Синатра) - app.rb
# initialize application
require 'sinatra'
$ ruby app.rb
Голанг (Мартини) - 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
Дефиниране на маршрут (GET/POST)
Python (Flask)
# get
@app.route('/') # the default is GET only
def blog():
# ...
#post
@app.route('/new', methods=['POST'])
def new():
# ...
Руби (Синатра)
# get
get '/' do
# ...
end
# post
post '/new' do
# ...
end
Голанг (Мартини)
// 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) {
// ...
}
Изобразете JSON отговор
Python (Flask)
Flask предоставя метод jsonify(), но тъй като услугата използва MongoDB, се използва помощната програма mongodb bson.
from bson.json_util import dumps
return dumps(posts) # posts is a list of dicts [{}, {}]
Руби (Синатра)
require 'json'
content_type :json
posts.to_json # posts is an array (from mongodb)
Голанг (Мартини)
r.JSON(200, posts) // posts is an array of Post{} structs
Изобразете HTML отговор (шаблон)
Python (Flask)
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>
Руби (Синатра)
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>
Голанг (Мартини)
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>
Връзка с база данни
Всички приложения използват драйвера mongodb, специфичен за езика. Променливата на средата DB_PORT_27017_TCP_ADDR
е IP на свързан докер контейнер (IP на базата данни).
Python (Flask)
from pymongo import MongoClient
client = MongoClient(os.environ['DB_PORT_27017_TCP_ADDR'], 27017)
db = client.blog
Руби (Синатра)
require 'mongo'
db_ip = [ENV['DB_PORT_27017_TCP_ADDR']]
client = Mongo::Client.new(db_ip, database: 'blog')
Голанг (Мартини)
import "gopkg.in/mgo.v2"
session, _ := mgo.Dial(os.Getenv("DB_PORT_27017_TCP_ADDR"))
db := session.DB("blog")
defer session.Close()
Вмъкване на данни от POST
Python (Flask)
from flask import request
post = {
'title': request.form['title'],
'summary': request.form['summary'],
'content': request.form['content']
}
db.blog.insert_one(post)
Руби (Синатра)
client[:posts].insert_one(params) # params is a hash generated by sinatra
Голанг (Мартини)
db.C("posts").Insert(post) // post is an instance of the Post{} struct
Извличане на данни
Python (Flask)
posts = db.blog.find()
Руби (Синатра)
@posts = client[:posts].find.to_a
Голанг (Мартини)
var posts []Post
db.C("posts").Find(nil).All(&posts)
Разгръщане на приложение (Docker!)
Страхотно решение за внедряването на всички тези приложения е използването на docker и docker-compose.
Python (Flask)
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
Руби (Синатра)
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
Голанг (Мартини)
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
Заключение
В заключение нека да разгледаме това, което според мен са няколко категории, в които представените библиотеки се отделят една от друга.
Простота
Докато Flask е много лек и се чете ясно, приложението Sinatra е най-простото от трите при 23 LOC (в сравнение с 46 за Flask и 42 за Martini). Поради тези причини Синатра е победител в тази категория. Трябва да се отбележи обаче, че простотата на Синатра се дължи на повече „магия“ по подразбиране – например неявна работа, която се случва зад кулисите. За новите потребители това често може да доведе до объркване.
Ето конкретен пример за „магия“ в Синатра:
params # the "request.form" logic in python is done "magically" behind the scenes in Sinatra.
И еквивалентният код на Flask:
from flask import request
params = {
'title': request.form['title'],
'summary': request.form['summary'],
'content': request.form['content']
}
За начинаещи в програмирането Flask и Sinatra със сигурност са по-прости, но за опитен програмист с време, прекарано в други статично въведени езици, Martini предоставя доста опростен интерфейс.
Документация
Документацията на Flask беше най-лесната за търсене и най-достъпната. Докато Синатра и Мартини са добре документирани, самата документация не беше толкова достъпна. Поради тази причина Flask е победител в тази категория.
Общност
Flask е победителят в тази категория. Общността на Ruby често е догматична за това, че Rails е единственият добър избор, ако имате нужда от нещо повече от основна услуга (въпреки че Padrino предлага това на върха на Sinatra). Общността на Golang все още не е близо до консенсус относно една (или дори няколко) уеб рамки, което може да се очаква, тъй като самият език е толкова млад. Python обаче е приел редица подходи към уеб разработката, включително Django за готови напълно функционални уеб приложения и Flask, Bottle, CheryPy и Tornado за подход на микро-рамка.
Окончателно определяне
Имайте предвид, че целта на тази статия не беше да популяризира един инструмент, а по-скоро да предостави безпристрастно сравнение на Flask, Sinatra и Martini. С това казано, бих избрал Flask (Python) или Sinatra (Ruby). Ако идвате от език като C или Java, може би статично въведената природа на Golang може да ви хареса. Ако сте начинаещ, Flask може да е най-добрият избор, тъй като е много лесен за стартиране и работа и има много малко „магия“ по подразбиране. Моята препоръка е да бъдете гъвкави в решенията си, когато избирате библиотека за вашия проект.
Въпроси? Обратна връзка? Моля, коментирайте по-долу. Благодаря ви!
Също така, уведомете ни, ако се интересувате да видите някои сравнителни показатели.