avatarIsrael Josué Parra Rosales

Summary

The provided content outlines the process of defining a REST API, implementing the Gin-Gonic framework, coding endpoints, and creating request handlers for a shopping cart microservice.

Abstract

This chapter delves into the technical aspects of constructing a REST API for a shopping cart microservice using the Gin-Gonic framework in Go. It covers the organization of the codebase into packages, the use of Data Transfer Objects (DTOs) for efficient data transfer, and the implementation of handler functions to manage HTTP requests. The code integrates various components such as database configuration, services, and repositories using dependency injection and the Inversion of Control (IoC) principle to enhance maintainability and testability. The chapter also emphasizes the importance of input validation and error handling to ensure data integrity and application reliability.

Opinions

  • The Inversion of Control (IoC) principle is highly beneficial for software development due to its advantages in decoupling, testability, component replacement, extensibility, clarity, maintainability, and code reuse.
  • The use of DTOs is crucial for defining the structure of request and response objects, ensuring that only the necessary data is transferred and validated.
  • Structured error handling with error wrapping is considered essential for providing meaningful feedback to the user and facilitating debugging.
  • The Gin-Gonic framework is praised for its ease of use in configuring routes and handling HTTP requests, which simplifies the development of RESTful APIs.
  • The separation of concerns is achieved by dividing the application into distinct layers, such as the API, handler, and domain layers, each with a specific responsibility.
  • The importance of thorough documentation and validation for API endpoints is highlighted to ensure that the API behaves predictably and is user-friendly.

Chapter 13 - Coding our Microservice (Part 3)

Defining the REST API

The following list is the previous chapters of this series:

I recommend you take a look at the previous chapters if you have not read them yet. That will help you to get more knowledge in this wonderful world of “Microservices architecture”.

Structure

In this chapter, the following topics will be covered:

  • Defining REST API
  • Implementing gin-gonic in our project.
  • Coding our needed endpoints
  • Coding the request handlers

Introduction

In this chapter will be defined the code related to the application. The “api” package is primarily responsible for exposing the microservice’s functionality through an HTTP API. It encompasses Data Transfer Objects (DTOs) for efficient data transfer, controllers for handling HTTP requests, and route definitions specified in “api.go” and “routes.go” files.

This package seamlessly interfaces with the “domain” package, which serves as the domain layer. Here, the fundamental structures and business logic associated with the “Shopping Cart” and “Product” entities are meticulously defined and orchestrated.

Next will describe and analyze each one of the files defined under this package.

...
│   ├── app/
│   │   └── api
│   │       ├── dto/
│   │       │   ├── shopping_cart.go
│   │       │   └── product.go
│   │       ├── handler/
│   │       │   ├── product/
│   │       │   │   └── handler.go
│   │       │   └── shopping_cart/
│   │       │       └── handler.go
│   │       ├── api.go
│   │       └── routes.go
...

/app/api/api.go

package api
import (
    "github.com/gin-gonic/gin"
    product_handler "github.com/go-microservices/shopping-cart-service/internal/app/api/handler/product"
    scHandler "github.com/go-microservices/shopping-cart-service/internal/app/api/handler/shopping_cart"
    "github.com/go-microservices/shopping-cart-service/internal/domain/product"
    shoppingcart "github.com/go-microservices/shopping-cart-service/internal/domain/shopping_cart"
    "github.com/go-microservices/shopping-cart-service/internal/infrastructure/database"
    "github.com/go-microservices/shopping-cart-service/internal/infrastructure/persistence"
    "github.com/go-microservices/shopping-cart-service/internal/shared/logger"
)

type ShoppingCartHandler interface {
    Create(c *gin.Context)
    Get(c *gin.Context)
}

type ProductHandler interface {
    Create(c *gin.Context)
    Get(c *gin.Context)
    Delete(c *gin.Context)
    Update(c *gin.Context)
}

// Description 1
type apiV1 struct {
    shoppingCartHandler ShoppingCartHandler
    productHandler      ProductHandler
}

