Using a singleton database class with separate models and services packages - go

My question is similar to How to create singleton DB class in GoLang but I'm having trouble getting it to work with separate models and services packages.
project/lib/database/mysql.go:
package database
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
type Manager struct {
*gorm.DB
}
var Mgr *Manager
func init() {
dsn := MysqlConnectionString("parseTime=true")
tablePrefix := "demo"
var err error
gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
return fmt.Sprintf("%v_%v", tablePrefix, defaultTableName)
}
db, err := gorm.Open("mysql", dsn)
if err != nil {
panic(err)
}
Mgr = &Manager{db}
}
project/lib/models/retailer_keys.go
package models
import (
"fmt"
"project/lib/database"
"time"
)
type RetailerKeysInterface interface {
RetailerKeys() ([]*RetailerKey, error)
}
type DB struct {
database.Manager
}
type RetailerKey struct {
Id int `json:"id"`
RetailerId int `json:"retailer_id"`
Key string `json:"key"`
Enabled *bool `json:"enabled"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
}
func (db *DB) RetailerKeys() ([]*RetailerKey, error) {
var keys []*RetailerKey
if err := db.Find(&keys).Error; err != nil {
return nil, err
}
return keys, nil
}
project/lib/services/retailer_keys.go
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/apigateway"
"gopkg.in/volatiletech/null.v6"
"project/lib/models"
"project/lib/services/api_keys"
)
func GetKeys() ([]*models.RetailerKey, error) {
var q models.RetailerKeysInterface
keys, err := q.RetailerKeys()
if err != nil {
return nil, err
}
return keys, nil
}
func CreateKey(id int) (models.RetailerKey, error) {
...
}
Then be able to use it in my main package like:
package main
import (
"context"
"encoding/json"
// "reflect"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
_ "project/lib/config"
"project/lib/services"
)
func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
statusCode := 200
keys, err := services.GetKeys()
if err != nil {
statusCode = 400
}
body, _ := json.Marshal(keys)
return events.APIGatewayProxyResponse{
Body: string(body),
StatusCode: statusCode,
}, nil
}
...
I'd like to be able to embed the relevant subset of the Manager type in my models.
EDIT:
Edited the question/code based on feedback in comments.
This gives me an error: runtime error: invalid memory address or nil pointer dereference.

I was definitely misunderstanding interfaces in go. After going through A Tour of Go it started to become clearer how it all fits together.
This is that I ended up doing for anyone that is going through the same thing. I'll leave the original question up so you can see the differences.
project/lib/database/mysql.go:
package database
import (
"fmt"
"log"
_ "github.com/go-sql-driver/mysql" // Needed for gorm
"github.com/jinzhu/gorm"
)
var Manager *gorm.DB
func init() {
var err error
dsn := MysqlConnectionString("parseTime=true")
tablePrefix := "qcommerce"
gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
return fmt.Sprintf("%v_%v", tablePrefix, defaultTableName)
}
Manager, err = gorm.Open("mysql", dsn)
if err != nil {
log.Fatal(err)
}
if err := Manager.DB().Ping(); err != nil {
log.Fatal(err)
}
}
project/lib/models/retailer_keys.go
package models
import (
"project/lib/database"
"time"
)
type QRetailerKeys interface {
Insert() error
Find() error
}
type RetailerKey struct {
ID int `json:"id"`
RetailerID int `json:"retailer_id"`
Retailer Retailer `json:"retailer"`
Key string `json:"key"`
Enabled bool `json:"enabled" gorm:"DEFAULT:true"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
}
// RetailerKeys returns a slice of all keys in table
func RetailerKeys() ([]*RetailerKey, error) {
var keys []*RetailerKey
if err := database.Manager.Find(&keys).Error; err != nil {
return nil, err
}
return keys, nil
}
func (r *RetailerKey) Find() error {
...
}
// Create a new key
func (r *RetailerKey) Create() error {
return database.Manager.Create(&r).Error
}
project/lib/services/retailer_keys.go
package services
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
// "github.com/aws/aws-sdk-go/service/apigateway"
"partners.drinks.com/lib/models"
"partners.drinks.com/lib/services/api_keys"
)
func sessionBuilder() *session.Session {
config := &aws.Config{
Region: aws.String("us-west-2"),
}
session := session.Must(session.NewSession(config))
return session
}
func GetKeys() ([]*models.RetailerKey, error) {
keys, err := models.RetailerKeys()
if err != nil {
return nil, err
}
return keys, nil
}
func CreateKey(id int) (models.RetailerKey, error) {
apikeys := &api_keys.ApiKeyBuilder{}
base64Key := apikeys.GenUUID().GenKey().Base64
var key = models.RetailerKey{
RetailerID: id,
Key: base64Key,
Enabled: func(b bool)
}
if err := key.Create(); err != nil {
return models.RetailerKey{}, err
}
...
return key, nil
}
I use it like:
package main
import (
"context"
"encoding/json"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
_ "partners.drinks.com/lib/config"
"partners.drinks.com/lib/services"
)
func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
statusCode := 200
keys, err := services.GetKeys()
if err != nil {
statusCode = 400
}
body, _ := json.Marshal(keys)
return events.APIGatewayProxyResponse{
Body: string(body),
StatusCode: statusCode,
}, nil
}
...
Thanks to #mkopriva for the linked resources in the comments.

