sql >> Database >  >> RDS >> Database

Python, Ruby en Golang:een vergelijking van webservicetoepassingen

Na een recente vergelijking van Python, Ruby en Golang voor een opdrachtregeltoepassing, besloot ik hetzelfde patroon te gebruiken om het bouwen van een eenvoudige webservice te vergelijken. Ik heb Flask (Python), Sinatra (Ruby) en Martini (Golang) geselecteerd voor deze vergelijking. Ja, er zijn veel andere opties voor webtoepassingsbibliotheken in elke taal, maar ik vond dat deze drie goed geschikt waren voor vergelijking.


Bibliotheekoverzichten

Hier is een vergelijking op hoog niveau van de bibliotheken door Stackshare.


Koel (Python)

Flask is een micro-framework voor Python gebaseerd op Werkzeug, Jinja2 en goede bedoelingen.

Voor zeer eenvoudige toepassingen, zoals degene die in deze demo wordt getoond, is Flask een goede keuze. De basistoepassing van Flask is slechts 7 regels code (LOC) in een enkel Python-bronbestand. Het voordeel van Flask ten opzichte van andere Python-webbibliotheken (zoals Django of Pyramid) is dat je klein kunt beginnen en zo nodig kunt opbouwen naar een complexere applicatie.

from flask import Flask
app = Flask(__name__)

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

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


Sinatra (Ruby)

Sinatra is een DSL voor het snel creëren van webapplicaties in Ruby met minimale inspanning.

Net als Flask is Sinatra geweldig voor eenvoudige toepassingen. De basistoepassing van Sinatra is slechts 4 LOC in een enkel Ruby-bronbestand. Sinatra wordt om dezelfde reden als Flask gebruikt in plaats van bibliotheken zoals Ruby on Rails - u kunt klein beginnen en de toepassing naar behoefte uitbreiden.

require 'sinatra'

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


Martini (Golang)

Martini is een krachtig pakket om snel modulaire webapplicaties/diensten in Golang te schrijven.

Martini wordt geleverd met een paar extra batterijen dan zowel Sinatra als Flask, maar is nog steeds erg licht om mee te beginnen - slechts 9 LOC voor de basistoepassing. Martini heeft enige kritiek gekregen van de Golang-gemeenschap, maar heeft nog steeds een van de best beoordeelde Github-projecten van elk Golang-webframework. De auteur van Martini reageerde hier direct op de kritiek. Enkele andere frameworks omvatten Revel, Gin en zelfs de ingebouwde net/http-bibliotheek.

package main

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

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

Laten we, met de basis uit de weg, een app bouwen!




Servicebeschrijving

De gecreëerde service biedt een zeer eenvoudige blogtoepassing. De volgende routes zijn aangelegd:

  • GET / :de blog retourneren (met behulp van een sjabloon om weer te geven).
  • GET /json :retourneer de bloginhoud in JSON-indeling.
  • POST /new :voeg een nieuw bericht (titel, samenvatting, inhoud) toe aan de blog.

De externe interface naar de blogservice is voor elke taal precies hetzelfde. Voor de eenvoud zal MongoDB worden gebruikt als de gegevensopslag voor dit voorbeeld, omdat dit de eenvoudigste is om in te stellen en we ons helemaal geen zorgen hoeven te maken over schema's. In een normale "blog-achtige" applicatie zou waarschijnlijk een relationele database nodig zijn.


Een bericht toevoegen

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


Bekijk de HTML

GET /



Bekijk de JSON

GET /json

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



Applicatiestructuur

Elke applicatie kan worden onderverdeeld in de volgende componenten:


Toepassing instellen

  • Een toepassing initialiseren
  • Voer de applicatie uit


Verzoek

  • Definieer routes waarop een gebruiker gegevens kan opvragen (GET)
  • Definieer routes waarop een gebruiker gegevens kan indienen (POST)


Reactie

  • JSON renderen (GET /json )
  • Render een sjabloon (GET / )


Database

  • Een verbinding initialiseren
  • Gegevens invoegen
  • Gegevens ophalen


App-implementatie

  • Dokter!

De rest van dit artikel vergelijkt elk van deze componenten voor elke bibliotheek. Het doel is niet om te suggereren dat een van deze bibliotheken beter is dan de andere - het is om een ​​specifieke vergelijking te geven tussen de drie tools:

  • Kos (Python)
  • Sinatra (Ruby)
  • Martini (Golang)



Projectconfiguratie

Alle projecten worden opgestart met behulp van docker en docker-compose. Voordat we ingaan op hoe elke applicatie onder de motorkap wordt opgestart, kunnen we gewoon Docker gebruiken om ze allemaal op precies dezelfde manier aan de gang te krijgen - docker-compose up

Serieus, dat is het! Nu is er voor elke applicatie een Dockerfile en een docker-compose.yml bestand dat specificeert wat er gebeurt als je de bovenstaande opdracht uitvoert.

Python (kolf) - Dockerfile

FROM python:3.4

ADD . /app
WORKDIR /app

RUN pip install -r requirements.txt

Dit Dockerfile zegt dat we beginnen met een basisafbeelding waarop Python 3.4 is geïnstalleerd en onze applicatie toevoegen aan de /app directory en het gebruik van pip om onze applicatievereisten te installeren die zijn gespecificeerd in requirements.txt .

