Go: learning wire dependency injection and cron timing tasks is actually so simple!

Time:2020-11-25

preface

Hi, my little asong is back. I didn’t update it for two weeks. I’m busy recently, plus I’m lazy. So, uh huh, you know. However, the sharing that I brought today is absolutely dry goods, which is also needed in the development of actual projects, so in order to be able to explain clearly, I specially wrote a sample for reference only. This article will focus on the sample learning, which has been uploaded to GitHub and can be downloaded by yourself. OK, stop talking nonsense, I know you can’t wait, let’s get started!!!

wire

Dependency injection

Before introducing wire, let’s take a look at dependency injection. Students who have used spring should be familiar with this. The most common way of inversion of control (IOC) is called dependency injection. A class that places a dependent class as a row parameter in a dependency is called dependency injection. Maybe you don’t understand. In my opinion, the way to construct an object with a large parameter is to accept only one parameter. The construction of his control operation is also handed over to a third party, namely the inversion of control. For example: there is no concept of class in go, which is embodied in the form of structure. Suppose we have a class of oars, and we want to set the ship to have 12 oars, then we can write the following code:

package main

import (
    "fmt"
)

type ship struct {
    pulp *pulp
}
func NewShip(pulp *pulp) *ship{
    return &ship{
        pulp: pulp,
    }
}
type pulp struct {
    count int
}

func Newpulp(count int) *pulp{
    return &pulp{
        count: count,
    }
}

func main(){
    p:= Newpulp(12)
    s := NewShip(p)
    fmt.Println(s.pulp.count)
}

I believe you can see the problem at a glance. Whenever the demand changes, we have to create an object to specify the oar. This code is not easy to maintain. Let’s make a change.

package main

import (
    "fmt"
)

type ship struct {
    pulp *pulp
}
func NewShip(pulp *pulp) *ship{
    return &ship{
        pulp: pulp,
    }
}
type pulp struct {
    count int
}

func Newpulp() *pulp{
    return &pulp{
    }
}

func (c *pulp)set(count int)  {
    c.count = count
}

func (c *pulp)get() int {
    return c.count
}

func main(){
    p:= Newpulp()
    s := NewShip(p)
    s.pulp.set(12)
    fmt.Println(s.pulp.get())
}

The advantage of this code is that it is loosely coupled, easy to maintain, and easy to test. If we change the demand now, we need 20 oars directlys.pulp.set(20)That’s fine.

Use of wire

wireThere are two basic concepts,Provider(constructor) andInjector(injector).ProviderIn fact, it is to create a function. Above usInitializeCronnamelyInjector。 Each injector is actually an object creation and initialization function. In this function, we just need to tellwireWhat type of object to create, the dependency of this type,wireWe will initialize and create a function for the tool to complete.

The purpose of pulling this long is to lead to wire. Although the code above implements dependency injection, it is no problem for us to implement dependency ourselves when the amount of code is small and the structure is not complex. When the relationship between structures becomes very complex, it will be extremely cumbersome and error prone to manually create dependencies and then assemble them 。 So the role of wire comes. Let’s install wire before using it.

$ go get github.com/google/wire/cmd/wire

Execution of this command will result in$GOPATH/binTo generate an executable programwireThis is the code generator. Don’t forget it$GOPATH/binAdd system environment variables$PATHMedium.

Based on the simple example above, let’s first see how to use wire. Let’s first create a wire file with the following contents:

//+build wireinject

package main

import (
    "github.com/google/wire"
)

type Ship struct {
    Pulp *Pulp
}
func NewShip(pulp *Pulp) *Ship {
    return &Ship{
        pulp: pulp,
    }
}
type Pulp struct {
    Count int
}

func NewPulp() *Pulp {
    return &Pulp{
    }
}

func (c *Pulp)set(count int)  {
    c.count = count
}

func (c *Pulp)get() int {
    return c.count
}

func InitShip() *Ship {
    wire.Build(
        NewPulp,
        NewShip,
        )
    return &Ship{}
}

func main(){

}

amongInitShipThe return value of this function is the type of object we need to create,wireJust know the type, it doesn’t matter what you return. In the function, we callwire.Build()Will be createdshipThe dependent type constructor is passed in. So we write it, and now we need to go to the console to execute itwire

$ wire
wire: asong.cloud/Golang_Dream/wire_cron_example/ship: wrote /Users/asong/go/src/asong.cloud/Golang_Dream/wire_cron_example/ship/wire_gen.go

We see wire generated_ gen.go This document:

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

