preface

Hi, my little Asong is back. I haven’t updated for two weeks, I’ve been busy lately, and I’m lazy, so, uh, uh, you get the idea. However, the share I bring today is absolutely dry goods, which is also needed in the actual project development. So in order to explain clearly, I specially wrote a sample for reference only. This article will focus on the sample study, has been uploaded to Github, you can download. Ok, no more nonsense, I know you can’t wait, let’s get started!!

wire

Dependency injection

Before introducing Wire, let’s look at what dependency injection is. This should be familiar to those of you who have used Spring. The most common form of inversion of control (IOC) is called dependency injection. A class that puts a dependent class into a dependency as a row parameter is called dependency injection. This may not make sense to you. To put it in plain English, an instantiated object, where I used to take various arguments to construct an object, now takes only one argument, the dependency on the object is injected and decoupled from the way it’s constructed. This control operation is also handed over to a third party, known as inversion of control. For example: Go does not have the concept of classes, which are represented in the form of structures. Suppose we now have a boat class and a paddle class, and we now want to set the boat to have 12 OARS, 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(a){
	p:= Newpulp(12)
	s := NewShip(p)
	fmt.Println(s.pulp.count)
}


Copy the code

I’m sure you can see the problem at once. Every time the requirements change, we have to create a new object to specify the paddle. This code is not easy to maintain, so let’s work around it.

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(a) *pulp{
	return &pulp{
	}
}

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

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

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

Copy the code

The benefits of this code are that it is loosely coupled, easy to maintain, and easy to test. If we need to change now and need 20 OARS, just direct S.pulp.set (20).

The use of the wire

Wire has two basic concepts, Provider and Injector. A Provider is essentially creating a function, so let’s see. InitializeCron means Injector. Each injector is essentially an object creation and initialization function. In this function, we just need to tell wire what type of object to create, the type of dependency, wire tool will generate a function for us to create and initialize the object.

Pulled so long, is to lead wire, although the code above is to implement the dependency injection, this is the code quantity is little, structure is complex, there is no question of our own to rely on, when the relationship between the structure becomes very complex, then manually create dependent, and then assembled them will become red, and are easy to make mistakes. So that’s where wire comes in. Let’s install the wire before using it.

$ go get github.com/google/wire/cmd/wire
Copy the code

Executing this command generates an executable called wire in $GOPATH/bin, which is the code generator. Don’t forget to add $GOPATH/bin to the system environment variable $PATH.

Let’s take a look at wire using the simple example above. Let’s start by creating 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(a) *Pulp {
	return &Pulp{
	}
}

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

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

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

func main(a){}Copy the code

The return value of InitShip is the type of object we need to create. Wire only needs to know the type, and it doesn’t matter what it returns. In this function we call wire.build () to pass in the type constructor on which the ship was created. Now that we’re done, we need to go to the console and execute the wire.

$ 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
Copy the code

We see the wire_gen.go file generated:

// Code generated by Wire. DO NOT EDIT.

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

package main

// Injectors from mian.go:

func InitShip(a) *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(a) *Pulp {
	return &Pulp{}
}

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

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

func main(a){}Copy the code

InitShip () ¶ This file generates the InitShip () function, and the dependent binding is implemented. We can call this function directly, saving a lot of code to implement the dependent binding.

* * note: ** If you are using wire for the first time, you are bound to run into a problem. The generated code conflicts with the original code because func InitShip() *Ship is defined as the same function, so you need to add //+build wireinject at the beginning of the original file. It also has a blank line with the package name, which resolves the conflict.

The above example is still simple, let’s look at a little more examples, we are in the daily web background development, code is layered, more familiar with dao, Service, controller, model and so on. Dao, Service, and controller are called in sequence. The Controller calls the Service layer, and the service layer calls the DAO layer, thus forming a dependency relationship. In actual development, we use hierarchical dependency injection to make it more hierarchical, and the code is easy to maintain. So, I wrote a sample and let’s learn how to use it. This replaces the Controller with the Cron timed task, which I’ll explain 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{}
}
Copy the code

Dao.NewClientDB creates a *UserDB object that depends on the mysql configuration file. Dao. NewUserService creates a UserService object that depends on the *UserDB object. Task. NewScanner creates a *Scanner object that depends on the *UserService object, cron. NewCron creates a *Cron object, which relies on the *Scanner object.