Ruby (sinatra)

FROM ruby:2.2

ADD . /app
WORKDIR /app

RUN bundle install

Dit Dockerfile zegt dat we beginnen met een basisimage waarop Ruby 2.2 is geïnstalleerd en onze applicatie toevoegen aan de /app directory en het gebruik van bundelaar om onze applicatievereisten te installeren die zijn gespecificeerd in het 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

Dit Dockerfile zegt dat we beginnen met een basisimage met Golang 1.3 geïnstalleerd, en onze applicatie toevoegen aan de /go/src/github.com/kpurdon/go-blog directory en het verkrijgen van al onze noodzakelijke afhankelijkheden met behulp van de go get commando.



Een toepassing initialiseren/uitvoeren

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


Definieer een route (GET/POST)

Python (Flask)

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

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

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


Een JSON-reactie weergeven

Python (Flask)

Flask biedt een jsonify()-methode, maar aangezien de service MongoDB gebruikt, wordt het hulpprogramma mongoDB bson gebruikt.

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

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


Een HTML-antwoord weergeven (template)

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>

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


Databaseverbinding

Alle toepassingen gebruiken het mongodb-stuurprogramma dat specifiek is voor de taal. De omgevingsvariabele DB_PORT_27017_TCP_ADDR is het IP-adres van een gekoppelde docker-container (de database-ip).

Python (Flask)

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

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


Gegevens uit een POST invoegen

Python (Flask)

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

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


Gegevens ophalen

Python (Flask)

posts = db.blog.find()

Ruby (Sinatra)

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

Golang (Martini)

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


Applicatie-implementatie (Docker!)

Een geweldige oplossing voor het implementeren van al deze applicaties is het gebruik van docker en docker-compose.

Python (Flask)

Dockerbestand

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

Ruby (Sinatra)

Dockerbestand

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)

Dockerbestand

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


Conclusie

Laten we tot slot eens kijken naar wat volgens mij een paar categorieën zijn waarin de gepresenteerde bibliotheken zich van elkaar scheiden.


Eenvoud

Hoewel Flask erg licht is en duidelijk leesbaar is, is de Sinatra-app de eenvoudigste van de drie met 23 LOC (vergeleken met 46 voor Flask en 42 voor Martini). Om deze redenen is Sinatra de winnaar in deze categorie. Er moet echter worden opgemerkt dat de eenvoud van Sinatra te wijten is aan meer standaard "magie" - bijvoorbeeld impliciet werk dat achter de schermen gebeurt. Voor nieuwe gebruikers kan dit vaak tot verwarring leiden.

Hier is een specifiek voorbeeld van "magie" in Sinatra:

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

En de equivalente Flask-code:

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

Voor beginners in het programmeren zijn Flask en Sinatra zeker eenvoudiger, maar voor een ervaren programmeur met tijd doorgebracht in andere statisch getypte talen biedt Martini een vrij simplistische interface.



Documentatie

De Flask-documentatie was het eenvoudigst te doorzoeken en het meest benaderbaar. Hoewel Sinatra en Martini beide goed gedocumenteerd zijn, was de documentatie zelf niet zo benaderbaar. Om deze reden is Flask de winnaar in deze categorie.



Gemeenschap

Flask is zonder twijfel de winnaar in deze categorie. De Ruby-gemeenschap is vaak dogmatisch dat Rails de enige goede keuze is als je meer nodig hebt dan een basisservice (hoewel Padrino dit bovenop Sinatra aanbiedt). De Golang-gemeenschap is nog lang niet in de buurt van een consensus over één (of zelfs enkele) webframeworks, wat te verwachten is omdat de taal zelf zo jong is. Python heeft echter een aantal benaderingen van webontwikkeling omarmd, waaronder Django voor kant-en-klare webapplicaties met volledige functionaliteit en Flask, Bottle, CheryPy en Tornado voor een microframework-benadering.




Definitieve bepaling

Merk op dat het punt van dit artikel niet was om een ​​enkel hulpmiddel te promoten, maar om een ​​onbevooroordeelde vergelijking te geven van Flask, Sinatra en Martini. Dat gezegd hebbende, zou ik Flask (Python) of Sinatra (Ruby) selecteren. Als je uit een taal als C of Java komt, kan het statisch getypeerde karakter van Golang je misschien aanspreken. Als je een beginner bent, is Flask misschien wel de beste keuze, omdat het heel gemakkelijk is om aan de slag te gaan en er is heel weinig "magie" standaard. Mijn aanbeveling is dat u flexibel bent in uw beslissingen bij het selecteren van een bibliotheek voor uw project.

Vragen? Feedback? Reageer hieronder. Bedankt!

Laat het ons ook weten als u geïnteresseerd bent in enkele benchmarks.



  1. De kunst van het aggregeren van gegevens in SQL van eenvoudige tot glijdende aggregaties

  2. BIJGEWERKT:Bug veroorzaakt Microsoft Office 365 Build 2105 Breaks Access Applications

  3. Verbinding maken met DB vanaf een Chrome-extensie?

  4. Moet elke externe sleutel van SQL Server een overeenkomende index hebben?