// Injectors from mian.go:

func InitShip() *Ship {
    pulp := NewPulp()
    ship := NewShip(pulp)
    return ship
}

// mian.go:

type Ship struct {
    pulp *Pulp
}

func NewShip(pulp *Pulp) *Ship {
    return &Ship{
        pulp: pulp,
    }
}

type Pulp struct {
    count int
}

func NewPulp() *Pulp {
    return &Pulp{}
}

func (c *Pulp) set(count int) {
    c.count = count
}

func (c *Pulp) get() int {
    return c.count
}

func main() {

}

You can see that the generated file is generated according to the definitionInitShip()This function and dependency binding are also implemented. We can call this function directly, which saves a lot of code to implement dependency binding relationship by ourselves.

be careful:If you are using wire for the first time, you will encounter a problem. The generated code will conflict with the original code because they both define the same functionfunc InitShip() *Ship, so you need to add it in the first line of the original file//+build wireinject, and also have a blank line with the package name to resolve the conflict.

The above example is still simple. Let’s take a look at a little more examples. When we develop the web background in daily life, the code is hierarchical and familiardaoservicecontrollermodelwait. actuallydaoservicecontrollerThere is a calling sequence.controllercallserviceLayer,serviceLayer calldaoLayer, which forms the dependency relationship. In the actual development, we use hierarchical dependency injection to make the code more hierarchical and easy to maintain. So, I’ve written an example, let’s learn how to use it. This adoptioncronTiming task replacementcontrollerInsteadcontrollercronI will explain the timing task later.

//+build wireinject

package wire

import (
    "github.com/google/wire"

    "asong.cloud/Golang_Dream/wire_cron_example/config"
    "asong.cloud/Golang_Dream/wire_cron_example/cron"
    "asong.cloud/Golang_Dream/wire_cron_example/cron/task"
    "asong.cloud/Golang_Dream/wire_cron_example/dao"
    "asong.cloud/Golang_Dream/wire_cron_example/service"
)

func InitializeCron(mysql *config.Mysql)  *cron.Cron{
    wire.Build(
        dao.NewClientDB,
        dao.NewUserDB,
        service.NewUserService,
        task.NewScanner,
        cron.NewCron,
        )
    return &cron.Cron{}
}

Let’s take a look at this code,dao.NewClientDBCreate a*sql.DBObject, depending onmysqlConfiguration file for,dao.NewUserDBCreate a*UserDBObject, he depends on*sql.DBservice.NewUserServiceCreate aUserServiceObject, depending on*UserDBObject,task.NewScannerCreate a*ScannerObject, he depends on*UserServiceObject,cron。NewCronCreate a*CronObject, he depends on*ScannerObject, in fact, there are layers of binding relationship, layer by layer, hierarchical, and easy to maintain code.

Well, here’s the basic usage. Let’s learn about itcron

cron

Basic learning

In our daily development or operation and maintenance, we often encounter some periodic tasks or requirements, such as executing a script every period of time and performing an operation every month. Linux provides us with a convenient way — crontab timing task; crontab is a custom timer, we can use crontab command to execute specified system instructions or shell script scripts at fixed intervals. This time interval is written in a similar way to the cron expression we usually use. The operation is performed periodically by setting the character

Now that we know the basic concepts, let’s introduce cron expressions. There are two commonly used cron formats: one is the “standard” cron format, which consists ofcron linuxSystem program use, there is another kind isQuartz SchedulerUse cron format. One of the differences between the two is supportsecondsField, one is not supported, but the gap is not very big, we will bring it in the following explanationsecondsThis field has no effect.

cronAn expression is a string that consists of6Space divided into7Each domain represents a time meaning. The format is as follows:

[Second] [minute] [hour] [day] [month] [week] [year]

The part of [year] is usually omitted, and actually consists of the first six parts.

The definition of each part is presented in the form of a table

field Is it required Values and ranges wildcard
second yes 0-59 , – * /
branch yes 0-59 , – * /
Time yes 0-23 , – * /
day yes 1-31 , – * ? / L W
month yes 1-12 or Jan-Dec , – * /
week yes 1-7 or sun-sat , – * ? / L #
year no 1970-2099 , – * /