func NewApiV1(shoppingCartHandler ShoppingCartHandler, productHandler ProductHandler) apiV1 {
    return apiV1{
        shoppingCartHandler: shoppingCartHandler,
        productHandler:      productHandler,
    }
}

// Description 2
func LoadApiV1() {
    logger := logger.NewLogger()
    logger.Info("loading shopping-cart API")
    dbConfig, err := database.NewConfig()
    if err != nil {
        logger.Errorf("error loading configuration: %v", err)
    }
    dbConnection, err := database.NewDatabseConnection(dbConfig)
    if err != nil {
        logger.Errorf("error connecting to database: %v", err)
    }
    defer dbConnection.Close()
    concreteShoppinCartRepoImpl := persistence.NewshoppingCartMySQLRepo(dbConnection, logger)
    shoppingCartSvc := shoppingcart.NewShoppingCartService(concreteShoppinCartRepoImpl, logger)
    spHandler := scHandler.NewShoppingCartHandler(shoppingCartSvc, logger)
    concreteProductRepoImp := persistence.NewProductMySQLRepo(dbConnection, logger)
    producScv := product.NewProductService(concreteProductRepoImp, logger)
    pHandler := product_handler.NewProductHandler(producScv, logger)
    apiV1 := NewApiV1(spHandler, pHandler)
    
    // defined in a separate file
    apiV1.loadRoutes()
}

Explaining the code: This code will integrate the different components of our microservice, Injecting the dependencies for each one of the components.

Description 1: Here is defined a structure to group the Handlers defined for our microservice, as can be seen, those handlers are wrapped by using the Interface.

Description 2 (LoadApiV1): The function LoadApiV1 will be called by main.go to build our REST API. This function defines the configuration of the database, the creation of concrete repositories and services, and the assignment of handlers to the “apiV1” instance. Finally, load the routes for the API.

The approach used to integrate each of the ApiV1 components is the Inversion of Control (IoC), since it is beneficial in software development for several reasons:

Decoupling By injecting dependencies instead of creating them internally, software components and modules become less dependent on concrete implementations of their dependencies. This reduces coupling and makes the code more flexible and maintainable.

Testability The IoC makes unit testing and automated testing easy. You can replace concrete implementations with test implementations (mocks) during testing, allowing you to isolate and test each component independently.

Component Replacement With the IoC, it’s easier to replace a particular implementation with another without changing a lot of code. This is useful when you need to change the data source, for example from a database to an external API.

Extensibility The IoC makes your code more extensible. You can add new implementations or functionality without modifying existing code, as long as they adhere to the same interface.

Clarity and Maintainability By defining dependencies explicitly, your code becomes clearer and easier to understand. Developers can quickly see what dependencies a component has by looking at its constructor or configure method.

Code Reuse The IoC promotes code reuse. You can use a specific implementation in multiple places without duplicating code.

/app/api/routes.go

package api

import (
    "log"
    "net/http"
    "github.com/gin-gonic/gin"
    swaggerFiles "github.com/swaggo/files"
    ginSwagger "github.com/swaggo/gin-swagger"
)

func (api apiV1) loadRoutes() {
    log.Println("loading Routes")
    r := gin.Default()

    v1 := r.Group("/api/v1")
    {
        shoppingCart := v1.Group("shopping-cart")
        {
            shoppingCart.POST("", api.shoppingCartHandler.Create)
            shoppingCart.GET(":userID", api.shoppingCartHandler.Get)
        }
        product := v1.Group("/product")
        {
            product.POST("", api.productHandler.Create)
            product.PATCH(":productId", api.productHandler.Update)
            product.GET(":shoppingCartId", api.productHandler.Get)
            product.DELETE(":productId", api.productHandler.Delete)
        }
    }

    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    err := r.Run(":8080")
    if err != nil {
        log.Println("can not start service")
    }
}