Related

Golang RCP call hanging

I have a back-end app and a front-end, both in Go. I am trying to make them talk via rpc.
back-end main.go
package main
import (
"fmt"
"log"
"net"
"net/http"
"net/rpc"
"time"
)
type Application struct {
config struct {
server struct {
port int
network string
}
}
}
type MusicProject struct {
Id string
CreatedTime time.Time
LastEditedTime time.Time
Title string
Year int
Status string
Description string
ChoirRollup string
}
func main() {
var app Application
app.config.server.port = 5002
app.config.server.network = "tcp"
if err := rpc.Register(new(Application)); err != nil {
log.Fatal(err)
}
rpc.HandleHTTP()
// start the rpc server
log.Println("Starting server port", app.config.server.port)
l, err := net.Listen("tcp", fmt.Sprintf(":%v", app.config.server.port))
if err != nil {
log.Fatal(err)
}
if err = http.Serve(l, nil); err != nil {
log.Fatal(err)
}
}
func (app *Application) GetMusicProjectById(id string, model *MusicProject) error {
musicProject := MusicProject{
Id: id,
CreatedTime: time.Now(),
LastEditedTime: time.Now(),
Title: "Test Project",
Year: 2020,
Status: "Completed",
Description: "Short project Description",
ChoirRollup: "Best Choir",
}
model = &musicProject
return nil
}
front-end rpc call
package main
import (
"log"
"net/rpc"
"time"
)
type MusicProject struct {
Id string
CreatedTime time.Time
LastEditedTime time.Time
Title string
Year int
Status string
Description string
ChoirRollup string
}
func main() {
dial, err := rpc.Dial("tcp", "localhost:5002")
if err != nil {
log.Println(err)
}
projectID := "some_id_123"
var musicProject MusicProject
err = dial.Call("Application.GetMusicProjectById", projectID, &musicProject)
if err != nil {
log.Println(err)
}
log.Println(musicProject)
}
Once the client method is call, the call hanged indefinite with out sending back any error, so quite difficult to debug.
Do you have any suggestion for debugging? Do you see anything wrong in my code?
Thank you for your help!
Your server is serving HTTP:
if err = http.Serve(l, nil); err != nil {
log.Fatal(err)
}
But your client is using straight TCP (HTTP runs over TCP but adds another layer):
err = dial.Call("Application.GetMusicProjectById", projectID, &musicProject)
So to fix your issue you need to change one side or the other. For example change the server to:
rpc.Accept(l)
(alternatively you could use rpc.DialHTTP in the client)
There are a couple of other issues with your code (the main one being the way you set the response in the back-end). The following works for me (have not put much effort into making it tidy or testing!).
server
package main
import (
"fmt"
"log"
"net"
"net/rpc"
"time"
)
type MusicProject struct {
Id string
CreatedTime time.Time
LastEditedTime time.Time
Title string
Year int
Status string
Description string
ChoirRollup string
}
type Application struct {
}
func (app *Application) GetMusicProjectById(id string, model *MusicProject) error {
*model = MusicProject{
Id: id,
CreatedTime: time.Now(),
LastEditedTime: time.Now(),
Title: "Test Project",
Year: 2020,
Status: "Completed",
Description: "Short project Description",
ChoirRollup: "Best Choir",
}
return nil
}
func main() {
const port = 5002
if err := rpc.Register(new(Application)); err != nil {
log.Fatal(err)
}
// start the rpc server
log.Println("Starting server on port:", port)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
log.Fatal(err)
}
rpc.Accept(l)
}
client
package main
import (
"log"
"net/rpc"
"time"
)
type MusicProject struct {
Id string
CreatedTime time.Time
LastEditedTime time.Time
Title string
Year int
Status string
Description string
ChoirRollup string
}
func main() {
dial, err := rpc.Dial("tcp", "localhost:5002")
if err != nil {
log.Fatal(err)
}
projectID := "some_id_123"
var musicProject MusicProject
err = dial.Call("Application.GetMusicProjectById", projectID, &musicProject)
if err != nil {
log.Fatal("error:", err)
}
log.Println(musicProject)
}