Ok, so that’s the basic usage, so let’s take a look at Cron.

cron

Based on learning

In daily development or operation and maintenance, we often encounter some tasks or requirements that are executed periodically. For example, we execute a script every period of time and an operation every month. Linux provides us with a convenient way to do the crontab scheduled task; The crontab command is a customized timer. You can use the crontab command to execute specified system commands or shell scripts at fixed intervals. The time interval is written in a similar way to the cron expression we usually use. To perform operations periodically by using characters or commands to set timing.

With the basic concepts in mind, let’s introduce cron expressions. There are two common CRON specification formats: the “standard” CRON format, used by cron Linux system programs, and the Cron format used by Quartz Scheduler. The difference between the two is that one supports the seconds field and the other does not, but the difference is not very large and we will use the seconds field for the rest of the tutorial.

A CRon expression is a string divided by six Spaces into seven fields, each representing a time meaning. The format is as follows:

[second] [minute] [hour] [day] [month] [week] [year]Copy the code

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

The definition of each part is presented in a table:

The domain If required Values and ranges The wildcard
seconds is 0-59 , – * /
points is 0-59 , – * /
when is 0-23 , – * /
day is 1-31 , – *? / L W
month is 1-12 or JAN – DEC , – * /
weeks is 1-7 – SAT or SUN , – *? / L #
years no 1970-2099. , – * /

It’s pretty easy to understand if you look at the range of values, but the hardest thing to understand is wildcards, so let’s focus on wildcards.

  • If we define 5,10, and 15 in the “points” field, it means that the scheduled task is executed at 5,10, and 15, respectively.

  • If we define 6-12 in the “time” field, it means every hour between 6 and 12, using, for 6,7,8,9,10,11,12

  • * represents all values, which can be read as “every”. If set to * in the day field, it will be triggered every day.

  • ? Indicates that no value is specified. Use scenarios that do not care about the current value of this field. For example, to trigger an action on the 8th of the month, but not the day of the week, we could set 0, 0, 0, 8 *?

  • / is emitted periodically on a field. This symbol divides the expression in its field into two parts. The first part is the starting value, which decreases by one unit except for seconds. On minute, the command is executed every 10 minutes starting from the fifth second.

  • L stands for “LAST”, which can only be used in “day” and “week”. It is set in “Day”, indicating the last day of the month (depending on the current month, or if it is February, depending on whether it is a calendar year), and in “week” indicating Saturday, which is equivalent to “7” or “SAT”. If the “L” is preceded by a number, it indicates the last of the data. For example, if the format of “7L” is set on “week”, it means “last Saturday of the month”.

  • W indicates the latest business day (Monday to Friday) to the specified date. It can only be used in days and after a specific number. If 15W is set to Day, it is triggered on the working day nearest the 15th of each month. If the 15th falls on a Saturday, the next Friday (14th) will be triggered. If the 15th falls on a weekend, the next Monday (16th) will be triggered. If the 15th falls on a weekday (Monday to Friday), it is triggered on that day. If the value is 1W, it can only be pushed to the next working day of the month, not to the next month.

  • # indicates the day of the month. For example, “2#3” is on the third Tuesday of each month.

Having learned about wildcards, let’s look at some examples:

  • Once a day at 10:00:0, 0, 10 * * *
  • Run this command every 10 minutes:0 */10 * * *
  • Once at 3am on the first day of each month:0, 0, 3, 1 times?
  • Once at 23:30 on the last day of each month:0 30 23 L * ?
  • Once every Saturday at 3am:0 0 3? * L
  • Perform once at 30 and 50:0 30,50 * * *?

Cron is used in GO

Now that we’ve learned the basics, we want to use timed tasks in the GO project. What do we do? Github has a cron library with a relatively high star. We can use robfig/ Cron to develop our scheduled tasks.

Before we learn, let’s install Cron

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

This is the current stable version, the current version is standard specification, the default is not with seconds, if we want to include fields, we need to create cron objects to specify. I’ll show you in a moment. Let’s start with a simple use:

package main

import (
  "fmt"
  "time"

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

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

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

  c.Start()
  select{}}Copy the code

