Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release version 1.0.0 of the plugin #1

Merged
merged 2 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
233 changes: 231 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,231 @@
# caches
Caches Plugin
# Gorm Caches

Gorm Caches plugin using database request reductions (easer), and response caching mechanism provide you an easy way to optimize database performance.

## Features

- Database request reduction. If three identical requests are running at the same time, only the first one is going to be executed, and its response will be returned for all.
- Database response caching. By implementing the Cacher interface, you can easily setup a caching mechanism for your database queries.
- Supports all databases that are supported by gorm itself.

## Install

```bash
go get -u github.com/go-gorm/caches
```

## Usage

Configure the `easer`, and the `cacher`, and then load the plugin to gorm.

```go
package main

import (
"fmt"
"sync"

"github.com/go-gorm/caches"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)

func main() {
db, _ := gorm.Open(
mysql.Open("DATABASE_DSN"),
&gorm.Config{},
)
cachesPlugin := &caches.Caches{Conf: &caches.Config{
Easer: true,
Cacher: &yourCacherImplementation{},
}}
_ = db.Use(cachesPlugin)
}
```

## Easer Example

```go
package main

import (
"fmt"
"sync"
"time"

"github.com/go-gorm/caches"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)

type UserRoleModel struct {
gorm.Model
Name string `gorm:"unique"`
}

type UserModel struct {
gorm.Model
Name string
RoleId uint
Role *UserRoleModel `gorm:"foreignKey:role_id;references:id"`
}

func main() {
db, _ := gorm.Open(
mysql.Open("DATABASE_DSN"),
&gorm.Config{},
)

cachesPlugin := &caches.Caches{Conf: &caches.Config{
Easer: true,
}}

_ = db.Use(cachesPlugin)

_ = db.AutoMigrate(&UserRoleModel{})

_ = db.AutoMigrate(&UserModel{})

adminRole := &UserRoleModel{
Name: "Admin",
}
db.FirstOrCreate(adminRole, "Name = ?", "Admin")

guestRole := &UserRoleModel{
Name: "Guest",
}
db.FirstOrCreate(guestRole, "Name = ?", "Guest")

db.Save(&UserModel{
Name: "ktsivkov",
Role: adminRole,
})
db.Save(&UserModel{
Name: "anonymous",
Role: guestRole,
})

var (
q1Users []UserModel
q2Users []UserModel
)
wg := &sync.WaitGroup{}
wg.Add(2)
go func() {
db.Model(&UserModel{}).Joins("Role").Find(&q1Users, "Role.Name = ? AND Sleep(1) = false", "Admin")
wg.Done()
}()
go func() {
time.Sleep(500 * time.Millisecond)
db.Model(&UserModel{}).Joins("Role").Find(&q2Users, "Role.Name = ? AND Sleep(1) = false", "Admin")
wg.Done()
}()
wg.Wait()

fmt.Println(fmt.Sprintf("%+v", q1Users))
fmt.Println(fmt.Sprintf("%+v", q2Users))
}
```

## Cacher Example

```go
package main

import (
"fmt"
"sync"

"github.com/go-gorm/caches"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)

type UserRoleModel struct {
gorm.Model
Name string `gorm:"unique"`
}

type UserModel struct {
gorm.Model
Name string
RoleId uint
Role *UserRoleModel `gorm:"foreignKey:role_id;references:id"`
}

type dummyCacher struct {
store *sync.Map
}

func (c *dummyCacher) init() {
if c.store == nil {
c.store = &sync.Map{}
}
}

func (c *dummyCacher) Get(key string) interface{} {
c.init()
val, _ := c.store.Load(key)
return val
}

func (c *dummyCacher) Store(key string, val interface{}) error {
c.init()
c.store.Store(key, val)
return nil
}

func main() {
db, _ := gorm.Open(
mysql.Open("DATABASE_DSN"),
&gorm.Config{},
)

cachesPlugin := &caches.Caches{Conf: &caches.Config{
Cacher: &dummyCacher{},
}}

_ = db.Use(cachesPlugin)

_ = db.AutoMigrate(&UserRoleModel{})

_ = db.AutoMigrate(&UserModel{})

adminRole := &UserRoleModel{
Name: "Admin",
}
db.FirstOrCreate(adminRole, "Name = ?", "Admin")

guestRole := &UserRoleModel{
Name: "Guest",
}
db.FirstOrCreate(guestRole, "Name = ?", "Guest")

db.Save(&UserModel{
Name: "ktsivkov",
Role: adminRole,
})
db.Save(&UserModel{
Name: "anonymous",
Role: guestRole,
})

var (
q1Users []UserModel
q2Users []UserModel
)

db.Model(&UserModel{}).Joins("Role").Find(&q1Users, "Role.Name = ? AND Sleep(1) = false", "Admin")
fmt.Println(fmt.Sprintf("%+v", q1Users))

db.Model(&UserModel{}).Joins("Role").Find(&q2Users, "Role.Name = ? AND Sleep(1) = false", "Admin")
fmt.Println(fmt.Sprintf("%+v", q2Users))
}
```

## License

MIT license.

## Easer
The easer is an adjusted version of the [ServantGo](https://github.com/ktsivkov/servantgo) library to fit the needs of this plugin.
6 changes: 6 additions & 0 deletions cacher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package caches

type Cacher interface {
Get(key string) interface{}
Store(key string, val interface{}) error
}
48 changes: 48 additions & 0 deletions cacher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package caches

import (
"errors"
"sync"
)

type cacherMock struct {
store *sync.Map
}

func (c *cacherMock) init() {
if c.store == nil {
c.store = &sync.Map{}
}
}

func (c *cacherMock) Get(key string) interface{} {
c.init()
val, _ := c.store.Load(key)
return val
}

func (c *cacherMock) Store(key string, val interface{}) error {
c.init()
c.store.Store(key, val)
return nil
}

type cacherStoreErrorMock struct {
store *sync.Map
}

func (c *cacherStoreErrorMock) init() {
if c.store == nil {
c.store = &sync.Map{}
}
}

func (c *cacherStoreErrorMock) Get(key string) interface{} {
c.init()
val, _ := c.store.Load(key)
return val
}

func (c *cacherStoreErrorMock) Store(key string, val interface{}) error {
return errors.New("store-error")
}
Loading