Melkeydev / go-blueprint

Go-blueprint allows users to spin up a quick Go project using a popular framework
MIT License
2.07k stars 141 forks source link

[Database Template] Implement Singleton Pattern #209

Closed H0llyW00dzZ closed 1 month ago

H0llyW00dzZ commented 1 month ago

By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

Problem/Feature

This pull request implements the Singleton pattern for database connections, which is useful for reusing connections efficiently.

Description of Changes:

Note: This a fix syntax for example it can be used multipe services such as

dbInstance = &service{
        db:       db,
        authuser: NewServiceAuthUser(db), // Initialize the auth user service
    }

Checklist

H0llyW00dzZ commented 1 month ago

tested live in production:

image

json:

{"status":"up","open_connections":"2","idle":"1","max_lifetime_closed":"0","message":"It's healthy","in_use":"1","wait_count":"0","wait_duration":"0s","max_idle_closed":"0"}
H0llyW00dzZ commented 1 month ago

@briancbarrow how about that MongoDB I've been Implement it ?

H0llyW00dzZ commented 1 month ago

btw the ci error lmao

image

MitchellBerend commented 1 month ago

@H0llyW00dzZ I peaked at your action results real quick (for both #209 and #206) and github seems to be having issues right now.

https://www.githubstatus.com/history

H0llyW00dzZ commented 1 month ago

@H0llyW00dzZ I peaked at your action results real quick (for both #209 and #206) and github seems to be having issues right now.

https://www.githubstatus.com/history

@MitchellBerend it's unstable, look this a weird failure

image

H0llyW00dzZ commented 1 month ago

image

Ujstor commented 1 month ago

It seems that GitHub is having issues with actions. I reran manually, and more checks are passing

https://www.githubstatus.com/history

H0llyW00dzZ commented 1 month ago

It seems that GitHub is having issues with actions. I reran manually, and more checks are passing

https://www.githubstatus.com/history

this a weird

image

H0llyW00dzZ commented 1 month ago

now this one error again lmao

image

MitchellBerend commented 1 month ago

The cache posting issue is resolved in #213 since that removes that action from ci, not sure if this issue is related to yesterday's outage but maybe it's safer to wait for that pr to be merged.

H0llyW00dzZ commented 1 month ago

The cache posting issue is resolved in #213 since that removes that action from ci, not sure if this issue is related to yesterday's outage but maybe it's safer to wait for that pr to be merged.

@MitchellBerend The cache posting issue seems to be a race condition because Node.js cannot catch it. Also I don't recommend running CI/CD on Node.js, which is an interpreted language, especially for running lots of CI tasks like this. Unlike compiled languages that can run directly on the machine, such as Go, Node.js adds an extra layer of interpretation.

Ujstor commented 1 month ago

By the end of the weekend, linter fix will be merged

MitchellBerend commented 1 month ago

@MitchellBerend The cache posting issue seems to be a race condition because Node.js cannot catch it. Also I don't recommend running CI/CD on Node.js, which is an interpreted language, especially for running lots of CI tasks like this. Unlike compiled languages that can run directly on the machine, such as Go, Node.js adds an extra layer of interpretation.

Ye I used their github action without actually looking at the internals but I guess when it's being ran this often node is showing it's limitations.

Ujstor commented 1 month ago

Updated workflow will be used on a new commit. I can't run it manually.

H0llyW00dzZ commented 1 month ago

Updated workflow will be used on a new commit. I can't run it manually.

@Ujstor, @briancbarrow this singleton pattern supports Fiber storage as well and provides perfect scalability for rate limiting.

For example:

package database

import (
    "context"
    "database/sql"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/storage/mysql/v2"

    _ "github.com/go-sql-driver/mysql"
    _ "github.com/joho/godotenv/autoload"
)

type Service interface {
    Health() map[string]string
    // RateLimiter returns the [fiber.Storage] interface for rate limiting.
    RateLimiter() fiber.Storage
}

type service struct {
    db          *sql.DB
    ratelimiter fiber.Storage
}

var (
    dbname     = os.Getenv("DB_DATABASE")
    password   = os.Getenv("DB_PASSWORD")
    username   = os.Getenv("DB_USERNAME")
    port       = os.Getenv("DB_PORT")
    host       = os.Getenv("DB_HOST")
    dbInstance *service
)

func New() Service {
    // Reuse Connection
    if dbInstance != nil {
        return dbInstance
    }

    // Opening a driver typically will not attempt to connect to the database.
    db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", username, password, host, port, dbname))
    if err != nil {
        // This will not be a connection error, but a DSN parse error or
        // another initialization error.
        log.Fatal(err)
    }
    db.SetConnMaxLifetime(0)
    db.SetMaxIdleConns(50)
    db.SetMaxOpenConns(50)

    dbInstance = &service{
        db: db,
        ratelimiter: mysql.New(mysql.Config{
            Db:         db,
            Reset:      false,
            GCInterval: 10 * time.Second,
            Table:      "rate_limiter",
        }), // Initialize the rate limiter storage
    }
    return dbInstance
}

func (s *service) Health() map[string]string {
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()

    err := s.db.PingContext(ctx)
    if err != nil {
        log.Fatalf(fmt.Sprintf("db down: %v", err))
    }

    return map[string]string{
        "message": "It's healthy",
    }
}

// RateLimiter returns the [fiber.Storage] interface for rate limiting.
func (s *service) RateLimiter() fiber.Storage {
    return s.ratelimiter
}

In the database:

Database Table

And it returns nothing when there are no IPs or custom keys, such as API keys, being blocked:

Empty Result

H0llyW00dzZ commented 1 month ago

@H0llyW00dzZ this is a good idea, but again we'd need the change applied to all database options.

@briancbarrow all done, also it supported this https://github.com/Melkeydev/go-blueprint/pull/209#issuecomment-2041578129

H0llyW00dzZ commented 1 month ago

@briancbarrow by the way, for Redis and MongoDB, might not have to set a singleton pattern, I think, because they are both mostly used for caching. Using sync.Once might cause issues because, unlike other databases like MySQL, PostgreSQL, and SQLite, which are stable and can recover from connection issues (I've tested this by killing connections and database servers, and the Go routines can reconnect without using previous connection).

briancbarrow commented 1 month ago

@briancbarrow by the way, for Redis and MongoDB, might not have to set a singleton pattern, I think, because they are both mostly used for caching. Using sync.Once might cause issues because, unlike other databases like MySQL, PostgreSQL, and SQLite, which are stable and can recover from connection issues (I've tested this by killing connections and database servers, and the Go routines can reconnect without using previous connection).

Okay. Will you remove that code from the Redis and Mongo files?

H0llyW00dzZ commented 1 month ago

@briancbarrow done

H0llyW00dzZ commented 1 month ago

Also, note that the singleton pattern is more stable and better suited for traditional SQL databases (e.g., MySQL, PostgreSQL, SQLite) compared to using a pooling pattern. On the other hand, the pooling pattern is more stable and better suited for NoSQL databases such as Redis and MongoDB, which are primarily used for caching.