Terraform on Naver Cloud - Analyzing of the Go API

To develop my own API in the form of a Terraform Provider, an API is required first. Therefore, this post will cover the development of a Go CRUD API for Terraform Provider development.

[GitHub Repository - Product API in Go] πŸ”— https://github.com/inpyu/product-api-go

[GitHub Repository - Terraform Provider Cafe Package Client] πŸ”— https://github.com/inpyu/hashicups-client-go

You can refer to the completed API in the above GitHub repositories.

πŸ”— https://github.com/hashicorp-demoapp/hashicups-client-go

The API is structured as shown in the diagram. This post will first cover the development of the Product API before moving on to the Client and Provider development.

Project Structure

.
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ LICENSE
β”œβ”€β”€ Makefile
β”œβ”€β”€ README.md
β”œβ”€β”€ blueprint
β”‚   β”œβ”€β”€ README.md
β”‚   β”œβ”€β”€ config.json
β”‚   └── stack.hcl
β”œβ”€β”€ client
β”‚   β”œβ”€β”€ http.go
β”‚   └── http_test.go
β”œβ”€β”€ conf.json
β”œβ”€β”€ config
β”‚   β”œβ”€β”€ config.go
β”‚   └── config_test.go
β”œβ”€β”€ data
β”‚   β”œβ”€β”€ connection.go
β”‚   β”œβ”€β”€ mockcon.go
β”‚   └── model
β”‚       β”œβ”€β”€ cafe.go
β”‚       β”œβ”€β”€ cafe_test.go
β”‚       β”œβ”€β”€ coffee.go
β”‚       β”œβ”€β”€ coffee_test.go
β”‚       β”œβ”€β”€ ingredient.go
β”‚       β”œβ”€β”€ ingredient_test.go
β”‚       β”œβ”€β”€ order.go
β”‚       β”œβ”€β”€ order_test.go
β”‚       β”œβ”€β”€ token.go
β”‚       β”œβ”€β”€ token_test.go
β”‚       β”œβ”€β”€ user.go
β”‚       └── user_test.go
β”œβ”€β”€ database
β”‚   β”œβ”€β”€ Dockerfile
β”‚   └── products.sql
β”œβ”€β”€ docker_compose
β”‚   β”œβ”€β”€ conf.json
β”‚   └── docker-compose.yml
β”œβ”€β”€ functional_tests
β”‚   β”œβ”€β”€ features
β”‚   β”‚   β”œβ”€β”€ basic_functionality.feature
β”‚   β”‚   β”œβ”€β”€ coffee.feature
β”‚   β”‚   β”œβ”€β”€ order.feature
β”‚   β”‚   └── user.feature
β”‚   β”œβ”€β”€ helper_test.go
β”‚   └── main_test.go
β”œβ”€β”€ go.mod
β”œβ”€β”€ go.sum
β”œβ”€β”€ handlers
β”‚   β”œβ”€β”€ auth.go
β”‚   β”œβ”€β”€ cafe.go
β”‚   β”œβ”€β”€ coffee.go
β”‚   β”œβ”€β”€ coffee_test.go
β”‚   β”œβ”€β”€ health.go
β”‚   β”œβ”€β”€ ingredients.go
β”‚   β”œβ”€β”€ ingredients_test.go
β”‚   β”œβ”€β”€ order.go
β”‚   β”œβ”€β”€ order_test.go
β”‚   β”œβ”€β”€ user.go
β”‚   └── user_test.go
β”œβ”€β”€ main
β”œβ”€β”€ main.go
β”œβ”€β”€ open_api.yaml
β”œβ”€β”€ telemetry
β”‚   └── telemetry.go
└── test_docker
    └── docker-compose.yaml
