diff --git a/README.md b/README.md index bdb8419..fe67812 100644 --- a/README.md +++ b/README.md @@ -140,8 +140,36 @@ https://github.com/veermuchandi/microservices-on-openshift.git \ oc expose svc/twitter-api ``` +## 4. Create the Products API Microservice +> (Golang application) +This microservice is a Golang REST API that manages product data with full CRUD operations. It provides endpoints for creating, reading, updating, and deleting products, and integrates with other microservices in the architecture. + +The Products API exposes the following REST endpoints: +- `GET /api/products` - List all products +- `POST /api/products` - Create a new product +- `GET /api/products/:id` - Get product by ID +- `PUT /api/products/:id` - Update product by ID +- `DELETE /api/products/:id` - Delete product by ID +- `GET /` - Health check endpoint -## 4. Create the frontend user registration application as a separate microservice +```sh +oc new-app -e EMAIL_SERVICE_URL="http://emailsvc-$OSE_PROJECT.$OSE_DOMAIN:8080" \ +USER_SERVICE_URL="http://userregsvc-$OSE_PROJECT.$OSE_DOMAIN:8080" \ +--context-dir='golang-products-api' \ +https://github.com/veermuchandi/microservices-on-openshift.git \ +--name='products-api' -l microservice=productssvc + +oc expose svc/products-api +``` + +The service is configured with environment variables to communicate with other microservices: +- `EMAIL_SERVICE_URL` - Points to the Python email service for sending notifications +- `USER_SERVICE_URL` - Points to the Node.js user registration service for user validation + +The Products API runs on port 8080 (like other microservices) and includes CORS support for frontend integration. It uses in-memory storage with sample products for demonstration purposes. + + +## 5. Create the frontend user registration application as a separate microservice > (php application) This microservice produces html+javascript to run in a browser and makes ajax calls to the backend User Registration service using REST APIs. Note that we are setting an environment variable for userregsvc to access the backend using REST APIs. @@ -158,15 +186,16 @@ $ oc expose svc/userreg ``` The service exposed in the above step is our application front end. You can find the URL by running ```oc get route``` -## 5. Verification and Testing +## 6. Verification and Testing > Visit http://userreg-msdev.apps.10.2.2.2.xip.io/ to see the php frontend. -## 6. Scaling applications +## 7. Scaling applications > Suppose you have a huge traffic and you want to scale front end ```sh oc scale dc/userreg --replicas=4 ``` + diff --git a/golang-products-api/go.mod b/golang-products-api/go.mod new file mode 100644 index 0000000..1d07219 --- /dev/null +++ b/golang-products-api/go.mod @@ -0,0 +1,9 @@ +module golang-products-api + +go 1.21 + +require ( + github.com/gin-contrib/cors v1.4.0 + github.com/gin-gonic/gin v1.9.1 +) + diff --git a/golang-products-api/main.go b/golang-products-api/main.go new file mode 100644 index 0000000..f58a9ba --- /dev/null +++ b/golang-products-api/main.go @@ -0,0 +1,217 @@ +package main + +import ( + "net/http" + "os" + "strconv" + "time" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +// Product represents a product in our system +type Product struct { + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Price float64 `json:"price"` + Category string `json:"category"` + InStock bool `json:"inStock"` + CreatedAt time.Time `json:"createdAt"` +} + +// In-memory storage for products +var products []Product +var nextID int = 1 + +func main() { + // Initialize some sample products + initSampleProducts() + + // Create Gin router + r := gin.Default() + + // Configure CORS + config := cors.DefaultConfig() + config.AllowAllOrigins = true + config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"} + config.AllowHeaders = []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With"} + r.Use(cors.New(config)) + + // Health check endpoint + r.GET("/", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "Products API Works!", + "status": "healthy", + }) + }) + + // API routes + api := r.Group("/api") + { + // GET /api/products - List all products + api.GET("/products", getProducts) + + // POST /api/products - Create a new product + api.POST("/products", createProduct) + + // GET /api/products/:id - Get product by ID + api.GET("/products/:id", getProductByID) + + // PUT /api/products/:id - Update product by ID + api.PUT("/products/:id", updateProduct) + + // DELETE /api/products/:id - Delete product by ID + api.DELETE("/products/:id", deleteProduct) + } + + // Get port from environment or default to 8080 + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + + // Start server + r.Run(":" + port) +} + +func initSampleProducts() { + products = []Product{ + {ID: 1, Name: "Laptop", Description: "High-performance laptop", Price: 999.99, Category: "Electronics", InStock: true, CreatedAt: time.Now()}, + {ID: 2, Name: "Coffee Mug", Description: "Ceramic coffee mug", Price: 12.99, Category: "Kitchen", InStock: true, CreatedAt: time.Now()}, + {ID: 3, Name: "Book", Description: "Programming guide", Price: 29.99, Category: "Books", InStock: false, CreatedAt: time.Now()}, + } + nextID = 4 +} + +func getProducts(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "success": true, + "data": products, + "count": len(products), + }) +} + +func createProduct(c *gin.Context) { + var newProduct Product + + if err := c.ShouldBindJSON(&newProduct); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "error": "Invalid JSON data", + }) + return + } + + // Set ID and creation time + newProduct.ID = nextID + newProduct.CreatedAt = time.Now() + nextID++ + + // Add to products slice + products = append(products, newProduct) + + c.JSON(http.StatusCreated, gin.H{ + "success": true, + "data": newProduct, + "message": "Product created successfully", + }) +} + +func getProductByID(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "error": "Invalid product ID", + }) + return + } + + for _, product := range products { + if product.ID == id { + c.JSON(http.StatusOK, gin.H{ + "success": true, + "data": product, + }) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{ + "success": false, + "error": "Product not found", + }) +} + +func updateProduct(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "error": "Invalid product ID", + }) + return + } + + var updatedProduct Product + if err := c.ShouldBindJSON(&updatedProduct); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "error": "Invalid JSON data", + }) + return + } + + for i, product := range products { + if product.ID == id { + // Preserve ID and creation time + updatedProduct.ID = id + updatedProduct.CreatedAt = product.CreatedAt + products[i] = updatedProduct + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "data": updatedProduct, + "message": "Product updated successfully", + }) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{ + "success": false, + "error": "Product not found", + }) +} + +func deleteProduct(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "success": false, + "error": "Invalid product ID", + }) + return + } + + for i, product := range products { + if product.ID == id { + // Remove product from slice + products = append(products[:i], products[i+1:]...) + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "Product deleted successfully", + }) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{ + "success": false, + "error": "Product not found", + }) +} + diff --git a/golang-products-api/models/product.go b/golang-products-api/models/product.go new file mode 100644 index 0000000..5452645 --- /dev/null +++ b/golang-products-api/models/product.go @@ -0,0 +1,15 @@ +package models + +import "time" + +// Product represents a product in the system +type Product struct { + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Price float64 `json:"price"` + Category string `json:"category"` + InStock bool `json:"inStock"` + CreatedAt time.Time `json:"createdAt"` +} + diff --git a/installscripts/6.deployProductsAPI-Golang.sh b/installscripts/6.deployProductsAPI-Golang.sh new file mode 100644 index 0000000..7f29859 --- /dev/null +++ b/installscripts/6.deployProductsAPI-Golang.sh @@ -0,0 +1,13 @@ +oc project $OSE_SERVICES_PROJECT + +export EMAIL_SERVICE_URL="http://emailsvc."$OSE_INFRA_PROJECT":8080" +export USER_SERVICE_URL="http://userregsvc."$OSE_SERVICES_PROJECT":8080" + +oc new-app -e EMAIL_SERVICE_URL=$EMAIL_SERVICE_URL \ +USER_SERVICE_URL=$USER_SERVICE_URL \ +--context-dir='golang-products-api' \ +https://github.com/debianmaster/microservices-on-openshift.git \ +--name='products-api' -l microservice=productssvc + +oc expose svc/products-api +