Looking at the range of this value, it is easy to understand. The most difficult to understand is the wildcard. Let’s focus on the wildcard.

  • ,This means that it is executed at more than two time points, if we define it as5,10,15It means that the timing task is executed at the 5th, 10th and 15th points respectively.
  • -This is a better understanding of this is to specify a continuous range in a certain domain, if we define it in the “time” field6-12It is triggered every hour between 6 and 12 o’clock,express6,7,8,9,10,11,12
  • *Represents all values and can be interpreted as “per”. If you set it in the “day” field*, which means that it is triggered every day.
  • ?Indicates that no value is specified. The scenario used is that you don’t need to care about the value of this field currently set. For example: to trigger an operation on the 8th day of each month, but we don’t care about the day of the week, we can set it like this0 0 0 8 * ?
  • /When a field is triggered periodically, the symbol divides the expression in its domain into two parts. The first part is the starting value, which will be reduced by one unit except the second, for example, it is defined on the “second”5/10It means to execute every 10 seconds from the 5th second, while “Min” means to execute every 10 minutes from the 5th second.
  • LIn EnglishLASTIt can only be used in “day” and “week”. It is set in “day” to indicate the last day of the current month (based on the current month, if it is February, it will also be based on whether it is a Runnian); on “week”, it means Saturday, equivalent to “7” or “SAT”. If you add a number before “L”, it means the last one in the data. For example, setting “7L” on “week” means “the last Saturday of the month”
  • WIndicates the closest working day (Monday to Friday) from the specified date. It can only be used in “day” and can only be used after specific numbers. If “15W” is set on the “day”, it means that it is triggered on the working day nearest to the 15th of each month. If the 15th happens to be a Saturday, it will be triggered on the nearest Friday (14th). If the 15th is a weekend, it will be triggered on the nearest Monday (16th). If the 15th happens to be on a weekday (Monday to Friday), it will be triggered on that day. If it is “1W”, it can only be pushed to the next most recent working day of this month, not to the previous month.
  • #Represents the day of the week of a month. It can only be used on the week. For example, “2 × 3” means on the third Tuesday of each month.

After learning about wildcards, let’s look at a few examples:

  • Once a day at 10:00:0 0 10 * * *
  • Every 10 minutes:0 */10 * * *
  • Once a month at 3:00 a.m. on the 1st of every month:0 0 3 1 * ?
  • At 23:30 on the last day of each month:0 30 23 L * ?
  • Once a week at 3 am on Saturdays:0 0 3 ? * L
  • Once at 30 and 50:0 30,50 * * * ?

Cron in go

We learned the basics. Now we want to use timed tasks in go projects. What should we do?githubThere’s a star on it, the higher onecronLibrary, we can use itrobfig/cronThis library develops our timed tasks.

Before learning, let’s install itcron

$ go get -u github.com/robfig/cron/v3

This is a relatively stable version at present. Now this version adopts the standard specification, and the default is NosecondsIf we want to bring fields, we need to create cron objects to specify. Show it later. Let’s start with a simple use:

package main

import (
  "fmt"
  "time"

  "github.com/robfig/cron/v3"
)

func main() {
  c := cron.New()

  c.AddFunc("@every 1s", func() {
    fmt.Println("task start in 1 seconds")
  })

  c.Start()
  select{}
}

Here we usecron.NewCreate a cron object to manage scheduled tasks. callcronObject’sAddFunc()Method to add a scheduled task to the manager.AddFunc()Two parameters are accepted. Parameter 1 specifies the trigger time rule in the form of string. Parameter 2 is a function without parameters, which is called every time it is triggered.@every 1sIt is triggered once per second,@everyAdd a time interval after it to indicate how often it is triggered. for example@every 1hIt is triggered every hour,@every 1m2sIt means to trigger every 1 minute and 2 seconds.time.ParseDuration()All the supported formats can be used here. callc.Start()Start timing cycle.

Pay attention, becausec.Start()Start a new goroutine for loop detection, and we add a line at the end of the codeselect{}Prevent the main goroutine from exiting.

When we define time above, we usecronPre defined time rules: let’s learn about the predefined time rules that he has:

  • @yearlyYou can also write@annually, representing 0:00 on the first day of each year. Equivalent to0 0 1 1 *
  • @monthly: represents 0:00 on the first day of each month. Equivalent to0 0 1 * *
  • @weekly: refers to 0:00 on the first day of a week. Note that the first day is Sunday, that is, the 0:00 that ends on Saturday and starts on Sunday. Equivalent to0 0 * * 0
  • @dailyYou can also write@midnight, which means 0 o’clock every day. Equivalent to0 0 * * *
  • @hourly: indicates the beginning of the hour. Equivalent to0 * * * *

cronIt also supports fixed time interval. The format is as follows:

@every <duration>