.
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ LICENSE
β”œβ”€β”€ Makefile
β”œβ”€β”€ README.md
β”œβ”€β”€ blueprint
β”‚   β”œβ”€β”€ README.md
β”‚   β”œβ”€β”€ config.json
β”‚   └── stack.hcl
β”œβ”€β”€ client
β”‚   β”œβ”€β”€ http.go
β”‚   └── http_test.go
β”œβ”€β”€ conf.json
β”œβ”€β”€ config
β”‚   β”œβ”€β”€ config.go
β”‚   └── config_test.go
β”œβ”€β”€ data
β”‚   β”œβ”€β”€ connection.go
β”‚   β”œβ”€β”€ mockcon.go
β”‚   └── model
β”‚       β”œβ”€β”€ cafe.go
β”‚       β”œβ”€β”€ cafe_test.go
β”‚       β”œβ”€β”€ coffee.go
β”‚       β”œβ”€β”€ coffee_test.go
β”‚       β”œβ”€β”€ ingredient.go
β”‚       β”œβ”€β”€ ingredient_test.go
β”‚       β”œβ”€β”€ order.go
β”‚       β”œβ”€β”€ order_test.go
β”‚       β”œβ”€β”€ token.go
β”‚       β”œβ”€β”€ token_test.go
β”‚       β”œβ”€β”€ user.go
β”‚       └── user_test.go
β”œβ”€β”€ database
β”‚   β”œβ”€β”€ Dockerfile
β”‚   └── products.sql
β”œβ”€β”€ docker_compose
β”‚   β”œβ”€β”€ conf.json
β”‚   └── docker-compose.yml
β”œβ”€β”€ functional_tests
β”‚   β”œβ”€β”€ features
β”‚   β”‚   β”œβ”€β”€ basic_functionality.feature
β”‚   β”‚   β”œβ”€β”€ coffee.feature
β”‚   β”‚   β”œβ”€β”€ order.feature
β”‚   β”‚   └── user.feature
β”‚   β”œβ”€β”€ helper_test.go
β”‚   └── main_test.go
β”œβ”€β”€ go.mod
β”œβ”€β”€ go.sum
β”œβ”€β”€ handlers
β”‚   β”œβ”€β”€ auth.go
β”‚   β”œβ”€β”€ cafe.go
β”‚   β”œβ”€β”€ coffee.go
β”‚   β”œβ”€β”€ coffee_test.go
β”‚   β”œβ”€β”€ health.go
β”‚   β”œβ”€β”€ ingredients.go
β”‚   β”œβ”€β”€ ingredients_test.go
β”‚   β”œβ”€β”€ order.go
β”‚   β”œβ”€β”€ order_test.go
β”‚   β”œβ”€β”€ user.go
β”‚   └── user_test.go
β”œβ”€β”€ main
β”œβ”€β”€ main.go
β”œβ”€β”€ open_api.yaml
β”œβ”€β”€ telemetry
β”‚   └── telemetry.go
└── test_docker
    └── docker-compose.yaml

Understanding the Directory Structure

  • client directory
    • client/: Contains HTTP client-related code.
    • http.go: Implements the HTTP client.
  • data directory
    • data/: Contains database connection and data model files.
    • connection.go: Handles database connections.
    • mockcon.go: Implements a mock database connection.
    • model/: Contains data models for different entities.
      • cafe.go, coffee.go, ingredient.go, order.go, token.go, user.go: Define models for cafes, coffee, ingredients, orders, tokens, and users.
  • handlers directory
    • handlers/: Contains handlers that process HTTP requests.
    • auth.go: Handles authentication-related requests.
    • cafe.go, coffee.go, ingredients.go, order.go, user.go: Handle requests related to cafes, coffee, ingredients, orders, and users.

Key Components

  • main.go:
    • Functions like a controller in a Spring application.
    • Defines routes and request handlers.
  • client/http.go:
    • Handles interaction with external APIs.
    • Processes incoming HTTP requests.
  • data/model:
    • Defines entity structures.
  • data/connection.go:
    • Implements database connection and business logic.
  • handlers:
    • Processes HTTP requests on the server side.
    • Interacts with the database to return or manipulate data.

