Skip to content

Commit

Permalink
Merge pull request #14 from xufeisofly/feature/cache-for-src-value
Browse files Browse the repository at this point in the history
feat: add cache for getting src field value
  • Loading branch information
iFaceless authored Jun 1, 2020
2 parents 7b288ce + da66a74 commit 42565e2
Show file tree
Hide file tree
Showing 15 changed files with 603 additions and 25 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,43 @@ To learn more about [portal](https://github.com/iFaceless/portal), please read t
1. When dumping to multiple schemas, portal will do it concurrently if any fields in the schema are tagged with `protal:"async"`.
1. You can always disable concurrency strategy with option `portal.DisableConcurrency()`.

# Cache Strategy
1. Cache is implemented in the field level when `portal.SetCache(portal.DefaultCache)` is configured.
1. Cache will be disabled by tagging the fields with `portal:"disablecache"` or by defining a `PortalDisableCache() bool` meth for the schema struct, or by a `portal.DisableCache()` option setting while dumping.
1. Cache keys are in determined by the model structs' addresses, which means the invalidation is dependent on the time GC revokes the model struct. So please avoid the snippet as follows.
```go
type User struct {
ID int
}

func (u *User) Score() int {
return getScoreByID(u.ID)
}

type UserSchema struct {
Score int `portal:"attr:Score"`
}

var user = User{ID: 1}
var ret UserSchema

portal.Dump(&ret, &user) // => user_1's score
// user.ID = 2
portal.Dump(&ret, &user) // => will still get user_1's score
```
A better way might be
```go
portal.Dump(&ret, &user) // => user_1's score
user2 := User{ID: 2}
portal.Dump(&ret, &user) // => user_2's score

// or like this
portal.Dump(&ret, &user) // => user_1's score
user = copy(user)
user.ID = 2
portal.Dump(&ret, &user) // => user_2's score
````

# Core APIs

```go
Expand Down
37 changes: 37 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,43 @@ func main() {
1. 当序列化 Schema 列表时,会分析 Schema 中有无标记了 `async` 的字段,如果存在的话,则使用并发填充策略;否则只在当前 goroutine 中完成序列化;
1. 可以在 Dump 时添加 `portal.DisableConcurrency()` 禁用并发序列化的功能。

# 缓存策略控制
1.`portal.SetCache(portal.DefaultCache)` 被设置之后,字段维度的缓存会被开启;
1. 以下情况下缓存会被禁用。Schema 字段中标记了 `portal:"diablecache"` 的 Tag; 被序列化的 Schema 定义了 `DisableCache() bool` 方法;序列化时设置了 `portal.DisableCache()` 选项;
1. 缓存 key 是由 model 结构体的地址决定,GC 回收 model 时缓存就会失效,因此要避免如下写法;
```go
type User struct {
ID int
}

func (u *User) Score() int {
return getScoreByID(u.ID)
}

type UserSchema struct {
Score int `portal:"attr:Score"`
}

var user = User{ID: 1}
var ret UserSchema

portal.Dump(&ret, &user) // => user_1's grade
// user.ID = 2
portal.Dump(&ret, &user) // => will still get user_1's grade
```
A better way might be
```go
portal.Dump(&ret, &user) // => user_1's score
user2 := User{ID: 2}
portal.Dump(&ret, &user) // => user_2's score

// 或者下面这样
portal.Dump(&ret, &user) // => user_1's score
user = copy(user)
user.ID = 2
portal.Dump(&ret, &user) // => user_2's score
````

# 核心 APIs

```go
Expand Down
82 changes: 82 additions & 0 deletions USERGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ It will override the default tag settings defined in your struct.

See example [here](https://github.com/iFaceless/portal/blob/65aaa0b537fd13607bd4d45c1016c1689dc53beb/_examples/todo/main.go#L36).

### Disable cache for a single dump
```go
portal.Dump(&dst, &src, portal.DisableCache())
```

## Special Tags
### Load Data from Model's Attribute: `attr`
```go
Expand Down Expand Up @@ -150,6 +155,27 @@ type UserSchema struct {
```

### Disable Cache for Field: `disablecache`
```go
type Student struct {
ID int
}
type info struct {
Name string
Height int
}
func(s *Student) Info() info {
return &info{Name: "name", Height: 180}
}
type StudentSchema struct {
Name string `json:"name" portal:"attr:Info.Name,disablecache"`
Height int `json:"height" portal:"attr:Info.Height,disablecache"`
}
```

### Set Default Value for Field: `default`

Only works for types: pointer/slice/map. For basic types (integer, string, bool), default value will be converted and set to field directly. For complex types (eg. map/slice/pointer to custom struct), set default to `AUTO_INIT`, portal will initialize field to its zero value.
Expand Down Expand Up @@ -215,3 +241,59 @@ func (t *Timestamp) UnmarshalJSON(data []byte) error {
return nil
}
```

## Use Cache to speed up

Values from functions will be cached for schema fields tagged by `ATTR` and `METH`. You can choose not to use it by disabling the cache of a single field, a whole schema, or simple for one time `Dump`.

```go
type StudentModel struct {
ID int
}
// the Meta might be costful
func (m *StudentModel) Meta() *meta {
time.Sleep(100 * time.Millisecond)
return &meta{ID: 1}
}
type StudentSchema struct {
Name string `json:"name" portal:"attr:Meta.Name"`
Height int `json:"height" portal:"attr:Meta.Height"`
Subjects []*SubjectSchema `json:"subjects" portal:"nested;async"`
// NextID is a Set method, which must not be cached
NextID int `json:"next_id" portal:"meth:SetNextID;disablecache"` // no using cache
}
func (s *StudentSchema) SetNextID(m *StudentModel) int {
return m.ID + 1
}
type SubjectSchema struct {
Name string `json:"name" portal:"meth:GetInfo.Name"`
Teacher string `json:"teacher" portal:"meth:GetInfo.Teacher"`
}
// If you don't want SubjectSchema to use cache, forbidden it by implementing a PortalDisableCache method.
func (s *StudentSchema) PortalDisableCache() bool { return true }
func (s *StudentSchema) GetInfo(m *StudentModel) info {
return info{Name: "subject name", teacher: "teacher"}
}
var m = StudentModel{ID: 1}
var s StudentSchema
// setup cache
portal.SetCache(portal.DefaultCache)
defer portal.SetCache(nil)
// Not using cache for this dump
portal.Dump(&s, &m, portal.DisableCache())
// StudentSchema.NextID and whole SubjectSchema are not using cache
portal.Dump(&s, &m)
```

Incidently, portal.Cacher interface{} are expected to be implemented if you'd like to replace the portal.DefaultCache and to use your own.
2 changes: 2 additions & 0 deletions _examples/todo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ func main() {

portal.SetMaxPoolSize(1024)
portal.SetDebug(true)
portal.SetCache(portal.DefaultCache)
defer portal.SetCache(nil)

task := model.TaskModel{
ID: 1,
Expand Down
78 changes: 78 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package portal

import (
"context"
"fmt"

"github.com/bluele/gcache"
)

type Cacher interface {
Set(ctx context.Context, key interface{}, value interface{}) error
Get(ctx context.Context, key interface{}) (interface{}, error)
}

type LRUCache struct {
c gcache.Cache
}

func NewLRUCache(size int) *LRUCache {
return &LRUCache{
c: gcache.New(size).LRU().Build(),
}
}

var _ Cacher = (*LRUCache)(nil)

func (lru *LRUCache) Set(_ context.Context, key, value interface{}) error {
return lru.c.Set(key, value)
}

func (lru *LRUCache) Get(_ context.Context, key interface{}) (interface{}, error) {
return lru.c.Get(key)
}

const (
cacheKeyTem = "%s#%s#%s"
defaultLRUSize = 8192
)

var (
DefaultCache = NewLRUCache(defaultLRUSize)
portalCache Cacher
isCacheDisabled = false
)

// SetCache enable cache strategy
func SetCache(c Cacher) {
if c == nil {
isCacheDisabled = true
return
}
isCacheDisabled = false
portalCache = c
}

// genCacheKey generate cache key
// rules: ReceiverName#MethodName#cacheID
// eg. meth:GetName UserSchema#GetName#0xc000498150,
// attr:Name UserModel#Name#0xc000498150
func genCacheKey(ctx context.Context, receiver interface{}, cacheObj interface{}, methodName string) *string {
cacheID := defaultCacheID(cacheObj)

ck := fmt.Sprintf(cacheKeyTem, structName(receiver), methodName, cacheID)
return &ck
}

// defaultCacheID is the addr of src struct
func defaultCacheID(cacheObj interface{}) string {
return fmt.Sprintf("%p", cacheObj)
}

func isCacheKeyValid(cacheKey *string) bool {
return portalCache != nil && cacheKey != nil
}

type cachable interface {
PortalDisableCache() bool
}
Loading

0 comments on commit 42565e2

Please sign in to comment.