├── .gitignore ├── LICENSE ├── README.md ├── booking ├── app.go ├── controllers │ └── booking.go └── routes │ └── routes.go ├── dao └── db.go ├── database ├── booking.json ├── main.go ├── movie.json ├── showtimes.json └── user.json ├── helper └── utils.go ├── messaging └── message.go ├── models └── modles.go ├── movies ├── app.go ├── controllers │ └── movie.go └── routes │ └── routes.go ├── showtimes ├── app.go ├── controllers │ └── showtimes.go └── routes │ └── routes.go └── users ├── app.go ├── controllers └── user.go └── routes └── routes.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 CoderMiner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 使用Golang和MongoDB构建微服务 2 | 3 | 根据 [umermansoor github](https://github.com/umermansoor/microservices)的 `Python`版本的微服务改造成 `Golang`版本 4 | 一共有4个微服务 5 | * Movie Service: 是关于电影的基本信息,标题、评分等 6 | * ShowTimes Service: 关于电影上映时间的信息 7 | * Booking Service: 关于电影的订阅的信息 8 | * User Service: 用户的信息 9 | 10 | 11 | #### 要求 12 | 13 | * [Golang](https://golang.org) 14 | * [mux](https://github.com/gorilla/mux) 15 | * [MongoDB](https://www.mongodb.com/) 16 | 17 | #### API和文档 18 | 19 | 各个服务之间相互独立,单独的路由和单独的数据库,各个服务之间的通信是通过 `HTTP JSON`,每个服务的API的返回结果也是JSON类型,可以参考 [使用Golang和MongoDB构建 RESTful API](https://github.com/coderminer/restful),提取出各个服务之间共同的东西,独立于服务之外,供服务调用 20 | 21 | * 返回的结果封装 22 | 23 | `helper/utils.go` 24 | 25 | ``` 26 | func ResponseWithJson(w http.ResponseWriter, code int, payload interface{}) { 27 | response, _ := json.Marshal(payload) 28 | w.Header().Set("Content-Type", "application/json") 29 | w.WriteHeader(code) 30 | w.Write(response) 31 | } 32 | 33 | ``` 34 | 35 | * 基础数据 Entity 36 | 37 | `models/models.go` 38 | 39 | ``` 40 | type User struct { 41 | Id string `bson:"_id" json:"id"` 42 | Name string `bson:"name" json:"name"` 43 | } 44 | 45 | type Movie struct { 46 | Id string `bson:"_id" json:"id"` 47 | Title string `bson:"title" json:"title"` 48 | Rating float32 `bson:"rating" json:"rating"` 49 | Director string `bson:"director" json:"director"` 50 | } 51 | 52 | type ShowTimes struct { 53 | Id string `bson:"_id" json:"id"` 54 | Date string `bson:"date" json:"date"` 55 | Movies []string `bson:"movies" json:"movies"` 56 | } 57 | 58 | type Booking struct { 59 | Id string `bson:"_id" json:"id"` 60 | Name string `bson:"name" json:"name"` 61 | Books []BookInfo `bson:"books" json:"books"` 62 | } 63 | 64 | type BookInfo struct { 65 | Date string `bson:"date" json:"date"` 66 | Movies []string `bson:"movies" json:"movies"` 67 | } 68 | 69 | type Result struct { 70 | Name string `json:"name"` 71 | Books []ResultInfo `json:"books"` 72 | } 73 | 74 | type ResultInfo struct { 75 | Date string `json:"date"` 76 | Movies []Movie `json:"movies"` 77 | } 78 | ``` 79 | 80 | * 关于数据库的封装 81 | 82 | `dao/db.go`,具体的请参考 [对 mgo关于MongoDB的基础操作的封装](https://github.com/coderminer/goutil) 83 | 84 | ``` 85 | func Insert(db, collection string, docs ...interface{}) error { 86 | ms, c := connect(db, collection) 87 | defer ms.Close() 88 | return c.Insert(docs...) 89 | } 90 | 91 | func FindOne(db, collection string, query, selector, result interface{}) error { 92 | ms, c := connect(db, collection) 93 | defer ms.Close() 94 | return c.Find(query).Select(selector).One(result) 95 | } 96 | ... 97 | ``` 98 | 99 | #### 服务 100 | 101 | 各个服务具体的逻辑具体的参考 [使用Golang和MongoDB构建 RESTful API](https://github.com/coderminer/restful) 102 | 103 | * User Service(port 8000) 104 | * Movie Service(port 8001) 105 | * ShowTimes Service(port 8002) 106 | * Booking Service(port 8003) 107 | 108 | #### 服务通信 109 | 110 | 查询某个用户的订阅的电影信息时,需要先通过 `User Service` 服务查询这个用户,根据用户名通过 `Booking Service `查询用户的订阅信息,然后通过 `Movie Service`服务查询对应的电影的信息,都是通过 `HTTP` 通信 111 | 112 | ``` 113 | params := mux.Vars(r) 114 | name := params["name"] 115 | var user models.User 116 | if err := dao.FindOne(db, collection, bson.M{"_id": name}, nil, &user); err != nil { 117 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request") 118 | return 119 | } 120 | res, err := http.Get("http://127.0.0.1:8003/booking/" + name) 121 | if err != nil { 122 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request by name "+name) 123 | return 124 | } 125 | 126 | defer res.Body.Close() 127 | result, err := ioutil.ReadAll(res.Body) 128 | if err != nil { 129 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request of booking by name "+name) 130 | return 131 | } 132 | var booking models.Booking 133 | var resResult models.Result 134 | resResult.Name = name 135 | var resInfo models.ResultInfo 136 | 137 | if err := json.Unmarshal(result, &booking); err == nil { 138 | for _, book := range booking.Books { 139 | resInfo.Date = book.Date 140 | for _, movie := range book.Movies { 141 | res, err := http.Get("http://127.0.0.1:8001/movies/" + movie) 142 | if err == nil { 143 | result, err := ioutil.ReadAll(res.Body) 144 | if err == nil { 145 | var movie models.Movie 146 | if err := json.Unmarshal(result, &movie); err == nil { 147 | resInfo.Movies = append(resInfo.Movies, movie) 148 | } 149 | } 150 | } 151 | } 152 | resResult.Books = append(resResult.Books, resInfo) 153 | } 154 | helper.ResponseWithJson(w, http.StatusOK, resResult) 155 | } else { 156 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request") 157 | } 158 | ``` 159 | 160 | -------------------------------------------------------------------------------- /booking/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/globalsign/mgo/bson" 9 | 10 | "github.com/streadway/amqp" 11 | 12 | "github.com/coderminer/microservice/dao" 13 | "github.com/coderminer/microservice/messaging" 14 | "github.com/coderminer/microservice/models" 15 | 16 | "github.com/coderminer/microservice/booking/routes" 17 | ) 18 | 19 | var client messaging.IMessageClient 20 | 21 | func main() { 22 | 23 | initMessage() 24 | 25 | r := routes.NewRouter() 26 | http.ListenAndServe(":8003", r) 27 | 28 | } 29 | 30 | func initMessage() { 31 | client = &messaging.MessageClient{} 32 | err := client.ConnectToBroker("amqp://guest:guest@localhost:5672/") 33 | if err != nil { 34 | fmt.Println("Failed to connect to RabbitMQ", err) 35 | } 36 | 37 | err = client.SubscribeToQueue("new_booking", getBooking) 38 | if err != nil { 39 | fmt.Println("Failed to comsuer the msg", err) 40 | } 41 | } 42 | 43 | func getBooking(delivery amqp.Delivery) { 44 | 45 | var booking models.Booking 46 | json.Unmarshal(delivery.Body, &booking) 47 | booking.Id = bson.NewObjectId().Hex() 48 | dao.Insert("Booking", "BookModel", booking) 49 | fmt.Println("the booking msg", booking) 50 | } 51 | -------------------------------------------------------------------------------- /booking/controllers/booking.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/coderminer/microservice/dao" 8 | "github.com/coderminer/microservice/helper" 9 | "github.com/coderminer/microservice/models" 10 | "github.com/globalsign/mgo/bson" 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | const ( 15 | db = "Booking" 16 | collection = "BookModel" 17 | ) 18 | 19 | func CreateBooking(w http.ResponseWriter, r *http.Request) { 20 | defer r.Body.Close() 21 | var booking models.Booking 22 | if err := json.NewDecoder(r.Body).Decode(&booking); err != nil { 23 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request") 24 | return 25 | } 26 | booking.Id = bson.NewObjectId().Hex() 27 | if err := dao.Insert(db, collection, booking); err != nil { 28 | helper.ResponseWithJson(w, http.StatusInternalServerError, err.Error()) 29 | return 30 | } 31 | helper.ResponseWithJson(w, http.StatusOK, booking) 32 | } 33 | 34 | func GetAllBooking(w http.ResponseWriter, r *http.Request) { 35 | var bookings []models.Booking 36 | if err := dao.FindAll(db, collection, nil, nil, &bookings); err != nil { 37 | helper.ResponseWithJson(w, http.StatusInternalServerError, err.Error()) 38 | return 39 | } 40 | helper.ResponseWithJson(w, http.StatusOK, bookings) 41 | } 42 | 43 | func GetBookByName(w http.ResponseWriter, r *http.Request) { 44 | params := mux.Vars(r) 45 | name := params["name"] 46 | var booking models.Booking 47 | if err := dao.FindOne(db, collection, bson.M{"name": name}, nil, &booking); err != nil { 48 | helper.ResponseWithJson(w, http.StatusInternalServerError, err.Error()) 49 | return 50 | } 51 | helper.ResponseWithJson(w, http.StatusOK, booking) 52 | } 53 | -------------------------------------------------------------------------------- /booking/routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/coderminer/microservice/booking/controllers" 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | type Route struct { 11 | Method string 12 | Pattern string 13 | Handler http.HandlerFunc 14 | Middeleware mux.MiddlewareFunc 15 | } 16 | 17 | var routes []Route 18 | 19 | func init() { 20 | register("POST", "/booking", controllers.CreateBooking, nil) 21 | register("GET", "/booking", controllers.GetAllBooking, nil) 22 | register("GET", "/booking/{name}", controllers.GetBookByName, nil) 23 | } 24 | 25 | func register(method, pattern string, handler http.HandlerFunc, middleware mux.MiddlewareFunc) { 26 | routes = append(routes, Route{method, pattern, handler, middleware}) 27 | } 28 | 29 | func NewRouter() *mux.Router { 30 | router := mux.NewRouter() 31 | for _, route := range routes { 32 | r := router.Methods(route.Method). 33 | Path(route.Pattern) 34 | if route.Middeleware != nil { 35 | r.Handler(route.Middeleware(route.Handler)) 36 | } else { 37 | r.Handler(route.Handler) 38 | } 39 | } 40 | return router 41 | } 42 | -------------------------------------------------------------------------------- /dao/db.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/globalsign/mgo" 7 | ) 8 | 9 | const ( 10 | host = "127.0.0.1:27017" 11 | source = "admin" 12 | user = "user" 13 | pass = "123456" 14 | ) 15 | 16 | var globalS *mgo.Session 17 | 18 | func init() { 19 | dialInfo := &mgo.DialInfo{ 20 | Addrs: []string{host}, 21 | Source: source, 22 | Username: user, 23 | Password: pass, 24 | } 25 | s, err := mgo.DialWithInfo(dialInfo) 26 | if err != nil { 27 | log.Fatalln("create session error ", err) 28 | } 29 | 30 | globalS = s 31 | } 32 | 33 | func connect(db, collection string) (*mgo.Session, *mgo.Collection) { 34 | s := globalS.Copy() 35 | c := s.DB(db).C(collection) 36 | return s, c 37 | } 38 | 39 | func Insert(db, collection string, docs ...interface{}) error { 40 | ms, c := connect(db, collection) 41 | defer ms.Close() 42 | return c.Insert(docs...) 43 | } 44 | 45 | func FindOne(db, collection string, query, selector, result interface{}) error { 46 | ms, c := connect(db, collection) 47 | defer ms.Close() 48 | return c.Find(query).Select(selector).One(result) 49 | } 50 | 51 | func FindAll(db, collection string, query, selector, result interface{}) error { 52 | ms, c := connect(db, collection) 53 | defer ms.Close() 54 | return c.Find(query).Select(selector).All(result) 55 | } 56 | 57 | func Update(db, collection string, query, update interface{}) error { 58 | ms, c := connect(db, collection) 59 | defer ms.Close() 60 | return c.Update(query, update) 61 | } 62 | 63 | func UpdateAll(db, collection string, query, update interface{}) error { 64 | ms, c := connect(db, collection) 65 | defer ms.Close() 66 | _, err := c.UpdateAll(query, update) 67 | return err 68 | } 69 | 70 | func Remove(db, collection string, query interface{}) error { 71 | ms, c := connect(db, collection) 72 | defer ms.Close() 73 | return c.Remove(query) 74 | } 75 | 76 | func RemoveAll(db, collection string, query interface{}) error { 77 | ms, c := connect(db, collection) 78 | defer ms.Close() 79 | _, err := c.RemoveAll(query) 80 | return err 81 | } 82 | -------------------------------------------------------------------------------- /database/booking.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "5b471e5e9d5e3e3354f24b02", 4 | "name": "chris_rivers", 5 | "books": [ 6 | { 7 | "date": "20151201", 8 | "movies": [ 9 | "5b47125d9d5e3e34d4449987" 10 | ] 11 | } 12 | ] 13 | }, 14 | { 15 | "id": "5b471ebc9d5e3e3354f24b03", 16 | "name": "garret_heaton", 17 | "books": [ 18 | { 19 | "date": "20151201", 20 | "movies": [ 21 | "5b4712799d5e3e34d4449988" 22 | ] 23 | }, 24 | { 25 | "date": "20151202", 26 | "movies": [ 27 | "5b4712909d5e3e34d4449989" 28 | ] 29 | } 30 | ] 31 | }, 32 | { 33 | "id": "5b471f009d5e3e3354f24b04", 34 | "name": "dwight_schrute", 35 | "books": [ 36 | { 37 | "date": "20151201", 38 | "movies": [ 39 | "5b4712799d5e3e34d4449988", 40 | "5b47125d9d5e3e34d4449987" 41 | ] 42 | }, 43 | { 44 | "date": "20151205", 45 | "movies": [ 46 | "5b4712799d5e3e34d4449988", 47 | "5b4712909d5e3e34d4449989" 48 | ] 49 | } 50 | ] 51 | } 52 | ] -------------------------------------------------------------------------------- /database/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | json "encoding/json" 5 | "fmt" 6 | io "io/ioutil" 7 | 8 | "github.com/coderminer/microservice/dao" 9 | "github.com/coderminer/microservice/models" 10 | ) 11 | 12 | func main() { 13 | initUser("./user.json") 14 | initMovie("./movie.json") 15 | initShowtime("./showtimes.json") 16 | initBooking("./booking.json") 17 | } 18 | 19 | func initUser(filename string) { 20 | d, err := io.ReadFile(filename) 21 | if err != nil { 22 | panic("read user user.json error") 23 | } 24 | var users []models.User 25 | var ui []interface{} 26 | err = json.Unmarshal(d, &users) 27 | if err != nil { 28 | panic("unmarshal json file error") 29 | } 30 | for _, d := range users { 31 | ui = append(ui, d) 32 | } 33 | fmt.Println("users: ", ui) 34 | err = dao.Insert("User", "UserModel", ui...) 35 | if err != nil { 36 | panic(fmt.Sprintf("insert user %s", err)) 37 | } 38 | 39 | } 40 | 41 | func initMovie(filename string) { 42 | d, _ := io.ReadFile(filename) 43 | var movies []models.Movie 44 | json.Unmarshal(d, &movies) 45 | var inter []interface{} 46 | 47 | for _, item := range movies { 48 | inter = append(inter, item) 49 | } 50 | err := dao.Insert("Movie", "MovieModel", inter...) 51 | if err != nil { 52 | fmt.Println("insert movie error,", err) 53 | } 54 | } 55 | 56 | func initShowtime(filename string) { 57 | d, _ := io.ReadFile(filename) 58 | var shows []models.ShowTimes 59 | json.Unmarshal(d, &shows) 60 | var inter []interface{} 61 | for _, item := range shows { 62 | inter = append(inter, item) 63 | } 64 | err := dao.Insert("ShowTimes", "ShowModel", inter...) 65 | if err != nil { 66 | fmt.Println("insert ShowTimes error,", err) 67 | } 68 | } 69 | 70 | func initBooking(filename string) { 71 | d, _ := io.ReadFile(filename) 72 | var books []models.Booking 73 | json.Unmarshal(d, &books) 74 | var inter []interface{} 75 | for _, item := range books { 76 | inter = append(inter, item) 77 | } 78 | err := dao.Insert("Booking", "BookModel", inter...) 79 | if err != nil { 80 | fmt.Println("Insert booking error", err) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /database/movie.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "5b47122a9d5e3e34d4449985", 4 | "title": "The Good Dinosaur", 5 | "rating": 7.4, 6 | "director": "Peter Sohn" 7 | }, 8 | { 9 | "id": "5b4712439d5e3e34d4449986", 10 | "title": "The Martian", 11 | "rating": 8.2, 12 | "director": "Ridley Scott" 13 | }, 14 | { 15 | "id": "5b47125d9d5e3e34d4449987", 16 | "title": "The Night Before", 17 | "rating": 7.4, 18 | "director": "Jonathan Levine" 19 | }, 20 | { 21 | "id": "5b4712799d5e3e34d4449988", 22 | "title": "Creed", 23 | "rating": 8.8, 24 | "director": "Ryan Coogler" 25 | }, 26 | { 27 | "id": "5b4712909d5e3e34d4449989", 28 | "title": "Victor Frankenstein", 29 | "rating": 6.4, 30 | "director": "Paul McGuigan" 31 | }, 32 | { 33 | "id": "5b4712a69d5e3e34d444998a", 34 | "title": "The Danish Girl", 35 | "rating": 5.3, 36 | "director": "Tom Hooper" 37 | }, 38 | { 39 | "id": "5b4712bd9d5e3e34d444998b", 40 | "title": "Spectre", 41 | "rating": 7.1, 42 | "director": "Sam Mendes" 43 | } 44 | ] -------------------------------------------------------------------------------- /database/showtimes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "5b47178a9d5e3e0b8490040f", 4 | "date": "20151130", 5 | "movies": [ 6 | "5b47122a9d5e3e34d4449985", 7 | "5b4712439d5e3e34d4449986" 8 | ] 9 | }, 10 | { 11 | "id": "5b4717a89d5e3e0b84900410", 12 | "date": "20151201", 13 | "movies": [ 14 | "5b47125d9d5e3e34d4449987", 15 | "5b4712799d5e3e34d4449988" 16 | ] 17 | }, 18 | { 19 | "id": "5b4717c49d5e3e0b84900411", 20 | "date": "20151202", 21 | "movies": [ 22 | "5b4712909d5e3e34d4449989", 23 | "5b4712a69d5e3e34d444998a" 24 | ] 25 | }, 26 | { 27 | "id": "5b4717e59d5e3e0b84900412", 28 | "date": "20151203", 29 | "movies": [ 30 | "5b4712bd9d5e3e34d444998b", 31 | "5b47122a9d5e3e34d4449985" 32 | ] 33 | }, 34 | { 35 | "id": "5b4717fe9d5e3e0b84900413", 36 | "date": "20151204", 37 | "movies": [ 38 | "5b4712439d5e3e34d4449986", 39 | "5b47125d9d5e3e34d4449987" 40 | ] 41 | }, 42 | { 43 | "id": "5b4718169d5e3e0b84900414", 44 | "date": "20151205", 45 | "movies": [ 46 | "5b4712799d5e3e34d4449988", 47 | "5b4712909d5e3e34d4449989" 48 | ] 49 | }, 50 | { 51 | "id": "5b47182d9d5e3e0b84900415", 52 | "date": "20151206", 53 | "movies": [ 54 | "5b4712a69d5e3e34d444998a", 55 | "5b4712bd9d5e3e34d444998b" 56 | ] 57 | } 58 | ] -------------------------------------------------------------------------------- /database/user.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "chris_rivers", 4 | "name": "Chris Rivers" 5 | }, 6 | { 7 | "id": "peter_curley", 8 | "name": "Peter Curley" 9 | }, 10 | { 11 | "id": "garret_heaton", 12 | "name": "Garret Heaton" 13 | }, 14 | { 15 | "id": "michael_scott", 16 | "name": "Michael Scott" 17 | }, 18 | { 19 | "id": "jim_halpert", 20 | "name": "Jim Halpert" 21 | }, 22 | { 23 | "id": "pam_beesly", 24 | "name": "Pam Beesly" 25 | }, 26 | { 27 | "id": "dwight_schrute", 28 | "name": "Dwight Schrute" 29 | }, 30 | { 31 | "id": "kevin_woo", 32 | "name": "Kevin woo" 33 | } 34 | ] -------------------------------------------------------------------------------- /helper/utils.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | func ResponseWithJson(w http.ResponseWriter, code int, payload interface{}) { 9 | response, _ := json.Marshal(payload) 10 | w.Header().Set("Content-Type", "application/json") 11 | w.WriteHeader(code) 12 | w.Write(response) 13 | } 14 | -------------------------------------------------------------------------------- /messaging/message.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/streadway/amqp" 8 | ) 9 | 10 | type IMessageClient interface { 11 | ConnectToBroker(connectionStr string) error 12 | PublishToQueue(data []byte, queueName string) error 13 | SubscribeToQueue(queueName string, handlerFunc func(amqp.Delivery)) error 14 | 15 | Close() 16 | } 17 | 18 | type MessageClient struct { 19 | conn *amqp.Connection 20 | } 21 | 22 | func (m *MessageClient) ConnectToBroker(connectionStr string) error { 23 | if connectionStr == "" { 24 | panic("the connection str mustnt be null") 25 | } 26 | var err error 27 | m.conn, err = amqp.Dial(connectionStr) 28 | return err 29 | } 30 | 31 | func (m *MessageClient) PublishToQueue(body []byte, queueName string) error { 32 | if m.conn == nil { 33 | panic("before publish you must connect the RabbitMQ first") 34 | } 35 | 36 | ch, err := m.conn.Channel() 37 | defer ch.Close() 38 | failOnError(err, "Failed to open a channel") 39 | 40 | q, err := ch.QueueDeclare( 41 | queueName, 42 | false, 43 | false, 44 | false, 45 | false, 46 | nil, 47 | ) 48 | failOnError(err, "Failed to declare a queue") 49 | 50 | err = ch.Publish( 51 | "", 52 | q.Name, 53 | false, 54 | false, 55 | amqp.Publishing{ 56 | ContentType: "application/json", 57 | Body: body, 58 | }, 59 | ) 60 | failOnError(err, "Failed to publish a message") 61 | 62 | return nil 63 | } 64 | 65 | func (m *MessageClient) SubscribeToQueue(queueName string, handlerFunc func(amqp.Delivery)) error { 66 | ch, err := m.conn.Channel() 67 | //defer ch.Close() 68 | failOnError(err, "Failed to open a channel") 69 | 70 | q, err := ch.QueueDeclare( 71 | queueName, 72 | false, 73 | false, 74 | false, 75 | false, 76 | nil, 77 | ) 78 | failOnError(err, "Failed to declare a queue") 79 | 80 | msgs, err := ch.Consume( 81 | q.Name, 82 | "", 83 | true, 84 | false, 85 | false, 86 | false, 87 | nil, 88 | ) 89 | 90 | failOnError(err, "Failed to register a consumer") 91 | go consumeLoop(msgs, handlerFunc) 92 | return nil 93 | } 94 | 95 | func (m *MessageClient) Close() { 96 | if m.conn != nil { 97 | m.conn.Close() 98 | } 99 | } 100 | 101 | func consumeLoop(msgs <-chan amqp.Delivery, handlerFunc func(amqp.Delivery)) { 102 | for d := range msgs { 103 | handlerFunc(d) 104 | } 105 | } 106 | 107 | func failOnError(err error, msg string) { 108 | if err != nil { 109 | log.Fatalf("%s: %s", err, msg) 110 | panic(fmt.Sprintf("%s: %s", err, msg)) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /models/modles.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type User struct { 4 | Id string `bson:"_id" json:"id"` 5 | Name string `bson:"name" json:"name"` 6 | } 7 | 8 | type Movie struct { 9 | Id string `bson:"_id" json:"id"` 10 | Title string `bson:"title" json:"title"` 11 | Rating float32 `bson:"rating" json:"rating"` 12 | Director string `bson:"director" json:"director"` 13 | } 14 | 15 | type ShowTimes struct { 16 | Id string `bson:"_id" json:"id"` 17 | Date string `bson:"date" json:"date"` 18 | Movies []string `bson:"movies" json:"movies"` 19 | } 20 | 21 | type Booking struct { 22 | Id string `bson:"_id" json:"id"` 23 | Name string `bson:"name" json:"name"` 24 | Books []BookInfo `bson:"books" json:"books"` 25 | } 26 | 27 | type BookInfo struct { 28 | Date string `bson:"date" json:"date"` 29 | Movies []string `bson:"movies" json:"movies"` 30 | } 31 | 32 | type Result struct { 33 | Name string `json:"name"` 34 | Books []ResultInfo `json:"books"` 35 | } 36 | 37 | type ResultInfo struct { 38 | Date string `json:"date"` 39 | Movies []Movie `json:"movies"` 40 | } 41 | -------------------------------------------------------------------------------- /movies/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/coderminer/microservice/movies/routes" 7 | ) 8 | 9 | func main() { 10 | r := routes.NewRouter() 11 | http.ListenAndServe(":8001", r) 12 | } 13 | -------------------------------------------------------------------------------- /movies/controllers/movie.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/globalsign/mgo/bson" 8 | "github.com/gorilla/mux" 9 | 10 | "github.com/coderminer/microservice/dao" 11 | "github.com/coderminer/microservice/helper" 12 | "github.com/coderminer/microservice/models" 13 | ) 14 | 15 | const ( 16 | db = "Movie" 17 | collection = "MovieModel" 18 | ) 19 | 20 | func CreateMovie(w http.ResponseWriter, r *http.Request) { 21 | defer r.Body.Close() 22 | var movie models.Movie 23 | if err := json.NewDecoder(r.Body).Decode(&movie); err != nil { 24 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request") 25 | return 26 | } 27 | movie.Id = bson.NewObjectId().Hex() 28 | if err := dao.Insert(db, collection, movie); err != nil { 29 | helper.ResponseWithJson(w, http.StatusInternalServerError, err.Error()) 30 | return 31 | } 32 | helper.ResponseWithJson(w, http.StatusOK, movie) 33 | } 34 | 35 | func AllMovies(w http.ResponseWriter, r *http.Request) { 36 | var movies []models.Movie 37 | if err := dao.FindAll(db, collection, nil, nil, &movies); err != nil { 38 | helper.ResponseWithJson(w, http.StatusInternalServerError, err.Error()) 39 | return 40 | } 41 | helper.ResponseWithJson(w, http.StatusOK, movies) 42 | } 43 | 44 | func GetMovie(w http.ResponseWriter, r *http.Request) { 45 | params := mux.Vars(r) 46 | id := params["id"] 47 | var movie models.Movie 48 | if err := dao.FindOne(db, collection, bson.M{"_id": id}, nil, &movie); err != nil { 49 | helper.ResponseWithJson(w, http.StatusInternalServerError, err.Error()) 50 | return 51 | } 52 | helper.ResponseWithJson(w, http.StatusOK, movie) 53 | } 54 | -------------------------------------------------------------------------------- /movies/routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/coderminer/microservice/movies/controllers" 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | type Route struct { 11 | Method string 12 | Pattern string 13 | Handler http.HandlerFunc 14 | Middleware mux.MiddlewareFunc 15 | } 16 | 17 | var routes []Route 18 | 19 | func init() { 20 | register("POST", "/movies", controllers.CreateMovie, nil) 21 | register("GET", "/movies", controllers.AllMovies, nil) 22 | register("GET", "/movies/{id}", controllers.GetMovie, nil) 23 | } 24 | 25 | func register(method, pattern string, handler http.HandlerFunc, middleware mux.MiddlewareFunc) { 26 | routes = append(routes, Route{method, pattern, handler, middleware}) 27 | } 28 | 29 | func NewRouter() *mux.Router { 30 | router := mux.NewRouter() 31 | for _, route := range routes { 32 | r := router.Methods(route.Method). 33 | Path(route.Pattern) 34 | if route.Middleware != nil { 35 | r.Handler(route.Middleware(route.Handler)) 36 | } else { 37 | r.Handler(route.Handler) 38 | } 39 | } 40 | return router 41 | } 42 | -------------------------------------------------------------------------------- /showtimes/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/coderminer/microservice/showtimes/routes" 7 | ) 8 | 9 | func main() { 10 | r := routes.NewRouter() 11 | http.ListenAndServe(":8002", r) 12 | } 13 | -------------------------------------------------------------------------------- /showtimes/controllers/showtimes.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/coderminer/microservice/dao" 8 | "github.com/coderminer/microservice/helper" 9 | "github.com/coderminer/microservice/models" 10 | "github.com/globalsign/mgo/bson" 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | const ( 15 | db = "ShowTimes" 16 | collection = "ShowModel" 17 | ) 18 | 19 | func CreateTimes(w http.ResponseWriter, r *http.Request) { 20 | defer r.Body.Close() 21 | var times models.ShowTimes 22 | if err := json.NewDecoder(r.Body).Decode(×); err != nil { 23 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request") 24 | return 25 | } 26 | times.Id = bson.NewObjectId().Hex() 27 | if err := dao.Insert(db, collection, times); err != nil { 28 | helper.ResponseWithJson(w, http.StatusInternalServerError, err.Error()) 29 | return 30 | } 31 | helper.ResponseWithJson(w, http.StatusOK, times) 32 | } 33 | 34 | func ShowTime(w http.ResponseWriter, r *http.Request) { 35 | params := mux.Vars(r) 36 | datestr := params["date"] 37 | var times models.ShowTimes 38 | if err := dao.FindOne(db, collection, bson.M{"date": datestr}, nil, ×); err != nil { 39 | helper.ResponseWithJson(w, http.StatusInternalServerError, err.Error()) 40 | return 41 | } 42 | helper.ResponseWithJson(w, http.StatusOK, times) 43 | } 44 | 45 | func ShowAll(w http.ResponseWriter, r *http.Request) { 46 | var times []models.ShowTimes 47 | if err := dao.FindAll(db, collection, nil, nil, ×); err != nil { 48 | helper.ResponseWithJson(w, http.StatusInternalServerError, err.Error()) 49 | return 50 | } 51 | helper.ResponseWithJson(w, http.StatusOK, times) 52 | } 53 | -------------------------------------------------------------------------------- /showtimes/routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/coderminer/microservice/showtimes/controllers" 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | type Route struct { 11 | Method string 12 | Pattern string 13 | Handler http.HandlerFunc 14 | Middleware mux.MiddlewareFunc 15 | } 16 | 17 | var routes []Route 18 | 19 | func init() { 20 | register("POST", "/showtimes", controllers.CreateTimes, nil) 21 | register("GET", "/showtimes/{date}", controllers.ShowTime, nil) 22 | register("GET", "/showtimes", controllers.ShowAll, nil) 23 | } 24 | 25 | func register(method, pattern string, handler http.HandlerFunc, middleware mux.MiddlewareFunc) { 26 | routes = append(routes, Route{method, pattern, handler, middleware}) 27 | } 28 | 29 | func NewRouter() *mux.Router { 30 | router := mux.NewRouter() 31 | for _, route := range routes { 32 | r := router.Methods(route.Method). 33 | Path(route.Pattern) 34 | if route.Middleware != nil { 35 | r.Handler(route.Middleware(route.Handler)) 36 | } else { 37 | r.Handler(route.Handler) 38 | } 39 | } 40 | return router 41 | } 42 | -------------------------------------------------------------------------------- /users/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/coderminer/microservice/users/routes" 7 | ) 8 | 9 | func main() { 10 | r := routes.NewRouter() 11 | http.ListenAndServe(":8000", r) 12 | } 13 | -------------------------------------------------------------------------------- /users/controllers/user.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/coderminer/microservice/messaging" 10 | 11 | "github.com/coderminer/microservice/dao" 12 | "github.com/coderminer/microservice/helper" 13 | "github.com/coderminer/microservice/models" 14 | "github.com/globalsign/mgo/bson" 15 | "github.com/gorilla/mux" 16 | ) 17 | 18 | const ( 19 | db = "User" 20 | collection = "UserModel" 21 | ) 22 | 23 | var client messaging.IMessageClient 24 | 25 | func init() { 26 | client = &messaging.MessageClient{} 27 | err := client.ConnectToBroker("amqp://guest:guest@localhost:5672/") 28 | if err != nil { 29 | fmt.Println("connect to rabbitmq error", err) 30 | } 31 | 32 | } 33 | 34 | func CreateUser(w http.ResponseWriter, r *http.Request) { 35 | defer r.Body.Close() 36 | var user models.User 37 | if err := json.NewDecoder(r.Body).Decode(&user); err != nil { 38 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request") 39 | return 40 | } 41 | 42 | if err := dao.Insert(db, collection, user); err != nil { 43 | helper.ResponseWithJson(w, http.StatusInternalServerError, err.Error()) 44 | return 45 | } 46 | helper.ResponseWithJson(w, http.StatusOK, user) 47 | 48 | } 49 | 50 | func AllUsers(w http.ResponseWriter, r *http.Request) { 51 | defer r.Body.Close() 52 | var users []models.User 53 | if err := dao.FindAll(db, collection, nil, nil, &users); err != nil { 54 | helper.ResponseWithJson(w, http.StatusInternalServerError, err.Error()) 55 | return 56 | } 57 | helper.ResponseWithJson(w, http.StatusOK, users) 58 | } 59 | 60 | func UserBooking(w http.ResponseWriter, r *http.Request) { 61 | defer r.Body.Close() 62 | params := mux.Vars(r) 63 | name := params["name"] 64 | var user models.User 65 | if err := dao.FindOne(db, collection, bson.M{"_id": name}, nil, &user); err != nil { 66 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request") 67 | return 68 | } 69 | res, err := http.Get("http://127.0.0.1:8003/booking/" + name) 70 | if err != nil { 71 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request by name "+name) 72 | return 73 | } 74 | 75 | defer res.Body.Close() 76 | result, err := ioutil.ReadAll(res.Body) 77 | if err != nil { 78 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request of booking by name "+name) 79 | return 80 | } 81 | var booking models.Booking 82 | var resResult models.Result 83 | resResult.Name = name 84 | var resInfo models.ResultInfo 85 | 86 | if err := json.Unmarshal(result, &booking); err == nil { 87 | for _, book := range booking.Books { 88 | resInfo.Date = book.Date 89 | for _, movie := range book.Movies { 90 | res, err := http.Get("http://127.0.0.1:8001/movies/" + movie) 91 | if err == nil { 92 | result, err := ioutil.ReadAll(res.Body) 93 | if err == nil { 94 | var movie models.Movie 95 | if err := json.Unmarshal(result, &movie); err == nil { 96 | resInfo.Movies = append(resInfo.Movies, movie) 97 | } 98 | } 99 | } 100 | } 101 | resResult.Books = append(resResult.Books, resInfo) 102 | } 103 | helper.ResponseWithJson(w, http.StatusOK, resResult) 104 | } else { 105 | helper.ResponseWithJson(w, http.StatusBadRequest, "invalid request") 106 | } 107 | 108 | } 109 | 110 | func NewBooking(w http.ResponseWriter, r *http.Request) { 111 | params := mux.Vars(r) 112 | user_name := params["name"] 113 | defer r.Body.Close() 114 | 115 | var bookings models.Booking 116 | body, _ := ioutil.ReadAll(r.Body) 117 | err := json.Unmarshal(body, &bookings) 118 | if err != nil { 119 | fmt.Println("the format body error ", err) 120 | } 121 | fmt.Println("user name:", user_name, bookings) 122 | go notifyMsg(body) 123 | 124 | } 125 | 126 | func notifyMsg(body []byte) { 127 | err := client.PublishToQueue(body, "new_booking") 128 | if err != nil { 129 | fmt.Println("Failed to publis message", err) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /users/routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/coderminer/microservice/users/controllers" 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | type Route struct { 11 | Method string 12 | Pattern string 13 | Handler http.HandlerFunc 14 | Middleware mux.MiddlewareFunc 15 | } 16 | 17 | var routes []Route 18 | 19 | func init() { 20 | register("POST", "/user", controllers.CreateUser, nil) 21 | register("GET", "/user", controllers.AllUsers, nil) 22 | register("GET", "/user/{name}/booking", controllers.UserBooking, nil) 23 | register("POST", "/user/{name}/booking", controllers.NewBooking, nil) 24 | } 25 | 26 | func register(method, pattern string, handler http.HandlerFunc, middleware mux.MiddlewareFunc) { 27 | routes = append(routes, Route{method, pattern, handler, middleware}) 28 | } 29 | 30 | func NewRouter() *mux.Router { 31 | router := mux.NewRouter() 32 | for _, route := range routes { 33 | r := router.Methods(route.Method). 34 | Path(route.Pattern) 35 | if route.Middleware != nil { 36 | r.Handler(route.Middleware(route.Handler)) 37 | } else { 38 | r.Handler(route.Handler) 39 | } 40 | } 41 | return router 42 | } 43 | --------------------------------------------------------------------------------