├── .gitignore ├── High-Level-Architecture.png ├── go.mod ├── Dockerfile ├── go.sum ├── pkg ├── db │ └── db.go ├── models │ └── item.go ├── services │ └── item_service.go ├── repositories │ └── item_repo.go └── controllers │ └── item_controller.go ├── README.md ├── cmd └── main.go ├── LICENSE └── Go-Microservice-REST-API.postman_collection.json /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /High-Level-Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shehab-as/Go-Microservices/HEAD/High-Level-Architecture.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module Go-Microservices 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/go-sql-driver/mysql v1.5.0 7 | github.com/gorilla/mux v1.8.0 8 | ) 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.14-alpine as build-env 2 | 3 | RUN apk add --no-cache git 4 | 5 | WORKDIR /src 6 | 7 | COPY . . 8 | 9 | RUN go mod download 10 | 11 | RUN CGO_ENABLED=0 go build -o ./out/main /src/cmd/main.go 12 | 13 | 14 | FROM scratch 15 | COPY --from=build-env /src/out/main /main 16 | EXPOSE 8080 17 | CMD ["./main"] -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 2 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 3 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 4 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 5 | -------------------------------------------------------------------------------- /pkg/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | _ "github.com/go-sql-driver/mysql" 6 | ) 7 | 8 | func ConnectDB() *sql.DB { 9 | dbDriver := "mysql" 10 | dbUser := "root" 11 | dbPass := "password123" 12 | dbName := "todo" 13 | dbURI := "(127.0.0.1:3306)" 14 | db, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@tcp"+dbURI+"/"+dbName) 15 | if err != nil { 16 | panic(err.Error()) 17 | } 18 | 19 | return db 20 | } -------------------------------------------------------------------------------- /pkg/models/item.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Item struct { 4 | ID string `json:"id"` 5 | Name string `json:"name"` 6 | Cost float64 `json:"cost"` 7 | Details string `json:"details"` 8 | } 9 | 10 | type ItemList struct { 11 | ID string `json:"id"` 12 | Items []Item `json:"items"` 13 | } 14 | 15 | type ItemRepository interface { 16 | GetItemById(ID int) (*Item, error) 17 | GetAllItems() (*ItemList, error) 18 | SaveItem(*Item) (bool, error) 19 | DeleteItem(ID int) (bool, error) 20 | UpdateItem(*Item) (bool, error) 21 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-Microservices 2 | 3 | Simple Example of adopting Repository-Service pattern with MySQL database in Go 4 | 5 | This repo provides a simple example of adopting repository-service pattern with CRUD operations on a MySQL database. 6 | 7 | Simple High-level Architecture 8 | 9 | ![](https://github.com/ShehabMMohamed/Go-Microservices/blob/main/High-Level-Architecture.png) 10 | 11 | You can import `Go-Microservice-REST-API.postman_collection.json` for local testing. 12 | 13 | #### Note 🔴: You need to install MySQL server and initialize a database ("todo") if you want to test it locally 14 | 15 | ### Dockerizing it 16 | 17 | `docker build -t go-microservice .` 18 | 19 | `docker run -p 8080:8080 go-microservice` 20 | 21 | Give it a ⭐ if this repo was helpful to you 22 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "fmt" 6 | "net/http" 7 | "github.com/gorilla/mux" 8 | "Go-Microservices/pkg/db" 9 | "Go-Microservices/pkg/services" 10 | "Go-Microservices/pkg/controllers" 11 | "Go-Microservices/pkg/repositories" 12 | ) 13 | 14 | func main() { 15 | 16 | db := db.ConnectDB() 17 | defer db.Close() 18 | itemRepo := repositories.NewItemRepo(db) 19 | itemService := services.NewItemService(itemRepo) 20 | itemController := controllers.NewItemController(itemService) 21 | 22 | router := mux.NewRouter() 23 | fmt.Println("Server is running on Port 8080") 24 | 25 | router.HandleFunc("/api/v1/item", itemController.GetItemList).Methods("GET") 26 | router.HandleFunc("/api/v1/item", itemController.CreateItem).Methods("POST") 27 | router.HandleFunc("/api/v1/item/{id}", itemController.UpdateItem).Methods("PUT") 28 | router.HandleFunc("/api/v1/item/{id}", itemController.DeleteItem).Methods("DELETE") 29 | router.HandleFunc("/api/v1/item/{id}", itemController.GetItem).Methods("GET") 30 | 31 | log.Fatal(http.ListenAndServe(":8080", router)) 32 | 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Shehab Abdel-Salam 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 | -------------------------------------------------------------------------------- /pkg/services/item_service.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "strconv" 5 | "Go-Microservices/pkg/models" 6 | "Go-Microservices/pkg/repositories" 7 | ) 8 | 9 | type ItemService struct { 10 | itemRepo *repositories.ItemRepo 11 | } 12 | 13 | func NewItemService(itemRepo *repositories.ItemRepo) *ItemService { 14 | return &ItemService { 15 | itemRepo: itemRepo, 16 | } 17 | } 18 | 19 | func (s* ItemService) IsItemAvailable(id int) (bool, error) { 20 | item, err := s.itemRepo.GetItemById(id) 21 | if err == nil && item != nil { 22 | return true, nil 23 | } 24 | return false, err 25 | } 26 | 27 | func (s* ItemService) GetSingleItem(ID int) (*models.Item, error) { 28 | item, err := s.itemRepo.GetItemById(ID) 29 | return item, err 30 | } 31 | 32 | func (s *ItemService) GetAllItems() (*models.ItemList, error) { 33 | itemList, err := s.itemRepo.GetAllItems() 34 | return itemList, err 35 | } 36 | 37 | func (s* ItemService) InsertItem(item *models.Item) (bool, error) { 38 | state, err := s.itemRepo.SaveItem(item) 39 | return state, err 40 | } 41 | 42 | func (s* ItemService) DeleteItem(id int) (bool, error) { 43 | var err error 44 | found, err := s.IsItemAvailable(id) 45 | if found == false { 46 | return false, err 47 | } 48 | state, err := s.itemRepo.DeleteItem(id) 49 | return state, err 50 | } 51 | 52 | func (s* ItemService) UpdateItem(item *models.Item) (bool, error) { 53 | var err error 54 | idInt, err := strconv.Atoi(item.ID) 55 | found, err := s.IsItemAvailable(idInt) 56 | if found == false { 57 | return false, err 58 | } 59 | state, err := s.itemRepo.UpdateItem(item) 60 | return state, err 61 | } 62 | 63 | -------------------------------------------------------------------------------- /pkg/repositories/item_repo.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "fmt" 5 | "database/sql" 6 | "Go-Microservices/pkg/models" 7 | ) 8 | 9 | type ItemRepo struct { 10 | dbClient *sql.DB 11 | } 12 | 13 | func NewItemRepo(dbClient *sql.DB) *ItemRepo { 14 | return &ItemRepo { 15 | dbClient: dbClient, 16 | } 17 | } 18 | 19 | func (i *ItemRepo) GetItemById(ID int) (*models.Item, error) { 20 | var err error 21 | var item models.Item 22 | err = i.dbClient.QueryRow("SELECT * FROM items WHERE id = ?", ID).Scan(&item.ID, &item.Name, &item.Cost, &item.Details) 23 | if err != nil { 24 | fmt.Printf("ERROR SELECT QUERY - %s", err) 25 | return nil, err 26 | } 27 | return &item, nil 28 | } 29 | 30 | // TODO. 31 | func (i *ItemRepo) GetAllItems() (*models.ItemList, error) { 32 | var err error 33 | rows, err := i.dbClient.Query("SELECT * FROM Item") 34 | if err != nil { 35 | fmt.Printf("ERROR SELECT QUERY - %s", err) 36 | return nil, err 37 | } 38 | var itemList *models.ItemList 39 | for rows.Next() { 40 | var item models.Item 41 | err = rows.Scan(&item.ID, &item.Name, &item.Cost, &item.Details) 42 | if err != nil { 43 | fmt.Printf("ERROR QUERY SCAN - %s", err) 44 | return nil, err 45 | } 46 | fmt.Printf("%+v\n", item) 47 | } 48 | return itemList, nil 49 | } 50 | 51 | func (i *ItemRepo) SaveItem(item *models.Item) (bool, error) { 52 | var err error 53 | query, err := i.dbClient.Prepare("INSERT INTO Item(id, name, cost, details) VALUES(?,?,?,?)") 54 | if err != nil { 55 | fmt.Printf("ERROR INSERT QUERY - %s", err) 56 | return false, err 57 | } 58 | query.Exec(item.ID, item.Name, item.Cost, item.Details) 59 | return true, nil 60 | } 61 | 62 | func (i *ItemRepo) DeleteItem(ID int) (bool, error) { 63 | var err error 64 | query, err := i.dbClient.Prepare("DELETE FROM Item WHERE id=?") 65 | if err != nil { 66 | fmt.Printf("ERROR DELETE QUERY - %s", err) 67 | return false, err 68 | } 69 | query.Exec(ID) 70 | return true, nil 71 | } 72 | 73 | func (i *ItemRepo) UpdateItem(item *models.Item) (bool, error) { 74 | var err error 75 | query, err := i.dbClient.Prepare("UPDATE Item SET name=?, cost=?, details=? WHERE id=?") 76 | if err != nil { 77 | fmt.Printf("ERROR UPDATE QUERY - %s", err) 78 | return false, err 79 | } 80 | query.Exec(item.Name, item.Cost, item.Details, item.ID) 81 | return true, nil 82 | } -------------------------------------------------------------------------------- /pkg/controllers/item_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "net/http" 7 | "io/ioutil" 8 | "encoding/json" 9 | "Go-Microservices/pkg/models" 10 | "Go-Microservices/pkg/services" 11 | ) 12 | 13 | type ItemController struct { 14 | itemService *services.ItemService 15 | } 16 | 17 | func NewItemController(itemService *services.ItemService) *ItemController { 18 | return &ItemController { 19 | itemService: itemService, 20 | } 21 | } 22 | 23 | func (h *ItemController) GetItem(w http.ResponseWriter, r *http.Request) { 24 | var item *models.Item 25 | var err error 26 | query := r.URL.Query() 27 | idStr := query.Get("id") 28 | idInt, err := strconv.Atoi(idStr) 29 | item, err = h.itemService.GetSingleItem(idInt); 30 | if err != nil { 31 | fmt.Printf("ERROR - %s", err) 32 | } 33 | json.NewEncoder(w).Encode(item) 34 | } 35 | 36 | func (h *ItemController) GetItemList(w http.ResponseWriter, r *http.Request) { 37 | var itemList *models.ItemList 38 | var err error 39 | itemList, err = h.itemService.GetAllItems() 40 | if err != nil { 41 | fmt.Printf("ERROR - %s", err) 42 | } 43 | json.NewEncoder(w).Encode(itemList) 44 | } 45 | 46 | func (h *ItemController) CreateItem(w http.ResponseWriter, r *http.Request) { 47 | var itemToCreate models.Item 48 | var err error 49 | reqBody, _ := ioutil.ReadAll(r.Body) 50 | json.Unmarshal(reqBody, &itemToCreate) 51 | created, err := h.itemService.InsertItem(&itemToCreate) 52 | if err != nil { 53 | fmt.Printf("ERROR - %s", err) 54 | } 55 | if created == true { 56 | fmt.Println("Saved Item Successfully") 57 | } 58 | json.NewEncoder(w).Encode(itemToCreate) 59 | } 60 | 61 | func (h *ItemController) DeleteItem(w http.ResponseWriter, r *http.Request) { 62 | var err error 63 | query := r.URL.Query() 64 | idStr := query.Get("id") 65 | idInt, err := strconv.Atoi(idStr) 66 | deleted, err := h.itemService.DeleteItem(idInt) 67 | if err != nil { 68 | fmt.Printf("ERROR - %s", err) 69 | } 70 | if deleted == true { 71 | fmt.Println("Deleted Item Successfully") 72 | } 73 | } 74 | 75 | func (h *ItemController) UpdateItem(w http.ResponseWriter, r *http.Request) { 76 | var itemToUpdate models.Item 77 | var err error 78 | reqBody, _ := ioutil.ReadAll(r.Body) 79 | json.Unmarshal(reqBody, &itemToUpdate) 80 | updated, err := h.itemService.UpdateItem(&itemToUpdate) 81 | if err != nil { 82 | fmt.Printf("ERROR - %s", err) 83 | } 84 | if updated == true { 85 | fmt.Println("Updated Item Successfully") 86 | } 87 | json.NewEncoder(w).Encode(itemToUpdate) 88 | } 89 | -------------------------------------------------------------------------------- /Go-Microservice-REST-API.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "d6e124dc-128f-4de5-8347-6088c039f61a", 4 | "name": "Go-Microservice-API", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Get All Items", 10 | "request": { 11 | "method": "GET", 12 | "header": [], 13 | "url": { 14 | "raw": "http://localhost:8080/api/v1/item", 15 | "protocol": "http", 16 | "host": [ 17 | "localhost" 18 | ], 19 | "port": "8080", 20 | "path": [ 21 | "api", 22 | "v1", 23 | "item" 24 | ] 25 | } 26 | }, 27 | "response": [] 28 | }, 29 | { 30 | "name": "Get Item By ID", 31 | "protocolProfileBehavior": { 32 | "disableBodyPruning": true 33 | }, 34 | "request": { 35 | "method": "GET", 36 | "header": [], 37 | "body": { 38 | "mode": "raw", 39 | "raw": "{\n \"id\": \"2\",\n \"name\": \"F\",\n \"cost\": 0.99,\n \"details\": \"N/A\"\n}", 40 | "options": { 41 | "raw": { 42 | "language": "json" 43 | } 44 | } 45 | }, 46 | "url": { 47 | "raw": "http://localhost:8080/api/v1/item/1", 48 | "protocol": "http", 49 | "host": [ 50 | "localhost" 51 | ], 52 | "port": "8080", 53 | "path": [ 54 | "api", 55 | "v1", 56 | "item", 57 | "1" 58 | ] 59 | } 60 | }, 61 | "response": [] 62 | }, 63 | { 64 | "name": "Insert Item", 65 | "request": { 66 | "method": "POST", 67 | "header": [], 68 | "body": { 69 | "mode": "raw", 70 | "raw": "{\n \"id\": 1,\n \"name\": \"first_item\",\n \"cost\": 12.99,\n \"details\": \"details test\"\n}", 71 | "options": { 72 | "raw": { 73 | "language": "json" 74 | } 75 | } 76 | }, 77 | "url": { 78 | "raw": "http://localhost:8080/api/v1/item", 79 | "protocol": "http", 80 | "host": [ 81 | "localhost" 82 | ], 83 | "port": "8080", 84 | "path": [ 85 | "api", 86 | "v1", 87 | "item" 88 | ] 89 | } 90 | }, 91 | "response": [] 92 | }, 93 | { 94 | "name": "Delete Item", 95 | "request": { 96 | "method": "DELETE", 97 | "header": [], 98 | "url": { 99 | "raw": "http://localhost:8080/api/v1/item/1", 100 | "protocol": "http", 101 | "host": [ 102 | "localhost" 103 | ], 104 | "port": "8080", 105 | "path": [ 106 | "api", 107 | "v1", 108 | "item", 109 | "1" 110 | ] 111 | } 112 | }, 113 | "response": [] 114 | }, 115 | { 116 | "name": "Update Item", 117 | "request": { 118 | "method": "PUT", 119 | "header": [], 120 | "body": { 121 | "mode": "raw", 122 | "raw": "{\n \"id\": 1,\n \"name\": \"first_item\",\n \"cost\": 10.99,\n \"details\": \"details test updated\"\n}", 123 | "options": { 124 | "raw": { 125 | "language": "json" 126 | } 127 | } 128 | }, 129 | "url": { 130 | "raw": "http://localhost:8080/api/v1/item/1", 131 | "protocol": "http", 132 | "host": [ 133 | "localhost" 134 | ], 135 | "port": "8080", 136 | "path": [ 137 | "api", 138 | "v1", 139 | "item", 140 | "1" 141 | ] 142 | } 143 | }, 144 | "response": [] 145 | } 146 | ] 147 | } --------------------------------------------------------------------------------