Connecting Gorm and Gin Golang

I am trying to create an API with Go, gorm and gin. However, using a direct SQL query did work, but it became too cumbersome and I decided to leverage gorm which is fantastic. However after my implementation, I am unable to get the project up again and keep getting error
panic: runtime error: invalid memory address or nil pointer
dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0xb0
pc=0x137b606]
I have pasted my code with packages here. I would be glad if someone could point me to the issue.
PostgresDoa.go
package postgres
import (
"fmt"
"log"
"net/http"
"github.com/AdieOlami/bookstore_users-api/domain/users"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
)
type Sever struct {
DB *gorm.DB
Router *gin.Engine
}
func (server *Sever) Initialize(Dbdriver, DbUser, DbPassword, DbPort, DbHost, DbName string) {
var err error
if Dbdriver == "mysql" {
DBURL := fmt.Sprintf("%s:%s#tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", DbUser, DbPassword, DbHost, DbPort, DbName)
server.DB, err = gorm.Open(Dbdriver, DBURL)
if err != nil {
fmt.Printf("Cannot connect to %s database", Dbdriver)
log.Fatal("This is the error:", err)
} else {
fmt.Printf("We are connected to the %s database", Dbdriver)
}
}
if Dbdriver == "postgres" {
DBURL := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", DbHost, DbPort, DbUser, DbName, DbPassword)
server.DB, err = gorm.Open(Dbdriver, DBURL)
if err != nil {
fmt.Printf("Cannot connect to %s database", Dbdriver)
log.Fatal("This is the error:", err)
} else {
fmt.Printf("We are connected to the %s database", Dbdriver)
}
}
server.DB.Debug().AutoMigrate(&users.User{}) //database migration
server.Router = gin.Default()
}
func (server *Sever) Run(addr string) {
fmt.Println("Listening to port 8080")
log.Fatal(http.ListenAndServe(addr, server.Router))
}
UserDto.go
package users
import (
"strings"
"github.com/AdieOlami/bookstore_users-api/domain/base"
"github.com/AdieOlami/bookstore_users-api/utils/errors"
"github.com/jinzhu/gorm"
uuid "github.com/satori/go.uuid"
)
type User struct {
base.Base
UserID uuid.UUID `gorm:"type:uuid;column:userId;not null;" json:"userId"`
FirstName string `gorm:"size:255;not null;unique;column:firstName" json:"firstName"`
LastName string `gorm:"size:255;not null;unique;column:lastName" json:"lastName"`
Email string `gorm:"size:100;not null;unique;column:email" json:"email"`
}
func (user *User) Validate() *errors.Error {
user.Email = strings.TrimSpace(strings.ToLower(user.Email))
if user.Email == "" {
return errors.NewBadRequestError("invalid email address")
}
return nil
}
func (user *User) SaveUser(db *gorm.DB) *errors.Error {
var err error
err = db.Debug().Create(&user).Error
if err != nil {
return errors.NewInteralServerError(err.Error())
}
return nil
}
UserService.go
package services
import (
"github.com/AdieOlami/bookstore_users-api/domain/users"
"github.com/AdieOlami/bookstore_users-api/utils/errors"
)
func (server *Server) CreateUser(user users.User) (*users.User, *errors.Error) {
if err := user.Validate(); err != nil {
return nil, err
}
if err := user.SaveUser(server.DB); err != nil {
return nil, err
}
return &user, nil
}
UserController.go
package users
import (
"net/http"
"strconv"
"github.com/AdieOlami/bookstore_users-api/domain/users"
"github.com/AdieOlami/bookstore_users-api/services"
"github.com/AdieOlami/bookstore_users-api/utils/errors"
"github.com/gin-gonic/gin"
)
var (
server = services.Server{}
)
// CREATE USER
func CreateUser(c *gin.Context) {
var user users.User
if err := c.ShouldBindJSON(&user); err != nil {
// TODO: hnadle json error return bad request
err := errors.NewBadRequestError("invalid json body")
c.JSON(err.Status, err)
// fmt.Println(err.Error())
return
}
result, saveErr := server.CreateUser(user)
if saveErr != nil {
// TODO: handle user createing error
c.JSON(saveErr.Status, saveErr)
return
}
c.JSON(http.StatusCreated, result)
}
Routes.go
package app
import (
"github.com/AdieOlami/bookstore_users-api/controllers/users"
"github.com/AdieOlami/bookstore_users-api/database/postgres"
)
var (
server = postgres.Sever{}
)
func initializeRoutes() {
server.Router.POST("/users", users.CreateUser)
}
Application.go
package app
import (
"os"
"github.com/AdieOlami/bookstore_users-api/seed"
_ "github.com/jinzhu/gorm/dialects/mysql" //mysql database driver
_ "github.com/jinzhu/gorm/dialects/postgres" //postgres database driver
)
func StartApplication() {
server.Initialize(os.Getenv("DB_DRIVER"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_PORT"), os.Getenv("DB_HOST"), os.Getenv("DB_NAME"))
seed.Load(server.DB)
server.Run(":8088")
initializeRoutes()
}
in my Main.go
func main() {
app.StartApplication()
}
As far as I can understand from your code. You declared below function in Services package whereas Server object is declared in the Postgres package. This causes dereferencing of pointer server *Server to an invalid address. you have to declare this function in posgres package.
func (server *Server) CreateUser(user users.User) (*users.User, *errors.Error) {
if err := user.Validate(); err != nil {
return nil, err
}
if err := user.SaveUser(server.DB); err != nil {
return nil, err
}
return &user, nil
}

GORM DB Connection on other package

I start learning Go, reading about pointers, and want to split my database connection , and handler function for API. Already tried myself, by following this solution , but when i trying to read data, i am having this error
[2018-06-26 21:59:45] sql: database is closed
this is my source code.
db.go
package db
import (
"fmt"
"github.com/jinzhu/gorm"
"github.com/joho/godotenv"
"os"
)
var Db *gorm.DB
func Open() error {
var err error
_ = godotenv.Load(".env")
dbType := os.Getenv("DB_TYPE")
dbConnString := os.Getenv("DB_CONN_STRING")
Db, err = gorm.Open(dbType, dbConnString)
if err != nil {
fmt.Println(err)
}
Db.LogMode(true)
defer Db.Close()
return err
}
func Close() error {
return Db.Close()
}
person.go
package model
import (
"github.com/jinzhu/gorm"
"github.com/gin-gonic/gin"
"fmt"
"namastra/gin/result"
"namastra/gin/db"
)
type Person struct {
gorm.Model
FirstName string `json:”firstname”`
LastName string `json:”lastname”`
}
/*var db *gorm.DB
var err error*/
func GetPeople(c *gin.Context) {
var people []result.Person
if err := db.Db.Select("ID,first_name,last_name").Find(&people).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, people)
}
}
main.go
package main
import (
"log"
"namastra/gin/handler"
"namastra/gin/model"
"net/http"
"time"
"github.com/adam-hanna/jwt-auth/jwt"
"github.com/gin-gonic/gin"
_ "github.com/jinzhu/gorm/dialects/postgres"
"namastra/gin/db"
)
func main() {
if err := db.Open(); err != nil {
// handle error
panic(err)
}
defer db.Close()
router := gin.Default()
router.Use(gin.Recovery())
private := router.Group("/auth")
....(ommited)
router.GET("/", gin.WrapH(regularHandler))
router.GET("/people/", model.GetPeople)
router.Run("127.0.0.1:3000")
}
Sorry for my bad english, any kind of help is appreciated.
thank you.
edit1: case closed.
solution is by removing
defer Db.Close()
from db.go.
edi2: update some knowledge i learn by working in go project
As start learning GO, usually we put everything on single main.go file, and we think to split the code to multiple files.
That is the time Dependency Injection comes to play.
we can create something like this Env to store the handler.
type Env struct {
db *sql.DB
logger *log.Logger
templates *template.Template
}
and create something like this in models/db.go
package models
import (
"database/sql"
_ "github.com/lib/pq"
)
func NewDB(dataSourceName string) (*sql.DB, error) {
db, err := sql.Open("postgres", dataSourceName)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
return db, nil
}
main.go files
package main
import (
"namastra/gin/models"
"database/sql"
"fmt"
"log"
"net/http"
)
type Env struct {
db *sql.DB
}
func main() {
db, err := models.NewDB("postgres://user:pass#localhost/bookstore")
if err != nil {
log.Panic(err)
}
env := &Env{db: db}
http.HandleFunc("/peoples", env.peoplesIndex)
http.ListenAndServe(":3000", nil)
}
func (env *Env) peoplesIndex(w http.ResponseWriter, r *http.Request) {
# ...
}
and in models/people.go
package models
import "database/sql"
type Book struct {
Isbn string
Title string
Author string
Price float32
}
func AllPeoples(db *sql.DB) ([]*People, error) {
rows, err := db.Query("SELECT * FROM peoples")
if err != nil {
return nil, err
}
defer rows.Close()
# ... ommited for simplicity
}
you can read the full code & explanation in Alex Edwards post