It means everydurationTrigger once.<duration>Will calltime.ParseDuration()Function parsing, soParseDurationAll supported formats are OK.

Project usage

Since my own project is to add scheduled tasks through the implementation of the job interface, let’s introduce the use of the job interface. In addition to directly using the nonparametric function as a callback,cronAlso supportedjobInterface:

type Job interface{
    Run()
}

We need to implement this interface. Here, I will use the example I wrote to demonstrate it. Now, my timing task is to periodically scan the data in the DB table. The implementation tasks are as follows:

package task

import (
    "fmt"

    "asong.cloud/Golang_Dream/wire_cron_example/service"
)

type Scanner struct {
    lastID uint64
    user *service.UserService
}

const  (
    ScannerSize = 10
)

func NewScanner(user *service.UserService)  *Scanner{
    return &Scanner{
        user: user,
    }
}

func (s *Scanner)Run()  {
    err := s.scannerDB()
    if err != nil{
        fmt.Errorf(err.Error())
    }
}

func (s *Scanner)scannerDB()  error{
    s.reset()
    flag := false
    for {
        users,err:=s.user.MGet(s.lastID,ScannerSize)
        if err != nil{
            return err
        }
        if len(users) < ScannerSize{
            flag = true
        }
        s.lastID = users[len(users) - 1].ID
        for k,v := range users{
            fmt.Println(k,v)
        }
        if flag{
            return nil
        }
    }
}

func (s *Scanner)reset()  {
    s.lastID = 0
}

Above is the implementationRunMethod, and then we need to call thecronThe addjob method of theScannerObject is added to the timing manager.

package cron

import (
    "github.com/robfig/cron/v3"

    "asong.cloud/Golang_Dream/wire_cron_example/cron/task"
)

type Cron struct {
    Scanner *task.Scanner
    Schedule *cron.Cron
}

func NewCron(scanner *task.Scanner) *Cron {
    return &Cron{
        Scanner: scanner,
        Schedule: cron.New(),
    }
}

func (s *Cron)Start()  error{
    _,err := s.Schedule.AddJob("*/1 * * * *",s.Scanner)
    if err != nil{
        return err
    }
    s.Schedule.Start()
    return nil
}

actuallyAddFunc()Method is also calledAddJob()method. first,cronbe based onfunc()Type defines a new typeFuncJob

// cron.go
type FuncJob func()

Then let’sFuncJobrealizationJobInterface:

// cron.go
func (f FuncJob) Run() {
  f()
}

stayAddFunc()Method, convert the incoming callback toFuncJobType and then callAddJob()method:

func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) {
  return c.AddJob(spec, FuncJob(cmd))
}

Well, we’ll explain the basic use here. Finally, we’ll add the last knowledge point, that is, on the issue of time specification, defaultv3The version does not come withsecondsField, you need to use it in this way

cron.New(cron.WithSeconds())

Create the object’s passed in parameter.

All right. I want to explain the end of the code will not run, has been uploaded to GitHub, you can download to learn: https://github.com/asong2020/…

summary

That’s all for today’s article. The summary of this article is not complete. It’s just an entry-level effect. If you want to continue to deepen, you still need to read the document and learn by yourself. Learn to read official documents, in order to make more progress. For example, if I don’t look at the documents, I won’t know what the time specifications are now. So I still need to form a good habit of reading documents. Make a preview, the next issue is the go elastic tutorial, you can pay attention to it if you need it.

At the end, I’d like to send you a little welfare. Recently, I was reading the book “micro service architecture design mode”. I talked about it very well. I also collected a PDF. If you need it, you can download it by yourself. Access: public official account: DreamWorks [Golang], background reply: [micro service], you can get it.

I have translated a Chinese document of gin, which will be maintained regularly. If necessary, you can download it by replying to [gin] in the background.

I am asong, an ordinary program ape, let me slowly become stronger. Welcome to your attention. See you next time~~~

Go: learning wire dependency injection and cron timing tasks is actually so simple!

Recommended articles in the past:

  • It’s said that you don’t know JWT and swagger yet. I’m not going to eat any more. I’ll come with the practice project
  • You will master these two language levels
  • Go to realize the multi person chat room, where you want to chat anything you can!!!
  • Grpc practice – learning grpc is that simple
  • RPC practice of go standard library
  • In 2020, the latest gin framework Chinese document asong picked up English again and translated it with heart
  • Several hot loading methods based on gin
  • Boss: this kid can’t use the validator library to verify the data. It has opened ~~~