Go aka golang wurde hauptsächlich entwickelt, um Concurrency optimal zu unterstüzten. Daher bietet sich an, eine Web API mit Go zu entwickeln. Dieser Beitrag zeigt, was dafür notwendig ist und dass dafür nicht sonderlich viel Aufwand eingesetzt werden muss.
Folgendes möchte der Beitrag zeigen:
- Web API mit versionierter API-Entstelle, JSON wird als Datenformat verwendet; Die Web API kann eine Liste von Kontakten zurückgeben, sowie neue Kontakte erstellen
- Interaktion mit einer MySQL-Datenbank
Notwendiges Vorwissen
- Grundlagen Go / Umgebung aufsetzen und ausführen können, Verwendung von Paketen und Modulen
- Grundlagen Web APIs
- JSON
- MySQL Schema und Tabelle anlegen können
Tabelle erzeugen
Für dieses Beispiel wird eine Tabelle benötigt, dies ist mit folgendem Script anzulegen:
CREATE TABLE `contact` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`firstName` varchar(50) DEFAULT NULL,
`lastName` varchar(50) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
Im Beispiel wird von einem existierenden Schema contacts
ausgegangen.
Verwendete Packages
Mit dem Paket net/http
, welches mit Go ausgeliefert wird, können Routen grundsätzlich implementiert werden. Wer es etwas komfortabler haben möchte, verwendet github.com/gorilla/mux
. Dadurch kann beispielsweise einfacher mit HTTP-Verbs gearbeitet werden.
Für den Zugriff auf MySQL wird github.com/go-sql-driver/mysql
eingesetzt. Dies setzt auf das bereits enthaltene Paket database/sql
auf.
Zugriff auf MySQL
Mittels db.go
wird die Initialisierung der Datenbank zur Verfügung gestellt:
package models
import (
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
func InitializeDatabase(connectionString string) {
log.Println("Initializing database ...")
var err error
db, err = sql.Open("mysql", connectionString)
if err != nil {
log.Panic(err)
}
if err = db.Ping(); err != nil {
log.Panic(err)
}
}
In contacts.go
wird sowohl der Typ Contact
zur Verfügung gestellt, als auch die Funktionen für den Umgang mit der Datenbank:
package models
import "log"
type Contact struct {
Id int `json:"id"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Email string `json:"email"`
}
func AllContacts() ([]*Contact, error) {
rows, err := db.Query("SELECT id, firstName, lastName, email FROM contact")
if err != nil {
return nil, err
}
log.Println("Successfully loaded contacts from database")
contacts := make([]*Contact, 0)
for rows.Next() {
contact := new(Contact)
err := rows.Scan(&contact.Id, &contact.FirstName, &contact.LastName, &contact.Email)
if err != nil {
return nil, err
}
contacts = append(contacts, contact)
}
if err = rows.Err(); err != nil {
return nil, err
}
defer rows.Close()
return contacts, nil
}
func InsertContact(contact *Contact) (error){
result, err := db.Exec("INSERT INTO contact (firstName, lastName, email) VALUES (?,?,?)", contact.FirstName, contact.LastName, contact.Email)
if err != nil {
return err
}
id, _ := result.LastInsertId()
contact.Id = int(id)
return nil
}
Verarbeitung der API-Calls
Da nun der Datenbank-Zugriff geregelt ist, wenden wir uns der Kommunikation über HTTP zu. Es muss entsprechende Funktionen für die Rückgabe der bereits vorhandenen Kontakte, sowie zur Anlage geben.
package controller
import (
"net/http"
"sampleWebApi/models"
"encoding/json"
"log"
)
func AllContacts(w http.ResponseWriter, r *http.Request) {
log.Println("Endpoint AllContacts")
contacts, err := models.AllContacts()
if err != nil {
log.Fatal(err)
http.Error(w, http.StatusText(500), 500)
return
}
json.NewEncoder(w).Encode(contacts)
log.Println("Sent all contacts")
}
func InsertContact(w http.ResponseWriter, r *http.Request) {
log.Println("Endpoint InsertContact")
var c models.Contact
if r.Body == nil {
http.Error(w, "Please send any body", http.StatusBadRequest)
return
}
err := json.NewDecoder(r.Body).Decode(&c)
if err != nil {
log.Fatal(err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = models.InsertContact(&c)
if err != nil {
log.Fatal(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.WriteHeader(http.StatusOK)
log.Println("Inserted new contact")
}
Routing und Verbinden der Teile
In der main.go
werden nun die Einzelteile miteinander verbunden. So erfolgt im ersten Schritt die Initialisierung der Datenbank, danach wird das Routing (und somit die möglichen API-Endpunkte) konfiguriert:
package main
import (
"net/http"
"log"
"github.com/gorilla/mux" // specify used verbs
"sampleWebApi/models"
"sampleWebApi/controller"
)
func defaultPage(w http.ResponseWriter, r *http.Request) {
log.Println(w, "Endpoint hit")
}
func handleRequests() {
log.Println("Handling requests ...")
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/", defaultPage)
router.HandleFunc("/api/1/contacts", controller.AllContacts).Methods("GET")
router.HandleFunc("/api/1/contacts", controller.InsertContact).Methods("POST")
log.Fatal(http.ListenAndServe(":8081", router))
}
func main() {
models.InitializeDatabase("user:pass@tcp(127.0.0.1:3306)/contacts")
handleRequests()
}
Download
Dieses Beispiel steht auf GitHub zur Verfügung und kann frei verwendet werden. Entwickelt und getestet wurde es mit Go 1.10.1. Docker-Support inklusive.
Happy coding!