Skip to content

source-code-template/go-elastic-search-generic-sample

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-elastic-search-generic-sample

How to run

Clone the repository

git clone git@github.com:source-code-template/go-elastic-search-generic-sample.git cd go-elastic-search-generic-sample.git

To run the application

go run main.go

Architecture

Simple Layer Architecture

Layer Architecture

Layer Architecture with full features

Layer Architecture with standard features: config, health check, logging, middleware log tracing

  • Build the search model at http handler
  • Build dynamic SQL for search
    • Build SQL for paging by page index (page) and page size (limit)
    • Build SQL to count total of records

Search users: Support both GET and POST

POST /users/search

Request: POST /users/search

In the below sample, search users with these criteria:

  • get users of page "1", with page size "20"
  • email="tony": get users with email starting with "tony"
  • dateOfBirth between "min" and "max" (between 1953-11-16 and 1976-11-16)
  • sort by phone ascending, id descending
{ "page": 1, "limit": 20, "sort": "phone,-id", "email": "tony", "dateOfBirth": { "min": "1953-11-16T00:00:00+07:00", "max": "1976-11-16T00:00:00+07:00" } }
GET /users/search?page=1&limit=2&email=tony&dateOfBirth.min=1953-11-16T00:00:00+07:00&dateOfBirth.max=1976-11-16T00:00:00+07:00&sort=phone,-id

In this sample, search users with these criteria:

  • get users of page "1", with page size "20"
  • email="tony": get users with email starting with "tony"
  • dateOfBirth between "min" and "max" (between 1953-11-16 and 1976-11-16)
  • sort by phone ascending, id descending

Response:

  • total: total of users, which is used to calculate numbers of pages at client
  • list: list of users
{ "list": [ { "id": "ironman", "username": "tony.stark", "email": "tony.stark@gmail.com", "phone": "0987654321", "dateOfBirth": "1963-03-24T17:00:00Z" } ], "total": 1 }

API Design

Common HTTP methods

  • GET: retrieve a representation of the resource
  • POST: create a new resource
  • PUT: update the resource
  • PATCH: perform a partial update of a resource, refer to core and elasticsearch
  • DELETE: delete a resource

API design for health check

To check if the service is available.

Request: GET /health

Response:

{ "status": "UP", "details": { "elasticsearch": { "status": "UP" } } }

API design for users

Resource: users

Get all users

Request: GET /users

Response:

[ { "id": "spiderman", "username": "peter.parker", "email": "peter.parker@gmail.com", "phone": "0987654321", "dateOfBirth": "1962-08-25T16:59:59.999Z" }, { "id": "wolverine", "username": "james.howlett", "email": "james.howlett@gmail.com", "phone": "0987654321", "dateOfBirth": "1974-11-16T16:59:59.999Z" } ]

Get one user by id

Request: GET /users/:id

GET /users/wolverine

Response:

{ "id": "wolverine", "username": "james.howlett", "email": "james.howlett@gmail.com", "phone": "0987654321", "dateOfBirth": "1974-11-16T16:59:59.999Z" }

Create a new user

Request: POST /users

{ "id": "wolverine", "username": "james.howlett", "email": "james.howlett@gmail.com", "phone": "0987654321", "dateOfBirth": "1974-11-16T16:59:59.999Z" }

Response: 1: success, 0: duplicate key, -1: error

1

Update one user by id

Request: PUT /users/:id

PUT /users/wolverine
{ "username": "james.howlett", "email": "james.howlett@gmail.com", "phone": "0987654321", "dateOfBirth": "1974-11-16T16:59:59.999Z" }

Response: 1: success, 0: not found, -1: error

1

Patch one user by id

Perform a partial update of user. For example, if you want to update 2 fields: email and phone, you can send the request body of below.

Request: PATCH /users/:id

PATCH /users/wolverine
{ "email": "james.howlett@gmail.com", "phone": "0987654321" }

Response: 1: success, 0: not found, -1: error

1

Problems for patch

If we pass a struct as a parameter, we cannot control what fields we need to update. So, we must pass a map as a parameter.

type UserService interface { Update(ctx context.Context, user *User) (int64, error) Patch(ctx context.Context, user map[string]interface{}) (int64, error) }

We must solve the problem:

  • At http handler layer, we must convert the user struct to map, with json format, and make sure the nested data types are passed correctly.

Solutions for patch

  • At http handler layer, we use core-go/core, to convert the user struct to map, to make sure we just update the fields we need to update
import server "github.com/core-go/core" func (h *UserHandler) Patch(w http.ResponseWriter, r *http.Request) { var user User userType := reflect.TypeOf(user) _, jsonMap := sv.BuildMapField(userType) body, _ := sv.BuildMapAndStruct(r, &user) json, er1 := sv.BodyToJson(r, user, body, ids, jsonMap, nil) result, er2 := h.service.Patch(r.Context(), json) if er2 != nil { http.Error(w, er2.Error(), http.StatusInternalServerError) return } respond(w, result) }

Delete a new user by id

Request: DELETE /users/:id

DELETE /users/wolverine

Response: 1: success, 0: not found, -1: error

1

Common libraries

  • core-go/health: include HealthHandler, HealthChecker, MongoHealthChecker
  • core-go/config: to load the config file, and merge with other environments (SIT, UAT, ENV)
  • core-go/log: log and log middleware

core-go/health

To check if the service is available, refer to core-go/health

Request: GET /health

Response:

{ "status": "UP", "details": { "elasticsearch": { "status": "UP" } } }

To create health checker, and health handler

cfg := elasticsearch.Config{Addresses: []string{root.ElasticSearch.Url}} client, _ := elasticsearch.NewClient(cfg) elasticSearchChecker := es.NewHealthChecker(client) healthHandler := health.NewHealthHandler(elasticSearchChecker)

To handler routing

 r := mux.NewRouter() r.HandleFunc("/health", healthHandler.Check).Methods("GET")

core-go/config

To load the config from "config.yml", in "configs" folder

package main import "github.com/core-go/config" type Root struct { ElasticSearch ElasticSearchConfig `mapstructure:"elastic_search"` } type ElasticSearchConfig struct { Url string `mapstructure:"url"` Username string `mapstructure:"username"` Password string `mapstructure:"password"` } func main() { var conf Root err := config.Load(&conf, "configs/config") if err != nil { panic(err) } }

core-go/log & core-go/log/middleware

import ( "github.com/core-go/config" "github.com/core-go/log"	mid "github.com/core-go/log/middleware" "github.com/gorilla/mux" ) func main() { var conf app.Root config.Load(&conf, "configs/config") r := mux.NewRouter() log.Initialize(conf.Log) r.Use(mid.BuildContext) logger := mid.NewStructuredLogger() r.Use(mid.Logger(conf.MiddleWare, log.InfoFields, logger)) r.Use(mid.Recover(log.ErrorMsg)) }

To configure to ignore the health check, use "skips":

middleware: skips: /health