Explaining the code: This code defines the routes and routing for a REST API of our microservice. It contains all the necessary configurations to start up our microservice and start up a new service.

It uses the Gin-Gonic framework to configure routes and handle HTTP requests. Exposing endpoints related to shopping carts and products. Each of those endpoints helped us understand how different HTTP methods are implemented.

Two endpoints are defined as part of the “shopping cart” routes:

  • POST /shopping-cart This endpoint is defined to create new shopping carts in the database.
  • GET /shopping-cart/{userId} This endpoint is defined to retrieve a “shopping cart” assigned to a specific user.

On the other hand, to handle the “products” are defined the following endpoints:

  • POST /product This endpoint is defined to add products to a specific shopping cart
  • PATCH /product/{productId} This endpoint is defined to update the quantity of the product added to the shopping cart.
  • GET /product/{shoppingCartId} This endpoint is defined to list the products from a shopping cart.
  • DELETE /product/{productId} This endpoint is defined to remove a product from the shopping cart.

These routes facilitate the smooth operation of our microservice, making it a robust solution for managing shopping carts and product data.

Now let’s analyze the “dto” package. Inside the “api” package is defined the “dto” package, and inside will be defined the needed Request and Response objects used by “products” and “Shopping Cart” endpoints.

DTOs are data structures designed to transport information between different parts of an application. These objects encapsulate specific data required for specific operations and can include validations and transformations. Their main purpose is to enable a clear separation of responsibilities and enhance efficiency in communication between different layers of an application.

/app/api/dto/shopping_cart.go

package dto

import (
    validation "github.com/go-ozzo/ozzo-validation/v4"
    uuid "github.com/satori/go.uuid"
)

type ShoppingCartResponse struct {
    ID     *uuid.UUID `json:"id,omitempty"`
    UserID *uuid.UUID `json:"user_id"`
}

func NewShoppingCartResponse(id *uuid.UUID, userID *uuid.UUID) ShoppingCartResponse {
    return ShoppingCartResponse{
        ID:     id,
        UserID: userID,
    }
}

type ShoppingCartRequest struct {
    UserID *uuid.UUID `json:"user_id"`
}

func (sc ShoppingCartRequest) Validate() error {
    return validation.ValidateStruct(&sc,
        validation.Field(&sc.UserID, validation.Required))
}

Explaining the code: In this file will be defined two structs ShoppingCartRequest and ShoppingCartRespose, those structs define the DTOs used to map the API request and response.

The ShoppingCartRequest defines a method Validate(). That method will help us to verify the data values in order to ensure the data integrity before being stored in the database.

That method implements the ozzoo-validation library that provides a very good functionality to validate structs.

On the other hand, the ShoppingCartResponse is implemented to map the object returned from the domain to the API response.

/app/api/dto/product.go

package dto

import (
 validation "github.com/go-ozzo/ozzo-validation/v4"
 uuid "github.com/satori/go.uuid"
)

type ProductReq struct {
 Name           string     `json:"name"`
 Quantity       int        `json:"quantity"`
 Description    string     `json:"description"`
 ShoppingCartID *uuid.UUID `json:"shoppingCartId"`
}

func (p ProductReq) Validate() error {
 return validation.ValidateStruct(&p,
  validation.Field(&p.Name, validation.Required),
  validation.Field(&p.Quantity, validation.Required, validation.Min(1)),
  validation.Field(&p.Description, validation.Required),
  validation.Field(&p.ShoppingCartID, validation.Required),
 )
}

type ProductResp struct {
 ID             *uuid.UUID `json:"id,omitempty"`
 Name           string     `json:"name"`
 Quantity       int        `json:"quantity"`
 Description    string     `json:"description"`
 ShoppingCartID *uuid.UUID `json:"shoppingCartId"`
}

func NewProductResponse(
 id *uuid.UUID,
 name string,
 quantity int,
 description string,
 shoppingCartID *uuid.UUID,
) ProductResp {
 return ProductResp{
  ID:             id,
  Name:           name,
  Quantity:       quantity,
  Description:    description,
  ShoppingCartID: shoppingCartID,
 }
}