DB.Exec args always results in an error for my placeholder

I have an SQL script with a variable I want to set from Golang.
SET #foo_bar_invitation_id = ?;
SELECT #foo_bar_invitation_id;
I.e. I want to set ? to "foobar". My code:
package main
import (
"io/ioutil"
"log"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type handler struct{ db *sql.DB }
func (h handler) runsql() (err error) {
sqlscript, err := ioutil.ReadFile("script.sql")
if err != nil {
return
}
_, err = h.db.Exec(string(sqlscript), "foobar")
if err != nil {
log.Println(err)
}
return
}
Always results in Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT #foo_bar_invitation_id' at line 2
I test via code generated by gotests:
import (
"database/sql"
"os"
"testing"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
func TestMain(m *testing.M) {
db, _ = sql.Open("mysql", os.Getenv("DSN"))
defer db.Close()
os.Exit(m.Run())
}
func Test_handler_runsql(t *testing.T) {
type fields struct {
db *sql.DB
}
tests := []struct {
name string
fields fields
wantErr bool
}{{
"Check ID can be set",
fields{db: db},
false,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := handler{
db: tt.fields.db,
}
if err := h.runsql(); (err != nil) != tt.wantErr {
t.Errorf("handler.step2runsql() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
My DSN connection string includes ?multiStatements=true&sql_mode=TRADITIONAL.
I'm hoping I just do not understand how DB.Exec args interpolation / placeholder works, but I am finding it difficult to find examples.
The answer was to set interpolate params to true. https://github.com/go-sql-driver/mysql#interpolateparams

gin-gonic json conversion output throws empty array

for the following golang program, i'm not able to get json output using gin-gonic library, if i do fmt.Print variable shows values, but when i convert to c.JSON (inventory) out it shows empty array, what is wrong with my code?
package main
import (
"database/sql"
"os"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
)
func index(c *gin.Context) {
hostname, err := os.Hostname()
checkErr(err)
c.String(200, "v3 "+hostname)
}
func healthz(c *gin.Context) {
c.String(200, "OK")
}
type InventoryItem struct {
id int
productID string
productCost int
productAvailabilty int
productSubcat string
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
/******************* MAIN Function **************/
func main() {
app := gin.Default()
app.GET("/", index)
app.GET("/healthz", healthz)
app.GET("/inventory", fetch)
app.Run(":8000")
}
/******************* End MAIN Function **************/
func fetch(c *gin.Context) {
var (
invt InventoryItem
inventory []InventoryItem
)
connStr := os.Getenv("sql_user") + ":" + os.Getenv("sql_password") + "#tcp(" + os.Getenv("sql_host") + ":3306)/" + os.Getenv("sql_db")
db, err := sql.Open("mysql", connStr)
checkErr(err)
defer db.Close()
rows, err := db.Query("SELECT id,product_id as productID,product_cost as productCost,product_availabilty as productAvailabilty,product_subcat as productSubcat FROM inventory;")
for rows.Next() {
err = rows.Scan(&invt.id, &invt.productID, &invt.productCost, &invt.productAvailabilty, &invt.productSubcat)
checkErr(err)
inventory = append(inventory, invt)
}
checkErr(err)
defer rows.Close()
//fmt.Print(inventory[0].productAvailabilty)
c.JSON(200, inventory)
}
Issue is InventoryItem struct fields have to be exported -
type InventoryItem struct {
ID int `json:"id"`
ProductID string `json:"product_id"`
ProductCost int `json:"product_cost"`
ProductAvailabilty int `json:"product_availability"`
ProductSubcat string `json:"product_subact"`
}
Read more about exported and un-exported fields.

Resources