Xây dựng ứng dụng Microservice với Go
Các công nghệ chúng tôi sử dụng trong dự án này là:
- Go
- Goji : Một bộ ghép kênh yêu cầu tối giản và linh hoạt cho Go
- MongoDB : Robo 3T có sẵn dưới dạng IDE
- Postman : Công cụ kiểm tra API
Mục đích của bài viết này;
Sự chuẩn bị;
- Bắt đầu dịch vụ MongoDB
Hãy bắt đầu!
Đầu tiên, chúng ta chuẩn bị model;
models / Page.go
package models
type Page struct {
Title string
Author string
Header string
PageDefinition string
Content string
URI string
}
models / Book.go
package models
type Book struct {
ISBN string `json:"isbn"`
Title string `json:"title"`
Authors []string `json:"authors"`
Price string `json:"price"`
}
Tiếp theo, chúng tôi đang chuẩn bị layer chung;
common / JsonProcesses.go
package common
import (
"fmt"
"net/http"
)
func ErrorWithJSON(w http.ResponseWriter, message string, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
fmt.Fprintf(w, "{message: %q}", message)
}
func ResponseWithJSON(w http.ResponseWriter, json []byte, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
w.Write(json)
}
data/PageData.go
package data
import (
comm "common"
"encoding/json"
"goji.io/pat"
mgo "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"log"
m "models"
"net/http"
) // Bring all pages.
func AllPages(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy() // Create a copy of the session.
// End the session when the process is complete.
defer session.Close()
c := session.DB("store").C("pages")
var pages []m.Page
// MongoDB query : Bring all pages.
err := c.Find(bson.M{}).All(&pages)
if err != nil {
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed get all pages: ", err)
return
}
respBody, err := json.MarshalIndent(pages, "", " ") // Format the data.
if err != nil {
log.Fatal(err)
}
comm.ResponseWithJSON(w, respBody, http.StatusOK)
}
}
func AddPage(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
var page m.Page
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&page)
if err != nil {
comm.ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("pages")
err = c.Insert(page) // MongoDB query : Add data.
if err != nil {
if mgo.IsDup(err) { // Is there a duplicate error? (IsDup)
comm.ErrorWithJSON(w, "This page already exists", http.StatusBadRequest)
return
}
// For any error...
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed insert page: ", err)
return
}
// The output out this process will be JSON.
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Location", r.URL.Path+"/"+page.Title)
// Write header : "Created" status message.
w.WriteHeader(http.StatusCreated)
}
}
func PageByID(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
title := pat.Param(r, "title")
c := session.DB("store").C("pages")
var page m.Page
err := c.Find(bson.M{"title": title}).One(&page)
if err != nil {
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed find page: ", err)
return
}
if page.Title == "" {
comm.ErrorWithJSON(w, "Page not found", http.StatusNotFound)
return
}
respBody, err := json.MarshalIndent(page, "", " ")
if err != nil {
log.Fatal(err)
}
comm.ResponseWithJSON(w, respBody, http.StatusOK)
}
}
func UpdatePage(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
title := pat.Param(r, "title")
var page m.Page
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&page)
if err != nil {
comm.ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("pages")
err = c.Update(bson.M{"title": title}, &page)
if err != nil {
switch err {
default:
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed update page: ", err)
return
case mgo.ErrNotFound:
comm.ErrorWithJSON(w, "Page not found", http.StatusNotFound)
return
}
}
w.WriteHeader(http.StatusNoContent)
}
}
func DeletePage(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
title := pat.Param(r, "title") // Will be deleted with "title" data(So not with ID).
c := session.DB("store").C("pages")
err := c.Remove(bson.M{"title": title})
if err != nil {
switch err {
default:
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed delete page: ", err)
return
case mgo.ErrNotFound:
comm.ErrorWithJSON(w, "Page not found", http.StatusNotFound)
return
}
}
w.WriteHeader(http.StatusNoContent)
}
}
data/BookData.go
package data
import (
"encoding/json"
"log"
"net/http"
"goji.io/pat"
mgo "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
comm "common"
m "models"
)
func AllBooks(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
c := session.DB("store").C("books")
var books []m.Book
err := c.Find(bson.M{}).All(&books)
if err != nil {
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed get all books: ", err)
return
}
respBody, err := json.MarshalIndent(books, "", " ")
if err != nil {
log.Fatal(err)
}
comm.ResponseWithJSON(w, respBody, http.StatusOK)
}
}
func AddBook(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
var book m.Book
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&book)
if err != nil {
comm.ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("books")
err = c.Insert(book)
if err != nil {
if mgo.IsDup(err) {
comm.ErrorWithJSON(w, "Book with this ISBN already exists", http.StatusBadRequest)
return
}
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed insert book: ", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Location", r.URL.Path+"/"+book.ISBN)
w.WriteHeader(http.StatusCreated)
}
}
func BookByISBN(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
isbn := pat.Param(r, "isbn")
c := session.DB("store").C("books")
var book m.Book
err := c.Find(bson.M{"isbn": isbn}).One(&book)
if err != nil {
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed find book: ", err)
return
}
if book.ISBN == "" {
comm.ErrorWithJSON(w, "Book not found", http.StatusNotFound)
return
}
respBody, err := json.MarshalIndent(book, "", " ")
if err != nil {
log.Fatal(err)
}
comm.ResponseWithJSON(w, respBody, http.StatusOK)
}
}
func UpdateBook(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
isbn := pat.Param(r, "isbn")
var book m.Book
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&book)
if err != nil {
comm.ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("books")
err = c.Update(bson.M{"isbn": isbn}, &book)
if err != nil {
switch err {
default:
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed update book: ", err)
return
case mgo.ErrNotFound:
comm.ErrorWithJSON(w, "Book not found", http.StatusNotFound)
return
}
}
w.WriteHeader(http.StatusNoContent)
}
}
func DeleteBook(s *mgo.Session) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
session := s.Copy()
defer session.Close()
isbn := pat.Param(r, "isbn")
c := session.DB("store").C("books")
err := c.Remove(bson.M{"isbn": isbn})
if err != nil {
switch err {
default:
comm.ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed delete book: ", err)
return
case mgo.ErrNotFound:
comm.ErrorWithJSON(w, "Book not found", http.StatusNotFound)
return
}
}
w.WriteHeader(http.StatusNoContent)
}
}
And we complete the project…
main.go
package main
import (
"net/http"
"goji.io"
"goji.io/pat"
"gopkg.in/mgo.v2"
"data"
)
func main() {
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
mux := goji.NewMux()
// Page API
mux.HandleFunc(pat.Get("/pages"), data.AllPages(session))
mux.HandleFunc(pat.Post("/pages"), data.AddPage(session))
mux.HandleFunc(pat.Get("/pages/:title"), data.PageByID(session))
mux.HandleFunc(pat.Put("/pages/:title"), data.UpdatePage(session))
mux.HandleFunc(pat.Delete("/pages/:title"), data.DeletePage(session))
// Book API
mux.HandleFunc(pat.Get("/books"), data.AllBooks(session))
mux.HandleFunc(pat.Post("/books"), data.AddBook(session))
mux.HandleFunc(pat.Get("/books/:isbn"),dt.BookByISBN(session))
mux.HandleFunc(pat.Put("/books/:isbn"), data.UpdateBook(session))
mux.HandleFunc(pat.Delete("/books/:isbn"), data.DeleteBook(session))
http.ListenAndServe("localhost:8080", mux)
}
Tiếp theo chúng ta sẽ hoàn thành project...
main.go
package main
import (
"net/http"
"data"
"goji.io/pat"
)
func main() {
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
mux := goji.NewMux()
// Page API
mux.HandleFunc(pat.Get("/pages"), data.AllPages(session))
mux.HandleFunc(pat.Post("/pages"), data.AddPage(session))
mux.HandleFunc(pat.Get("/pages/:title"), data.PageByID(session))
mux.HandleFunc(pat.Put("/pages/:title"), data.UpdatePage(session))
mux.HandleFunc(pat.Delete("/pages/:title"), data.DeletePage(session))
// Book API
mux.HandleFunc(pat.Get("/books"), data.AllBooks(session))
mux.HandleFunc(pat.Post("/books"), data.AddBook(session))
mux.HandleFunc(pat.Get("/books/:isbn"), dt.BookByISBN(session))
mux.HandleFunc(pat.Put("/books/:isbn"), data.UpdateBook(session))
mux.HandleFunc(pat.Delete("/books/:isbn"), data.DeleteBook(session))
http.ListenAndServe("localhost:8080", mux)
}
Hiện tại chúng ta có thể chạy thử ứng dụng:
go run main.go
Hiện không có bộ sưu tập nào có sẵn trong cơ sở dữ liệu.
Thực hiện các thao tác sau:
- Create a new ‘page’ data
- Get and AllPages
- Put and Update
- and Delete
Dummy Data:
{
"Title": "Trainings",
"Author": "Cihan Özhan",
"Header": "CihanOzhan.Com - Trainings",
"PageDefinition": "Kişisel blog sitemdeki eğitimlerimi listelediğim sayfadır.",
"Content": "I have been researching, training and consulting on software for a long time. I share some of these researches as video training.",
"URI": "http://www.cihanozhan.com/trainings"
}
Các truy vấn mẫu để lọc:
chúc may mắn!
Cihan Ozhan