type ProductQuantity struct {
 Quantity int `json:"quantity"`
}

func (pq ProductQuantity) Validate() error {
 return validation.ValidateStruct(&pq,
  validation.Field(&pq.Quantity, validation.Required, validation.Min(1)),
 )
}

Explaining the code: This is the code required to define the Request and Response structures used by the product endpoints.

In this file, we have defined three structs (in your applications each one could be defined in a separate file) ProductQuantity, ProductReq, ProductResp.

As similar done for the shopping cart, the structures related to the request objects define their own validation function.

After defining the DTOs, the next step in the context of the “api” package is to implement the handler functionality. To achieve this, we will create a sub-package named “handler.” This sub-package aids in maintaining a structured and organized codebase.

The handler functions defined in this step are called by the/app/api/routes.go, which means that we going to have one function for each one of the defined routes.

To define the handlers we are implementing gin-gonic, each handler functions must follow a specific signature that includes a parameter of type *gin.Context, representing the context of the current request:

func myHandler(c *gin.Context) {
...
}

Let’s analyze each of the handles for “shopping Cart” and “products”.

/app/api/handler/product/handler.go

package product_handler

import (
 "errors"
 "net/http"

 "github.com/gin-gonic/gin"
 "github.com/go-microservices/shopping-cart-service/internal/app/api/dto"
 "github.com/go-microservices/shopping-cart-service/internal/domain/product"
 template_errors "github.com/go-microservices/shopping-cart-service/internal/shared/error"
 uuid "github.com/satori/go.uuid"
 "github.com/sirupsen/logrus"
)

type ProductService interface {
 Create(product product.Product) error
 GetAll(shoppingCartId uuid.UUID) ([]product.Product, error)
 Delete(productId uuid.UUID) error
 UpdateQuantity(productID uuid.UUID, quantity int) error
}

type productHandler struct {
 logger         *logrus.Logger
 productService ProductService
}

func NewProductHandler(productService ProductService, logger *logrus.Logger) productHandler {
 return productHandler{
  logger:         logger,
  productService: productService,
 }
}

// Create godoc
//
// @Summary        Adds products to shopping cart
// @Description    Add product to shopping cart
// @Tags           Products
// @Accept         json
// @Param          request body    dto.ProductReq  true    "shopping cart info"
// @Success        201
// @Failure        400 {object}    template_errors.TemplateError
// @Failure        500 {object}    template_errors.TemplateError
// @Router         /product [post]
func (p productHandler) Create(c *gin.Context) {
 p.logger.Info("On Create product handler")

 var input dto.ProductReq
 if err := c.BindJSON(&input); err != nil {
  p.logger.Errorf("On Create product handler - error: %v", err)
  c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewBadRequestError("bad request", err))
  return
 }

 if err := input.Validate(); err != nil {
  p.logger.Errorf("On Create product handler - invalid request: %v", err)
  c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewValidationError("invalid params", err))
  return
 }

 newProduct := product.NewProduct(input.Name, input.Quantity, input.Description, input.ShoppingCartID)
 err := p.productService.Create(newProduct)
 var errWraping template_errors.TemplateError
 if errors.As(err, &errWraping) {
  p.logger.Errorf("On Create product handler - error: %v", errWraping.Message)
  c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
  return
 }
 if err != nil {
  p.logger.Errorf("On Create product handler - error creating new record: %v", err)
  c.AbortWithStatusJSON(http.StatusInternalServerError, template_errors.NewErrorResponse(err.Error()))
  return
 }

 c.JSON(http.StatusCreated, nil)
}

