flamingo.me/flamingo-commerce/v3@v3.11.0/checkout/infrastructure/contextstore/redis.go (about) 1 package contextstore 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/gob" 7 "errors" 8 "fmt" 9 "runtime" 10 "time" 11 12 "flamingo.me/flamingo/v3/core/healthcheck/domain/healthcheck" 13 "flamingo.me/flamingo/v3/framework/flamingo" 14 "github.com/gomodule/redigo/redis" 15 "go.opencensus.io/trace" 16 17 "flamingo.me/flamingo-commerce/v3/checkout/domain/placeorder/process" 18 ) 19 20 type ( 21 // Redis saves all contexts in a simple map 22 Redis struct { 23 pool *redis.Pool 24 logger flamingo.Logger 25 ttl time.Duration 26 } 27 ) 28 29 var ( 30 _ process.ContextStore = new(Redis) 31 _ healthcheck.Status = &Redis{} 32 // ErrNoRedisConnection is returned if the underlying connection is erroneous 33 ErrNoRedisConnection = errors.New("no redis connection, see healthcheck") 34 ) 35 36 func init() { 37 gob.Register(process.Context{}) 38 } 39 40 // Inject dependencies 41 func (r *Redis) Inject( 42 logger flamingo.Logger, 43 cfg *struct { 44 MaxIdle int `inject:"config:commerce.checkout.placeorder.contextstore.redis.maxIdle"` 45 IdleTimeoutMilliseconds int `inject:"config:commerce.checkout.placeorder.contextstore.redis.idleTimeoutMilliseconds"` 46 Network string `inject:"config:commerce.checkout.placeorder.contextstore.redis.network"` 47 Address string `inject:"config:commerce.checkout.placeorder.contextstore.redis.address"` 48 Database int `inject:"config:commerce.checkout.placeorder.contextstore.redis.database"` 49 TTL string `inject:"config:commerce.checkout.placeorder.contextstore.redis.ttl"` 50 }) *Redis { 51 r.logger = logger 52 if cfg != nil { 53 var err error 54 r.ttl, err = time.ParseDuration(cfg.TTL) 55 if err != nil { 56 panic("can't parse commerce.checkout.placeorder.contextstore.redis.ttl") 57 } 58 59 r.pool = &redis.Pool{ 60 MaxIdle: cfg.MaxIdle, 61 IdleTimeout: time.Duration(cfg.IdleTimeoutMilliseconds) * time.Millisecond, 62 TestOnBorrow: func(c redis.Conn, t time.Time) error { 63 _, err := c.Do("PING") 64 return err 65 }, 66 Dial: func() (redis.Conn, error) { 67 return redis.Dial(cfg.Network, cfg.Address, redis.DialDatabase(cfg.Database)) 68 }, 69 } 70 runtime.SetFinalizer(r, func(r *Redis) { r.pool.Close() }) // close all connections on destruction 71 } 72 73 return r 74 } 75 76 // Store a given context 77 func (r *Redis) Store(ctx context.Context, key string, placeOrderContext process.Context) error { 78 _, span := trace.StartSpan(ctx, "checkout/Redis/Store") 79 defer span.End() 80 81 conn := r.pool.Get() 82 defer conn.Close() 83 if conn.Err() != nil { 84 r.logger.Error("placeorder/contextstore/Store:", conn.Err()) 85 return ErrNoRedisConnection 86 } 87 88 buffer := new(bytes.Buffer) 89 err := gob.NewEncoder(buffer).Encode(placeOrderContext) 90 if err != nil { 91 return err 92 } 93 _, err = conn.Do( 94 "SETEX", 95 key, 96 int(r.ttl.Round(time.Second).Seconds()), 97 buffer, 98 ) 99 100 return err 101 } 102 103 // Get a stored context 104 func (r *Redis) Get(ctx context.Context, key string) (process.Context, bool) { 105 _, span := trace.StartSpan(ctx, "checkout/Redis/Get") 106 defer span.End() 107 108 conn := r.pool.Get() 109 defer conn.Close() 110 if conn.Err() != nil { 111 r.logger.Error("placeorder/contextstore/Get:", conn.Err()) 112 } 113 114 content, err := redis.Bytes(conn.Do("GET", key)) 115 if err != nil { 116 return process.Context{}, false 117 } 118 119 buffer := bytes.NewBuffer(content) 120 decoder := gob.NewDecoder(buffer) 121 pctx := new(process.Context) 122 err = decoder.Decode(pctx) 123 if err != nil { 124 r.logger.Error(fmt.Sprintf("context in key %q is not decodable: %s", key, err)) 125 } 126 127 return *pctx, err == nil 128 } 129 130 // Delete a stored context, nop if it doesn't exist 131 func (r *Redis) Delete(ctx context.Context, key string) error { 132 _, span := trace.StartSpan(ctx, "checkout/Redis/Delete") 133 defer span.End() 134 135 conn := r.pool.Get() 136 defer conn.Close() 137 if conn.Err() != nil { 138 r.logger.Error("placeorder/contextstore/Delete:", conn.Err()) 139 return ErrNoRedisConnection 140 } 141 142 _, err := conn.Do("DEL", key) 143 144 return err 145 } 146 147 // Status handles the health check of redis 148 func (r *Redis) Status() (alive bool, details string) { 149 conn := r.pool.Get() 150 defer conn.Close() 151 152 _, err := conn.Do("PING") 153 if err == nil { 154 return true, "redis for place order context store replies to PING" 155 } 156 157 return false, err.Error() 158 }