Here we use cron.new to create a cron object to manage scheduled tasks. Call the AddFunc() method of the cron object to add a scheduled task to the manager. AddFunc() takes two arguments, parameter 1 specifying the firing time rule as a string, and parameter 2 is a no-parameter function that is called each time it is fired. @every 1s indicates that it is triggered once every second, and @every is followed by a time interval indicating how often it is triggered. For example, @every 1h indicates that the alarm is triggered every hour, and @every 1m2s indicates that the alarm is triggered every 1 minute and 2 seconds. Any format supported by time.parseDuration () can be used here. Call c.start () to start the timing loop.

Note that since C.start () starts a new goroutine for loop checking, we add a select{} line at the end of the code to prevent the main goroutine from exiting.

When we define time, we use cron’s predefined time rules. Let’s learn some of his predefined time rules:

  • @yearly: You can also write@annuallyIs 0 o ‘clock on the first day of each year. Is equivalent to0, 0, 1, 1 star;
  • @monthly: indicates 0 o ‘clock on the first day of each month. Is equivalent to0, 0, 1 * *;
  • @weekly: indicates 0 on the first day of each week. Note that the first day is Sunday, that is, the end of Saturday and the beginning of Sunday. Is equivalent to0, 0 * * 0;
  • @daily: You can also write@midnightIs 0 o ‘clock every day. Is equivalent to0 0 * * *;
  • @hourly: indicates the start of an hour. Is equivalent to0 * * * *.

Cron also supports fixed time intervals in the following format:

@every <duration>
Copy the code

Trigger every duration.

will be parsed by calling time.parseDuration (), so any format supported by ParseDuration will work.

Projects using

Cron also supports the job interface, in addition to the callback function with no arguments:

type Job interface{
	Run()
}
Copy the code

We need to implement this interface, here I write the example to do a demonstration, I now this scheduled task is periodic sweep DB table data, the implementation of the task is 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(a)  {
	err := s.scannerDB()
	iferr ! =nil{
		fmt.Errorf(err.Error())
	}
}

func (s *Scanner)scannerDB(a)  error{
	s.reset()
	flag := false
	for {
		users,err:=s.user.MGet(s.lastID,ScannerSize)
		iferr ! =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(a)  {
	s.lastID = 0
}
Copy the code

This is where we implement the Run method, and then we need to call the AddJob method of the Cron object to add the Scanner object 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(a)  error{
	_,err := s.Schedule.AddJob("*/1 * * * *",s.Scanner)
	iferr ! =nil{
		return err
	}
	s.Schedule.Start()
	return nil
}
Copy the code

The AddJob() method is actually called inside the AddFunc() method. First, Cron defines a new type FuncJob based on the func() type:

// cron.go
type FuncJob func(a)
Copy the code

Then let FuncJob implement the Job interface:

// cron.go
func (f FuncJob) Run(a) {
  f()
}
Copy the code

In the AddFunc() method, pass the callback to type FuncJob, and then call the AddJob() method:

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

The v3 version does not use the seconds field by default, so you need to use it this way

cron.New(cron.WithSeconds())
Copy the code

This parameter is enough to create the object.

All right. I want to explain the end, the code will not run, has been uploaded to Github, can be downloaded to learn: github.com/asong2020/G…

conclusion

Today’s article is here, this is not a summary of the whole, just to achieve an effect of entry, want to continue to go deeper, but also need you to read the document to learn. Learn to read official documents, to progress more yo. In this case, IF I don’t read the document, I won’t know what time rule I’m using, so get in the habit of reading the document. Let me know that go-Elastic is coming up next if you need it.

At the end, I will send you a small welfare. Recently, I was reading the book [micro-service architecture design mode], which is very good. I also collected a PDF, which can be downloaded by myself if you need it. Access: Follow the public account: [Golang Dreamworks], background reply: [micro service], can be obtained.

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

I am Asong, an ordinary program ape, let me gradually become stronger together. We welcome your attention, and we’ll see you next time

Recommended previous articles:

  • I heard you don’t know how to JWT or swagger. – I’m skipping meals and I’m here with my practice program
  • Master these Go language features and you will improve your level by N levels (ii)
  • Go multiplayer chat room, here you can talk about anything!!
  • GRPC Practice – Learning GRPC is that simple
  • Go Standard library RPC practices
  • Asong picked up English and translated it with his heart
  • Several hot loading methods based on GIN
  • Boss: The guy doesn’t know how to use the Validator library yet