// Get godoc
//
// @Summary        Get shopping cart products
// @Description    List products in shopping cart
// @Tags           Products
// @Produce        json
// @Param          shoppingCartId  path        string  true    "shopping cart ID"
// @Success        202             {array}     dto.ProductResp
// @Failure        400             {object}    template_errors.TemplateError
// @Failure        404             {object}    template_errors.TemplateError
// @Failure        500             {object}    template_errors.TemplateError
// @Router         /product/{shoppingCartId} [get]
func (p productHandler) Get(c *gin.Context) {
 p.logger.Info("On List products handler")

 id, err := uuid.FromString(c.Param("shoppingCartId"))
 if err != nil {
  p.logger.Errorf("On List products handler: %v", err)
  c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewErrorResponse(err.Error()))
  return
 }

 products, err := p.productService.GetAll(id)
 var errWraping template_errors.TemplateError
 if errors.As(err, &errWraping) {
  p.logger.Errorf("On List products handler - error: %v", errWraping.Message)
  c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
  return
 }
 if err != nil {
  p.logger.Errorf("On List products handler - error: %v", err)
  c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
  return
 }

 productsResp := []dto.ProductResp{}
 for _, p := range products {
  productsResp = append(productsResp, dto.NewProductResponse(p.ID, p.Name, p.Quantity, p.Description, p.ShoppingCartID))
 }

 c.JSON(http.StatusOK, productsResp)
}

// Delete godoc
//
// @Summary        Delete shopping cart products by ID
// @Description    Delete products in shopping cart
// @Tags           Products
// @Param          productId   path    string  true    "product ID"
// @Success        202
// @Failure        400 {object}    template_errors.TemplateError
// @Failure        404 {object}    template_errors.TemplateError
// @Failure        500 {object}    template_errors.TemplateError
// @Router         /product/{productId} [delete]
func (p productHandler) Delete(c *gin.Context) {
 p.logger.Info("On Delete product handler")

 id, err := uuid.FromString(c.Param("productId"))
 if err != nil {
  p.logger.Errorf("On Delete product handler: %v", err)
  c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewErrorResponse(err.Error()))
  return
 }

 err = p.productService.Delete(id)
 var errWraping template_errors.TemplateError
 if errors.As(err, &errWraping) {
  p.logger.Errorf("On Delete product handler - error: %v", err)
  c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
  return
 }
 if err != nil {
  p.logger.Errorf("On Delete product quantity handler - error: %v", err)
  c.AbortWithStatusJSON(http.StatusInternalServerError, template_errors.NewErrorResponse(err.Error()))
  return
 }

 c.JSON(http.StatusOK, nil)
}

// Update godoc
//
// @Summary        Update shopping cart products
// @Description    Update product quatity in shopping cart
// @Tags           Products
// @Accept         json
// @Param          productId   path    string              true    "Product ID"
// @Param          request     body    dto.ProductQuantity true    "Product quantity request"
// @Success        202
// @Failure        400 {object}    template_errors.TemplateError
// @Failure        404 {object}    template_errors.TemplateError
// @Failure        500 {object}    template_errors.TemplateError
// @Router         /product/{productId} [patch]
func (p productHandler) Update(c *gin.Context) {
 p.logger.Info("On Update product quantity handler")

 id, err := uuid.FromString(c.Param("productId"))
 if err != nil {
  p.logger.Errorf("On Update product quantity handler error: %v", err)
  c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewErrorResponse(err.Error()))
  return
 }

 var input dto.ProductQuantity
 if err := c.BindJSON(&input); err != nil {
  p.logger.Errorf("On Update product quantity handler - error: %v", err)
  c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewBadRequestError("bad request", err))
  return
 }

 if err := input.Validate(); err != nil {
  p.logger.Errorf("On Update product quantity handler - invalid request: %v", err)
  c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewValidationError("invalid params", err))
  return
 }
 err = p.productService.UpdateQuantity(id, input.Quantity)
 var errWraping template_errors.TemplateError
 if errors.As(err, &errWraping) {
  p.logger.Errorf("On Update product quantity - error: %v", errWraping.Message)
  c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
  return
 }
 if err != nil {
  p.logger.Errorf("On Update product quantity handler - error: %v", err)
  c.AbortWithStatusJSON(http.StatusInternalServerError, template_errors.NewErrorResponse(err.Error()))
  return
 }

 c.JSON(http.StatusAccepted, nil)
}

