diff --git a/README.md b/README.md index 899453a..c14753a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Ultimately, kun may support the following communication types: - In-process function call - RPC (e.g., HTTP and gRPC) - Asynchronous messaging +- Cron Jobs 中文博客:[Go 服务通信工具 Kun](http://russellluo.com/2021/12/kun.html)。 @@ -44,7 +45,7 @@ Ultimately, kun may support the following communication types: + [x] Event Subscriber + [x] Event Publisher - [x] Cron - + [x] Cron Job Handler + + [x] Cron Jobs 2. Useful Packages @@ -856,7 +857,7 @@ See the [OAS Schema](https://github.com/RussellLuo/kun/blob/master/pkg/oas2/sche ```go type Service interface { //kun:cron expr='@every 5s' - SendEmail() + SendEmail(ctx context.Context) error } // job: {"name": "send_email", "expr": "@every 5s"} @@ -867,7 +868,7 @@ See the [OAS Schema](https://github.com/RussellLuo/kun/blob/master/pkg/oas2/sche ```go type Service interface { //kun:cron name=send expr='@every 5s' - SendEmail() + SendEmail(ctx context.Context) error } // job: {"name": "send", "expr": "@every 5s"} diff --git a/examples/cronsvc/cron.go b/examples/cronsvc/cron.go index 65a6412..d1d03fd 100644 --- a/examples/cronsvc/cron.go +++ b/examples/cronsvc/cron.go @@ -10,9 +10,9 @@ import ( func NewCronJobs(svc Service) []micron.Job { return []micron.Job{ { - Name: "send_email", - Expr: "@every 5s", - Task: svc.SendEmail, + Name: "send_email", + Expr: "@every 5s", + Handler: svc.SendEmail, }, } } diff --git a/examples/cronsvc/endpoint.go b/examples/cronsvc/endpoint.go index 83386f3..b578c1b 100644 --- a/examples/cronsvc/endpoint.go +++ b/examples/cronsvc/endpoint.go @@ -9,10 +9,23 @@ import ( "github.com/go-kit/kit/endpoint" ) +type SendEmailResponse struct { + Err error `json:"-"` +} + +func (r *SendEmailResponse) Body() interface{} { return r } + +// Failed implements endpoint.Failer. +func (r *SendEmailResponse) Failed() error { return r.Err } + // MakeEndpointOfSendEmail creates the endpoint for s.SendEmail. func MakeEndpointOfSendEmail(s Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { - s.SendEmail() - return nil, nil + err := s.SendEmail( + ctx, + ) + return &SendEmailResponse{ + Err: err, + }, nil } } diff --git a/examples/cronsvc/service.go b/examples/cronsvc/service.go index 6dd51a0..be433be 100644 --- a/examples/cronsvc/service.go +++ b/examples/cronsvc/service.go @@ -1,6 +1,7 @@ package cronsvc import ( + "context" "log" ) @@ -9,11 +10,12 @@ import ( // Service is used for handling cron jobs. type Service interface { //kun:cron expr='@every 5s' - SendEmail() + SendEmail(ctx context.Context) error } type Handler struct{} -func (h *Handler) SendEmail() { +func (h *Handler) SendEmail(ctx context.Context) error { log.Println("Sending an email") + return nil } diff --git a/gen/cron/generator/generator.go b/gen/cron/generator/generator.go index f8709c0..9ea816d 100644 --- a/gen/cron/generator/generator.go +++ b/gen/cron/generator/generator.go @@ -32,7 +32,7 @@ func NewCronJobs(svc {{$.Data.SrcPkgQualifier}}{{$.Data.InterfaceName}}) []micro { Name: "{{getJobName .GoMethodName}}", Expr: "{{getJobExpr .GoMethodName}}", - Task: svc.{{.Name}}, + Handler: svc.{{.Name}}, }, {{end -}} {{/* range .Spec.Operations */ -}} } diff --git a/gen/cron/parser/parser.go b/gen/cron/parser/parser.go index 5fcfcd5..7163570 100644 --- a/gen/cron/parser/parser.go +++ b/gen/cron/parser/parser.go @@ -2,6 +2,7 @@ package parser import ( "fmt" + "go/types" "regexp" "github.com/RussellLuo/kun/gen/util/annotation" @@ -23,8 +24,9 @@ func Parse(data *ifacetool.Data, snakeCase bool) (map[string]*Job, error) { c := make(map[string]*Job) for _, m := range data.Methods { - job := new(Job) - c[m.Name] = job + if !validateSignature(m) { + return nil, fmt.Errorf("the signature of method %s must be `func(context.Context) error` when annotated by %s directive", m.Name, annotation.DirectiveCron) + } for _, comment := range m.Doc { if annotation.Directive(comment).Dialect() != annotation.DialectCron { @@ -41,6 +43,8 @@ func Parse(data *ifacetool.Data, snakeCase bool) (map[string]*Job, error) { return nil, err } + job := new(Job) + for _, pair := range pairs { switch pair.Key { case "name": @@ -52,6 +56,8 @@ func Parse(data *ifacetool.Data, snakeCase bool) (map[string]*Job, error) { } } + // Here we assume that all annotation keys are specified in the same line. + if job.Name == "" { job.Name = caseconv.ToLowerCamelCase(m.Name) if snakeCase { @@ -62,8 +68,29 @@ func Parse(data *ifacetool.Data, snakeCase bool) (map[string]*Job, error) { if job.Expr == "" { return nil, fmt.Errorf(`missing key "expr" for %s directive in comment: %s`, annotation.DirectiveCron, comment) } + + c[m.Name] = job + break // No need to continue since we have found the annotation. } } return c, nil } + +func validateSignature(m *ifacetool.Method) bool { + if len(m.Params) != 1 || len(m.Returns) != 1 { + return false + } + + p := m.Params[0] + if _, ok := p.Type.Underlying().(*types.Interface); !ok || p.TypeString != "context.Context" { + return false + } + + r := m.Returns[0] + if _, ok := r.Type.Underlying().(*types.Interface); !ok || r.TypeString != "error" { + return false + } + + return true +} diff --git a/go.mod b/go.mod index 7793147..d9487fe 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.16 require ( github.com/RussellLuo/appx v0.0.0-20221005075435-c578768595db - github.com/RussellLuo/micron v0.0.0-20221008065742-31d1b4e95230 + github.com/RussellLuo/micron v0.0.0-20221009070337-abf7aee8c3d5 github.com/RussellLuo/validating/v2 v2.1.1 github.com/go-chi/chi v4.1.2+incompatible github.com/go-kit/kit v0.10.0 diff --git a/go.sum b/go.sum index 41d2977..536b781 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/RussellLuo/appx v0.0.0-20221005075435-c578768595db h1:apVxO5ghwD7dOEzeCuBOuEIF9zqiffW0/8RoghDn4vw= github.com/RussellLuo/appx v0.0.0-20221005075435-c578768595db/go.mod h1:BYiSO63uqNy81Mlv9EfnJ+wNr+vkgAohKMgeDyhWu1s= -github.com/RussellLuo/micron v0.0.0-20221008065742-31d1b4e95230 h1:B2qSHwpUWyIa7BGJHNLNPu/eOmT8ocLEeOQ5V+TvBJE= -github.com/RussellLuo/micron v0.0.0-20221008065742-31d1b4e95230/go.mod h1:dSJ4Da5HdXgw8m9O8V0T5p+HQNEblPx01mXpV8tX2NY= +github.com/RussellLuo/micron v0.0.0-20221009070337-abf7aee8c3d5 h1:9C+0p+Ixdh7lZEbzX4VnIV39Jx6pCj1aQRUULcZbIGY= +github.com/RussellLuo/micron v0.0.0-20221009070337-abf7aee8c3d5/go.mod h1:dSJ4Da5HdXgw8m9O8V0T5p+HQNEblPx01mXpV8tX2NY= github.com/RussellLuo/validating/v2 v2.1.1 h1:lIqtzIgoeW1Rs4Oouo6Twk0HRxFjKgEZARMuOlT4hh0= github.com/RussellLuo/validating/v2 v2.1.1/go.mod h1:FCrcxiiCo8FXMbKoALpXAHkGXZoVh5pWVboC+GVruiI= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=