GoLang microservices tutorial using CRUD app examples

 Creating a GoLang Microservices architecture for a CRUD application involves designing multiple independent services that can communicate with each other via APIs. Each service will typically manage its own database and handle specific tasks (e.g., user management, order processing, etc.). In this tutorial, we will build a basic CRUD microservices architecture in Go using the Gin framework for HTTP APIs and GORM for ORM-based database interaction.

Prerequisites

  • Go (version 1.18 or higher)
  • Docker (for containerization)
  • MySQL or PostgreSQL (for database)
  • Knowledge of RESTful API design

Project Overview

We will create the following microservices for a basic CRUD application:

  1. User Service: Manages user data (create, read, update, delete).
  2. Order Service: Manages order data (create, read, update, delete).
  3. Product Service: Manages product data (create, read, update, delete).

Each service will communicate with its own database, and the microservices will interact with each other using REST APIs.


Step 1: Set up the Go Project

  1. Create a new directory for your project:

    mkdir go-microservices cd go-microservices
  2. Initialize a new Go module:

    go mod init go-microservices
  3. Install dependencies:

    • Gin framework for HTTP routing.
    • GORM for ORM and database management.
    • MySQL driver for GORM to interact with MySQL.
    go get github.com/gin-gonic/gin go get github.com/jinzhu/gorm go get github.com/jinzhu/gorm/dialects/mysql

Step 2: Build the User Service (CRUD operations)

Create the user_service.go file:

This file will contain the logic for creating, reading, updating, and deleting user records.

package main import ( "fmt" "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "net/http" ) var db *gorm.DB var err error type User struct { ID uint `json:"id"` Name string `json:"name"` Email string `json:"email"` Password string `json:"password"` } func main() { // Connect to MySQL database db, err = gorm.Open("mysql", "root:password@tcp(127.0.0.1:3306)/userdb?charset=utf8&parseTime=True&loc=Local") if err != nil { fmt.Println("failed to connect to database") } defer db.Close() // Migrate the schema (creating the table if it doesn't exist) db.AutoMigrate(&User{}) r := gin.Default() // Define routes for CRUD operations r.POST("/users", createUser) r.GET("/users", getUsers) r.GET("/users/:id", getUser) r.PUT("/users/:id", updateUser) r.DELETE("/users/:id", deleteUser) // Run the server r.Run(":8081") } func createUser(c *gin.Context) { var user User if err := c.ShouldBindJSON(&user); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } db.Create(&user) c.JSON(http.StatusOK, user) } func getUsers(c *gin.Context) { var users []User if err := db.Find(&users).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Users not found"}) return } c.JSON(http.StatusOK, users) } func getUser(c *gin.Context) { id := c.Param("id") var user User if err := db.First(&user, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) return } c.JSON(http.StatusOK, user) } func updateUser(c *gin.Context) { id := c.Param("id") var user User if err := db.First(&user, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) return } if err := c.ShouldBindJSON(&user); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } db.Save(&user) c.JSON(http.StatusOK, user) } func deleteUser(c *gin.Context) { id := c.Param("id") if err := db.Delete(&User{}, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) return } c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"}) }

Step 3: Build the Order Service (CRUD operations)

Create the order_service.go file for managing orders.

package main import ( "fmt" "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "net/http" ) var db *gorm.DB var err error type Order struct { ID uint `json:"id"` Product string `json:"product"` Quantity int `json:"quantity"` Amount int `json:"amount"` } func main() { // Connect to MySQL database db, err = gorm.Open("mysql", "root:password@tcp(127.0.0.1:3306)/orderdb?charset=utf8&parseTime=True&loc=Local") if err != nil { fmt.Println("failed to connect to database") } defer db.Close() // Migrate the schema db.AutoMigrate(&Order{}) r := gin.Default() // Define routes for CRUD operations r.POST("/orders", createOrder) r.GET("/orders", getOrders) r.GET("/orders/:id", getOrder) r.PUT("/orders/:id", updateOrder) r.DELETE("/orders/:id", deleteOrder) // Run the server r.Run(":8082") } func createOrder(c *gin.Context) { var order Order if err := c.ShouldBindJSON(&order); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } db.Create(&order) c.JSON(http.StatusOK, order) } func getOrders(c *gin.Context) { var orders []Order if err := db.Find(&orders).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Orders not found"}) return } c.JSON(http.StatusOK, orders) } func getOrder(c *gin.Context) { id := c.Param("id") var order Order if err := db.First(&order, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) return } c.JSON(http.StatusOK, order) } func updateOrder(c *gin.Context) { id := c.Param("id") var order Order if err := db.First(&order, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) return } if err := c.ShouldBindJSON(&order); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } db.Save(&order) c.JSON(http.StatusOK, order) } func deleteOrder(c *gin.Context) { id := c.Param("id") if err := db.Delete(&Order{}, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) return } c.JSON(http.StatusOK, gin.H{"message": "Order deleted successfully"}) }

Step 4: Run the Services

Now that we have created two basic services (User Service and Order Service), let's run both services on separate ports:

  1. User Service will run on port 8081.
  2. Order Service will run on port 8082.

Run the user service:

go run user_service.go

Run the order service:

go run order_service.go

Step 5: Test the Services

You can use tools like Postman or cURL to test the CRUD operations for both services.

  • User Service (on localhost:8081):

    • POST /users: Create a user
    • GET /users: Get all users
    • GET /users/{id}: Get a specific user
    • PUT /users/{id}: Update a user
    • DELETE /users/{id}: Delete a user
  • Order Service (on localhost:8082):

    • POST /orders: Create an order
    • GET /orders: Get all orders
    • GET /orders/{id}: Get a specific order
    • PUT /orders/{id}: Update an order
    • DELETE /orders/{id}: Delete an order

Step 6: Dockerize the Microservices

You can use Docker to containerize each service for easier deployment and management.

Example of Dockerfile for a Service

Dockerfile
FROM golang:1.18-alpine WORKDIR /app COPY . . RUN go mod tidy RUN go build -o app . CMD ["./app"]

Build and run the Docker container for each service:

docker build -t user-service . docker run -p 8081:8081 user-service docker build -t order-service . docker run -p 8082:8082 order-service

Conclusion

You've now created a simple Go microservices architecture with basic CRUD functionality for two services: User and Order. Each service communicates with its own database and handles separate tasks. This is a basic architecture, and you can expand it with additional services, authentication, and inter-service communication (such as using gRPC or message queues like RabbitMQ or Kafka) for more complex scenarios.

Comments

Popular posts from this blog

PrimeNG tutorial with examples using frequently used classes

Docker and Kubernetes Tutorials and QnA

Building strong foundational knowledge in frontend development topics