Explaining the code: In this file, we will define all the logic associated with the product handlers. Each function corresponds to an endpoint, and it’s worth noting that we declare an interface that defines the service methods. This interface is utilized because the productHandler structure includes a property used to access these service methods.

We’ll skip the comments preceding each function for now, as we’ll find their descriptions in the OpenAPI documentation section.

When working with the gin-gonic framework to define handler functions, we have access to a variety of functions that make easy request management. Let’s explore the generalities of these functions:

The handler function.

The logic defined in the function will be executed by each request to the related endpoint, for example: Request to POST /create, will execute the logic defined on func create(..){…}.

Those functions just need to define the logic related to the API handler layer, validations, object serializations, and responses. If there are more complex logic it should be split into a layer named service. That layer is defined as part of the application layer.

Taking the example of our use case, there isn’t a lot of logic, then we can work the validations in the handler function and then make the call to the DOMAIN service, and process the returned values.

Taking as an example the create, we are defining the domain logic inside of its own package and then the handler calls the domain.service function, for example: err := p.productService.Create(newProduct).

When the handler functions are related to the request that sent data, after getting the json and parsing it in a golang struct, the struct properties should be validated. Here is where the DTOs are implemented in our use case.

By using the defined DTOs we are able to execute the function defined Validate, which ensures that all the values read from the request are the expected, and then continue with the logic. If some of the values are not correct the handler will respond with a Validation error.

Another important point to mention in the handler is the use of the error wrapper. This allows us to map and propagate the correct errors from other layers. The error wrapping will be analyzed further in the section related to the error package.

The handler functions should validate and handle all the errors propagated in the other layers, by doing we ensure that the code returns the correct response and HTTP status.

It is crucial to not only validate incoming data but also effectively handle errors propagated from underlying layers. This robust error handling ensures that your code consistently returns the appropriate HTTP status codes and meaningful responses, contributing to the overall reliability and user-friendliness of your application.

/app/api/handler/shopping_cart/handler.go

Similar to products, the handler defined as the shopping cart is under the same specifications.

package shopping_cart_handler

import (
 "errors"
 "net/http"

 "github.com/gin-gonic/gin"
 "github.com/go-microservices/shopping-cart-service/internal/app/api/dto"
 shoppingcart "github.com/go-microservices/shopping-cart-service/internal/domain/shopping_cart"
 template_errors "github.com/go-microservices/shopping-cart-service/internal/shared/error"
 uuid "github.com/satori/go.uuid"
 "github.com/sirupsen/logrus"

 _ "github.com/swaggo/swag/example/celler/httputil"
)

type ShoppingCartService interface {
 Create(spc shoppingcart.ShoppingCart) error
 GetByUserID(userId *uuid.UUID) (*shoppingcart.ShoppingCart, error)
}

type shoppingCartHandler struct {
 logger              *logrus.Logger
 shoppingCartService ShoppingCartService
}

func NewShoppingCartHandler(
 shoppingCartService ShoppingCartService, logger *logrus.Logger) shoppingCartHandler {
 return shoppingCartHandler{
  logger:              logger,
  shoppingCartService: shoppingCartService,
 }
}

