Comment j'ai migré mon site de Django (Python) à Go

Ce blog a tourné sous différents langages en commençant par celui que je maîtrise le plus: PHP (sous le framework Symfony), en passant par Python (sous le framework Django) et tourne désormais sous Go.

Le (très-micro-)framework que j'ai écris pour apprendre

Ce blog tourne donc à présent sous Gofast, un micro-framework que j'ai écris pour apprendre le langage. Son code est disponible en open-source donc vous êtes libre d'aller y jeter un oeil. Il contient du code très simple qui permet de faire tourner des applications web très simples. Le langage Go est livré avec des librairies internes très utiles qui m'ont grandement aidées dans cette migration.

Voici à quoi ressemble un simple "Hello world" rendu avec Gofast :

package main

import (
    "github.com/eko/gofast"
)

func main() {
    g := gofast.Bootstrap()
    router := g.GetRouter()

    router.Get("homepage", "/", func (c gofast.Context) {
        response := c.GetResponse()
        response.Write([]byte("Hello world"))
    })

    g.Handle()
}

Vous pouvez voir ici que nous instancions notre micro-framework et récupérons une structure appelée un context. De cette structure, vous pouvez obtenir le routeur (comme fait dans l'exemple ci-dessus), la requête, la réponse et le templating.

Rendre un template en utilisant la librairie Pongo2

Mon micro-framework Gofast utilise la librairie de templating Pongo2 car elle permet d'utiliser des templates équivalent à ceux de Django (utilisant la syntaxe Jinja). Quasiment toutes les fonctions, filtres et tags sont fonctionnelles (exceptées les fonctions concernant les dates car elles sont assez particulières en Go) donc je n'ai pas eu à réécrire mes templates pour transformer mon site / blog personnel en Go.

Imaginez avoir un template homepage.html, cette librairie vous permet de rendre ce template Jinja simplement en écrivant le code suivant:

var template = pongo2.Must(pongo2.FromFile("homepage.html"))

template.ExecuteWriter(pongo2.Context{
    "request": context.GetRequest(),
    "response": context.GetResponse(),
}, context.GetResponse())

De plus, vous pouvez également passer à vos templates vos structures de requête, réponse et routeurs ce qui vous permettra, par exemple, de récupérer un paramètre de votre requête, par exemple :

{{ request.GetParameter("name") }}

Mapper les tables de votre base de données à des structures Go en utilisant la librairie Goorp

Nous avons maintenant besoin de requêter notre base de données (j'utilise MySQL comme base de données historique) pour obtenir mes articles, tags et autres données que nous allons devoir afficher sur notre site.

J'ai choisi d'utiliser la librairie Gorp pour mapper les champs de mes tables de de base de données à des structures Go.

Commencez par créer vos structures (ici, celle de ma table article):

type Article struct {
    Id      int       `db:"id"`
    Title   string    `db:"title"`
    Content string    `db:"content"`
    Url     string    `db:"url"`
    Date    string    `db:"date"`
}

Ensuite, nous devons mapper cette structure à une table de notre base de données, ce qui peut être fait très facilement en quelques lignes :

import (
    "github.com/coopernurse/gorp"
    _ "github.com/go-sql-driver/mysql"
    "database/sql"
)

db, err := sql.Open("mysql", "(host:port)/dbname")
if err != nil { log.Fatalln("Fail to connect to database", err) }

dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}
dbmap.AddTableWithName(Article{}, "article").SetKeys(true, "id")

Maintenant que notre table de base de données est mappée à notre structure, nous pouvons commencer à reqêter notre table et obtenir un tableau d'objets correspondant à notre structure Article :

var articles []Article
_, err := dbmap.Select(&articles, "SELECT * FROM article")
if err != nil { panic(err) }

Affichage de mon activité Github via l'API JSON

Pour ce faire, nous allons procéder de la même façon, à savoir créer des structures correspondant à la structure de la réponse JSON de l'API de Github et mapper les résultats de l'API dans nos structures Go :

type GithubRepo struct {
    Id   int    `json:"id"`
    Name string `json:"name"`
    Url  string `json:"url"`
}

type GithubActor struct {
    Id         int    `json:"id"`
    Login      string `json:"login"`
    GravatarId string `json:"gravatar_id"`
    Url        string `json:"url"`
    AvatarUrl  string `json:"avatar_url"`
}

type GithubAuthor struct {
    Email string `json:"email"`
    Name  string `json:"name"`
}

type GithubCommit struct {
    Sha      string       `json:"sha"`
    Author   GithubAuthor `json:"author"`
    Message  string       `json:"message"`
    Distinct bool         `json:"distinct"`
    Url      string       `json:"url"`
}

type GithubIssue struct {
    Number int `json:"number"`
}

type GithubPayload struct {
    Ref          string         `json:"ref"`
    Head         string         `json:"head"`
    Commits      []GithubCommit `json:"commits"`
    Issue        GithubIssue    `json:"issue"`
}

type GithubEvent struct {
    Id        string        `json:"id"`
    Type      string        `json:"type"`
    Actor     GithubActor   `json:"actor"`
    Repo      GithubRepo    `json:"repo"`
    Payload   GithubPayload `json:"payload"`
}

Une fois les structures écrites, nous pouvons effectuer l'appel à l'API et mapper les objets :

import (
    "net/http"
    "encoding/json"
)

res, err := http.Get("https://api.github.com/users/eko/events")
if err != nil { panic(err) }

body, err := ioutil.ReadAll(res.Body)

events := make([]GithubEvent,0)
err = json.Unmarshal(body, &events)
if err != nil { panic(err) }

Plutôt simple, non ? La méthode json.Unmarshal() va s'occuper de mapper les données.

Et oui, il s'agit d'un autre obstacle à notre migration qui a été résolu très simplement, grâce aux paquets natifs compris dans Go.

Générer des flux RSS et Atom de mes articles via la librairie feeds

La dernière chose qu'il me restait à faire afin de compléter la migration de mon blog était de créer des flux RSS et Atom de mes derniers articles de blog. J'ai utilisé la librairie feeds qui fait cela très bien.

J'ai simplement besoin de remplir un item avec les données de mes articles, comme suit :

import (
    "github.com/gorilla/feeds"
)

// Retrieve the blog posts
var articles []Article
_, err := dbmap.Select(&articles, "select * from article order by id desc limit 10")
if err != nil { }

// Creates the feed
now  := time.Now()
feed := &feeds.Feed{
    Title:       "Vincent Composieux - Blog",
    Link:        &feeds.Link{Href: "http://vincent.composieux.fr/blog"},
    Description: "A web engineer blog posts",
    Author:      &feeds.Author{"Vincent Composieux", ""},
    Created:     now,
}

var items = []*feeds.Item{}

for _, article := range articles {
    datetime, _ := time.Parse("2006-01-02 15:04:05", article.Date)

    items = append(items, &feeds.Item{
        Title:       article.Title,
        Link:        &feeds.Link{Href: fmt.Sprintf("http://vincent.composieux.fr/article/%s", article.Url)},
        Description: article.Content,
        Author:      &feeds.Author{"Vincent Composieux", ""},
        Created:    datetime
    })
}

feed.Items = items

Nous obtenons donc un objet feed rempli avec un item par article. Il ne nous reste plus qu'à appeler la méthode .ToRss() ou .ToAtom() qui va s'occuper de transformer ces objets en XML et rendre celui-ci via le paquet net/http :

rss, err := feed.ToRss()
if err != nil { panic(err) }

response := c.GetResponse()
response.Header().Set("Content-Type", "text/xml")
response.Write([]byte(rss))

Et voilà, tout mon front-end est maintenant migré en Go, et en plus de ça, j'ai beaucoup appris sur ce langage qui m'était inconnu jusque là.

Quelques autres resources en Go que vous pouvez regarder

J'espère que cet article vous aura paru intéressant. N'hésitez pas à me contacter si vous souhaitez avoir des informations complémentaires ou à commenter sur cet article directement.