While Client and Handler might seem similar, they serve different purposes:

  • Client: Gathers data and interacts with external APIs.
  • Handler: Processes requests and interacts with the database on the server side.

main.go

Now, let’s examine main.go, its functionalities, and the code implemented in this file.

Router and Middleware Setup

The application uses the gorilla/mux package to configure the router and the cors package to handle CORS settings.

r := mux.NewRouter()
r.Use(hckit.TracingMiddleware)
r.Use(cors.New(cors.Options{
    AllowedOrigins: []string{"*"},
    AllowedMethods: []string{"POST", "GET", "OPTIONS", "PUT", "DELETE"},
    AllowedHeaders: []string{"Accept", "content-type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization"},
}).Handler)

Registering Handlers

Various handlers are registered for different endpoints. Each handler is initialized with a database connection and a logger.

healthHandler := handlers.NewHealth(t, logger, db)
r.Handle("/health", healthHandler).Methods("GET")

coffeeHandler := handlers.NewCoffee(db, logger)
r.Handle("/coffees", coffeeHandler).Methods("GET")

ingredientsHandler := handlers.NewIngredients(db, logger)
r.Handle("/coffees/{id:[0-9]+}/ingredients", ingredientsHandler).Methods("GET")

userHandler := handlers.NewUser(db, logger)
r.HandleFunc("/signup", userHandler.SignUp).Methods("POST")

orderHandler := handlers.NewOrder(db, logger)
r.Handle("/orders", authMiddleware.IsAuthorized(orderHandler.GetUserOrders)).Methods("GET")

cafeHandler := handlers.NewCafe(db, logger)
r.Handle("/cafes", cafeHandler).Methods("GET")
r.Handle("/cafes/{id:[0-9]+}", cafeHandler).Methods("GET")

Explanation of Handlers

  • Health Check Handler
    • /health: Checks the application’s health status.
  • Coffee Handler
    • /coffees: Retrieves a list of coffee products.
    • Only authenticated users can create a coffee product.
  • Ingredient Handler
    • Retrieves the list of ingredients for a specific coffee product.
  • User Handler
    • Handles user signup and login.
  • Order Handler
    • Authenticated users can view, create, update, and delete orders.

data/coffee.go

Now, let’s analyze the model package, which defines coffee-related data and handles JSON serialization and deserialization.

Coffees Type

The Coffees type is a slice of Coffee objects.

FromJSON Method

func (c *Coffees) FromJSON(data io.Reader) error {
    de := json.NewDecoder(data)
    return de.Decode(c)
}
  • Deserializes JSON data into a Coffees type.
  • Reads JSON data from an io.Reader and decodes it into a Coffees object.

ToJSON Method

func (c *Coffees) ToJSON() ([]byte, error) {
    return json.Marshal(c)
}

Serializes a Coffees object into JSON format.

Coffee Struct

type Coffee struct {
	ID          int                `db:"id" json:"id"`
	Name        string             `db:"name" json:"name"`
	Teaser      string             `db:"teaser" json:"teaser"`
	Collection  string             `db:"collection" json:"collection"`
	Origin      string             `db:"origin" json:"origin"`
	Color       string             `db:"color" json:"color"`
	Description string             `db:"description" json:"description"`
	Price       float64            `db:"price" json:"price"`
	Image       string             `db:"image" json:"image"`
	CreatedAt   string             `db:"created_at" json:"-"`
	UpdatedAt   string             `db:"updated_at" json:"-"`
	DeletedAt   sql.NullString     `db:"deleted_at" json:"-"`
	Ingredients []CoffeeIngredient `json:"ingredients"`
}
  • Represents a coffee product stored in the database.
  • Uses json and db tags for serialization and database mapping.

This analysis covered the API structure and key components. The next steps involve implementing the client and Terraform Provider.

Categories:

Updated:

Leave a comment