// Create godoc
//
// @Summary  Creates Shopping Cart
// @Description Create a new Shopping Cart
// @Tags   Shopping Cart
// @Accept   json
// @Param   request body dto.ShoppingCartRequest true "shopping cart info"
// @Success  201
// @Failure  400 {object} template_errors.TemplateError
// @Failure  500 {object} template_errors.TemplateError
// @Router   /shopping-cart [post]
func (sc shoppingCartHandler) Create(c *gin.Context) {
 sc.logger.Info("On create sopping cart")

 var input dto.ShoppingCartRequest
 if err := c.BindJSON(&input); err != nil {
  sc.logger.Errorf("On create shopping cart handler - error: %v", err)
  c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewBadRequestError("bad request", err))
  return
 }

 if err := input.Validate(); err != nil {
  sc.logger.Errorf("On create shopping cart handler - invalid request: %v", err)
  c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewValidationError("invalid values", err))
  return
 }

 err := sc.shoppingCartService.Create(shoppingcart.NewShoppingCart(nil, input.UserID))
 var errWraping template_errors.TemplateError
 if errors.As(err, &errWraping) {
  sc.logger.Errorf("On create shopping cart handler - error: %v", errWraping.Message)
  c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
  return
 }
 if err != nil {
  sc.logger.Errorf("On create shopping cart handler - error creating new record: %v", err)
  c.AbortWithStatusJSON(http.StatusInternalServerError, template_errors.NewErrorResponse(err.Error()))
  return
 }

 c.JSON(http.StatusCreated, nil)
}

// Get godoc
//
// @Summary  Get User Shopping Cart
// @Description Returns a Shopping Cart
// @Tags   Shopping Cart
// @Produce  json
// @Param   id path  string true "User ID"
// @Success  200 {object} dto.ShoppingCartResponse
// @Failure  400 {object} template_errors.TemplateError
// @Failure  404 {object} template_errors.TemplateError
// @Failure  500 {object} template_errors.TemplateError
// @Router   /shopping-cart/{id} [get]
func (sc shoppingCartHandler) Get(c *gin.Context) {
 sc.logger.Info("On get shorpping cart")

 id, err := uuid.FromString(c.Param("userID"))
 if err != nil {
  sc.logger.Errorf("On get shorpping cart handler - error: %v", err)
  c.AbortWithStatusJSON(http.StatusBadRequest, template_errors.NewErrorResponse(err.Error()))
  return
 }

 resp, err := sc.shoppingCartService.GetByUserID(&id)
 var errWraping template_errors.TemplateError
 if errors.As(err, &errWraping) {
  sc.logger.Errorf("On get shorpping cart handler - error: %v", errWraping.Message)
  c.AbortWithStatusJSON(errWraping.Status, template_errors.NewErrorResponse(errWraping.Message))
  return
 }
 if err != nil {
  sc.logger.Errorf("On get shorpping cart handler: %v", err)
  c.AbortWithStatusJSON(http.StatusInternalServerError, template_errors.NewErrorResponse(err.Error()))
  return
 }

 c.JSON(http.StatusOK, dto.NewShoppingCartResponse(resp.ID, resp.UserID))
}

The main.go

/cmd/main.go

package main

import (
    "log"
    api "github.com/go-microservices/shopping-cart-service/internal/app/api"
    _ "github.com/go-microservices/shopping-cart-service/docs"
    _ "github.com/swaggo/files"
    _ "github.com/swaggo/gin-swagger"
)
//  @title          Shopping Cart API
//  @version        1.0
//  @description    APIs to manage sopping cart.
//  @termsOfService http://swagger.io/terms/
//  @contact.name   API Support
//  @contact.url    http://www.swagger.io/support
//  @contact.email  [email protected]
//  @license.name   Apache 2.0
//  @license.url    http://www.apache.org/licenses/LICENSE-2.0.html
// @host        localhost:8080
// @BasePath    /api/v1

func main() {
    log.Println("starting shopping-cart services")
    api.LoadApiV1()
}

Explaining the code: Let us focus on the main function content, we going to explain the comments related to OpenAPI in the next step.

The code here is simple, here it has just started up the web service by calling “api.LoadApiV1”, that function was defined in the previous steps when the API package was defined.

Next readings …

Wait for Chapter 14 “Coding our Microservice (Part 4) — Explaining the shared package”.

Golang
Software Development
Software Engineering
Software Architecture
Computer Science
Recommended from ReadMedium