Optimize your application architecture and implement the Redis cache
Project Address: Https://github.com/EDDYCJY/go ...
If it helps, you're welcome to a Star.
Objective
Before I thought, a lot of tutorials or examples of code design are one step (and no problem)
But does the actual user really understand why? Wrestling, with the content of today's chapter, I think the actual experience of the impression will be more profound
Planning
In this section, the following functions are organized:
- Extraction, layered business logic: Reduce the logic of API methods within routers/*.go (but this article is not layered repository, this logic is not heavy)
- Increased fault tolerance: judgment on Gorm errors
- Redis cache: Increase cache settings for interfaces that get data classes
- Reduce redundant code duplication
What's the problem?
In the planning phase we found a problem, which is the current pseudo code:
if ! HasErrors() { if ExistArticleByID(id) { DeleteArticle(id) code = e.SUCCESS } else { code = e.ERROR_NOT_EXIST_ARTICLE }} else { for _, err := range valid.Errors { logging.Info(err.Key, err.Message) }}c.JSON(http.StatusOK, gin.H{ "code": code, "msg": e.GetMsg(code), "data": make(map[string]string),})
If you add the functional logic in the plan, the pseudo-code will become:
if ! HasErrors() { exists, err := ExistArticleByID(id) if err == nil { if exists { err = DeleteArticle(id) if err == nil { code = e.SUCCESS } else { code = e.ERROR_XXX } } else { code = e.ERROR_NOT_EXIST_ARTICLE } } else { code = e.ERROR_XXX }} else { for _, err := range valid.Errors { logging.Info(err.Key, err.Message) }}c.JSON(http.StatusOK, gin.H{ "code": code, "msg": e.GetMsg(code), "data": make(map[string]string),})
If the logic of the cache is also added, the subsequent iterations slowly continue, will it become the same?
Now that we have found the problem, we should solve this code structure problem in time, and write the code clearly, beautifully, easy to read and easy to change is also an important indicator
How to change?
In the left ear mouse article, this type of code is called "Arrow-type" code, there are several questions:
1, my monitor is not wide enough, the arrow-type code indentation too ruthless, I need to pull back and forth horizontal scroll bar, which makes me read the code, quite uncomfortable
2, in addition to the width and length, some code of the If-else in the If-else in the If-else code too much, read in the middle you do not know the middle of the code is through what layer of check to come here
All in all, "arrow code" if too many nested, the code is too long, it will be quite easy to maintain the code of the people (including themselves) lost in the code, because you see the inner layer of the code, you do not know what the front layer of the condition of the judgment is what, the code is how to run here, so, The arrow-type code is very difficult to maintain and debug.
In simple terms, let the code go back first, make all the wrong judgments, and then the normal code is all that's left.
(Note: This paragraph refers to how to reconstruct the "arrow-type" code from Brother Mouse, it is recommended to savor)
Implement
This project will optimize and cache the existing code, and hopefully you'll acquire the method and optimize it elsewhere.
Step One: Complete the infrastructure for Redis (you need to install Redis first)
The second step: The existing code is disassembled, layered (will not be affixed to the specific steps of the code, I hope you can actually do a wave, deepen understanding)
Redis
First, the configuration
Open the Conf/app.ini file and add the configuration:
...[redis]Host = 127.0.0.1:6379Password =MaxIdle = 30MaxActive = 30IdleTimeout = 200
Second, Cache Prefix
Open the pkg/e directory, create a new cache.go, write the content:
package econst ( CACHE_ARTICLE = "ARTICLE" CACHE_TAG = "TAG")
Third, Cache Key
(1), open service directory, new Cache_service/article.go
Write content: Portal
(2), open service directory, new Cache_service/tag.go
Write content: Portal
This part is mainly to write a method to get the cache key, directly refer to the portal to
Iv. Redis Toolkit
Open the PKG directory, create a new gredis/redis.go, write the content:
Package Gredisimport ("Encoding/json" "Time" "Github.com/gomodule/redigo/redis" "Github.com/eddycjy/go-gin-ex Ample/pkg/setting ") var redisconn *redis. Poolfunc Setup () error {Redisconn = &redis. pool{maxidle:setting. Redissetting.maxidle, maxactive:setting. Redissetting.maxactive, idletimeout:setting. Redissetting.idletimeout, Dial:func () (Redis. Conn, error) {c, err: = Redis. Dial ("TCP", setting. Redissetting.host) If err! = Nil {return nil, err} if setting. Redissetting.password! = "" {if _, err: = C.do ("AUTH", setting. Redissetting.password); Err! = Nil {c.close () return nil, err}} Retu RN C, err}, Testonborrow:func (C Redis. Conn, T time. Time) Error {_, Err: = C.do ("PING") Return err},} return Nil}func Set (key string, Da Ta Interface{}, Time int) (bool, error) {conn: = Redisconn.get () defer Conn. Close () value, err: = json. Marshal (data) if err! = Nil {return false, err} reply, err: = Redis. Bool (Conn. Do ("SET", key, Value)) Conn. Do ("EXPIRE", key, time) return reply, Err}func Exists (key string) BOOL {conn: = Redisconn.get () defer Conn. Close () exists, err: = Redis. Bool (Conn. Do ("EXISTS", key)) if err! = Nil {return false} return Exists}func Get (key String) ([]byte, error) { Conn: = Redisconn.get () defer Conn. Close () reply, err: = Redis. Bytes (Conn. Do ("GET", key)) if err! = Nil {return nil, err} return reply, Nil}func Delete (Key string) (bool, error) {conn: = Redisconn.get () defer Conn. Close () return Redis. Bool (Conn. Do ("DEL", key))}func Likedeletes (key string) error {conn: = Redisconn.get () defer Conn. Close () keys, err: = Redis. Strings (Conn. Do ("KEYS", "*" +key+ "*")) if err! = Nil {return err} for _, Key: = Range Keys {_, Err = Delete (key) if err! = Nil {return err}} return n Il
Here we do some basic functional encapsulation
1, set Redisconn as Redis. Pool (connection pooling) and configures some of its parameters:
- Dial: Provides a function to create and configure application connections
- Testonborrow: Optional Application check Health feature
- Maxidle: Maximum number of idle connections
- Maxactive: The maximum number of connections allowed to allocate at a given time (no limit when zero)
- IdleTimeout: will remain idle for a given time and close the connection if the time limit is reached (no limit when zero)
2. Package Basic method
The file contains Set, Exists, Get, Delete, likedeletes to support the current business logic, and in it involves a method such as:
(1) RedisConn.Get()
: Get an active connection in the connection pool
(2) conn.Do(commandName string, args ...interface{})
: Send a command to the Redis server and return the reply received
(3) redis.Bool(reply interface{}, err error)
: Returns the command to a Boolean value
(4) redis.Bytes(reply interface{}, err error)
: Return command to Bytes
(5) redis.Strings(reply interface{}, err error)
: Return command to []string
There are a number of similar approaches in Redigo, original aim, and it is recommended that you familiarize yourself with their usage rules and Redis commands
Redis can be called happily until you get here. In addition to space restrictions, this piece of in-depth explanation will be opened separately!
Disassembly, layering
In previous planning, several methods were introduced to optimize our application structure.
- Error early return
- Unified Return method
- Pumping out Service, mitigating routers/api logic, layering
- Increase Gorm error judgment to make the error prompt more explicit (add internal error code)
Writing the Return method
To get the error back in advance, C.json's intrusion is unavoidable, but can make it more volatile, and when does the XML change from one day to the other?
1, open the PKG directory, create a new app/request.go, write the contents of the file:
package appimport ( "github.com/astaxie/beego/validation" "github.com/EDDYCJY/go-gin-example/pkg/logging")func MarkErrors(errors []*validation.Error) { for _, err := range errors { logging.Info(err.Key, err.Message) } return}
2, open the PKG directory, create a new app/response.go, write the contents of the file:
package appimport ( "github.com/gin-gonic/gin" "github.com/EDDYCJY/go-gin-example/pkg/e")type Gin struct { C *gin.Context}func (g *Gin) Response(httpCode, errCode int, data interface{}) { g.C.JSON(httpCode, gin.H{ "code": httpCode, "msg": e.GetMsg(errCode), "data": data, }) return}
If you want to change this way, you can change the method in the app package directly.
Modify existing Logic
Open Routers/api/v1/article.go, and look at the code after modifying the GetArticle method:
func GetArticle(c *gin.Context) { appG := app.Gin{c} id := com.StrTo(c.Param("id")).MustInt() valid := validation.Validation{} valid.Min(id, 1, "id").Message("ID必须大于0") if valid.HasErrors() { app.MarkErrors(valid.Errors) appG.Response(http.StatusOK, e.INVALID_PARAMS, nil) return } articleService := article_service.Article{ID: id} exists, err := articleService.ExistByID() if err != nil { appG.Response(http.StatusOK, e.ERROR_CHECK_EXIST_ARTICLE_FAIL, nil) return } if !exists { appG.Response(http.StatusOK, e.ERROR_NOT_EXIST_ARTICLE, nil) return } article, err := articleService.Get() if err != nil { appG.Response(http.StatusOK, e.ERROR_GET_ARTICLE_FAIL, nil) return } appG.Response(http.StatusOK, e.SUCCESS, article)}
Here are a few worth of changes, mainly in the internal added error return, if there is an error directly back. Additionally layered, the business logic is within the service layer, and the ROUTERS/API (Controller) is significantly reduced, and the code is more intuitive
For example, the articleService.Get()
method under Service/article_service:
func (a *Article) Get() (*models.Article, error) { var cacheArticle *models.Article cache := cache_service.Article{ID: a.ID} key := cache.GetArticleKey() if gredis.Exists(key) { data, err := gredis.Get(key) if err != nil { logging.Info(err) } else { json.Unmarshal(data, &cacheArticle) return cacheArticle, nil } } article, err := models.GetArticle(a.ID) if err != nil { return nil, err } gredis.Set(key, article, 3600) return article, nil}
For the Gorm error return setting, only the modified models/article.go are as follows:
func GetArticle(id int) (*Article, error) { var article Article err := db.Where("id = ? AND deleted_on = ? ", id, 0).First(&article).Related(&article.Tag).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err } return &article, nil}
Habitually increased. Error, control most of the errors. It is also important to note that in Gorm, finding records is an "error".
At last
Obviously, this chapter isn't about you knocking on my series. My topic for you is "implement the Redis cache and optimize the existing business logic code"
Enable it to continually adapt to business development, make code clearer and more readable, and layered and structured
If there is doubt, you can go to go-gin-example to see how I write, how do you write, and what are the advantages, disadvantages, a wave of each other?
Reference
Sample code for this series
Catalog of this series
- Gin Practice Serial One Golang introduction and environment installation
- Gin Practice Two Build blog API ' s (i)
- Gin Practice three Build blog API ' s (ii)
- Gin Practice Four Build blog API ' s (iii)
- Gin Practice Five Build blog API ' s (iv)
- Gin Practice Six Build blog API ' s (v)
- Gin Practice serial Seven Golang graceful restart HTTP service
- Gin practice serial Eight for it plus swagger
- Gin practice nine deploying Golang applications to Docker
- Gin Practice serial 10 Custom GORM callbacks
- Gin Practice Serial 11 Cron timed tasks
- Gin Practice Serial 12 optimize configuration structure and implement image upload
- Gin Practice Golang Cross-compiling
Recommended Reading
- How to refactor the "arrow-type" code