diff --git a/README.md b/README.md index 6c03f07c..24c3f08d 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,15 @@ Go Micro abstracts way the details of distributed systems. Here are the main fea - **Message Encoding** - Micro services can encode requests in a number of encoding formats and seamlessly decode based on the Content-Type header. - **RPC Client/Server** - The client and server leverage the above features and provide a clean simple interface for building microservices. +Go Micro supports both the Service and Function programming models. Read on to learn more. + ## Docs For more detailed information on the architecture, installation and use of go-micro checkout the [docs](https://micro.mu/docs). ## Learn By Example -An example service can be found in [**examples/service**](https://github.com/micro/examples/tree/master/service). The [**examples**](https://github.com/micro/examples) directory contains many more examples for using things such as middleware/wrappers, selector filters, pub/sub and code generation. - +An example service can be found in [**examples/service**](https://github.com/micro/examples/tree/master/service) and function in [**examples/function**](https://github.com/micro/examples/tree/master/function). The [**examples**](https://github.com/micro/examples) directory contains many more examples for using things such as middleware/wrappers, selector filters, pub/sub and code generation. For the complete greeter example look at [**examples/greeter**](https://github.com/micro/examples/tree/master/greeter). Other examples can be found throughout the GitHub repository. Check out the blog post to learn how to write go-micro services [https://micro.mu/blog/2016/03/28/go-micro.html](https://micro.mu/blog/2016/03/28/go-micro.html) or watch the talk from the [Golang UK Conf 2016](https://www.youtube.com/watch?v=xspaDovwk34). @@ -228,6 +229,48 @@ go run client.go Hello John ``` +## Writing a Function + +Go Micro includes the Function programming model. This is the notion of a one time executing Service which operates much like a service except exiting +after completing a request. A function is defined much like a service and called in exactly the same way. + +### Defining a Function + +```go +package main + +import ( + proto "github.com/micro/examples/function/proto" + "github.com/micro/go-micro" + "golang.org/x/net/context" +) + +type Greeter struct{} + +func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error { + rsp.Greeting = "Hello " + req.Name + return nil +} + +func main() { + // create a new function + fnc := micro.NewFunction( + micro.Name("go.micro.fnc.greeter"), + ) + + // init the command line + fnc.Init() + + // register a handler + fnc.Handle(new(Greeter)) + + // run the function + fnc.Run() +} +``` + +It's that simple. + ## How does it work?
diff --git a/function.go b/function.go new file mode 100644 index 00000000..bc47d38f --- /dev/null +++ b/function.go @@ -0,0 +1,81 @@ +package micro + +import ( + "time" + + "github.com/micro/go-micro/server" + "golang.org/x/net/context" +) + +type function struct { + cancel context.CancelFunc + Service +} + +func fnHandlerWrapper(f Function) server.HandlerWrapper { + return func(h server.HandlerFunc) server.HandlerFunc { + return func(ctx context.Context, req server.Request, rsp interface{}) error { + defer f.Done() + return h(ctx, req, rsp) + } + } +} + +func fnSubWrapper(f Function) server.SubscriberWrapper { + return func(s server.SubscriberFunc) server.SubscriberFunc { + return func(ctx context.Context, msg server.Publication) error { + defer f.Done() + return s(ctx, msg) + } + } +} + +func newFunction(opts ...Option) Function { + ctx, cancel := context.WithCancel(context.Background()) + + // force ttl/interval + fopts := []Option{ + RegisterTTL(time.Minute), + RegisterInterval(time.Second * 30), + } + + // prepend to opts + fopts = append(fopts, opts...) + + // make context the last thing + fopts = append(fopts, Context(ctx)) + + service := newService(fopts...) + + fn := &function{ + cancel: cancel, + Service: service, + } + + service.Server().Init( + // ensure the service waits for requests to finish + server.Wait(true), + // wrap handlers and subscribers to finish execution + server.WrapHandler(fnHandlerWrapper(fn)), + server.WrapSubscriber(fnSubWrapper(fn)), + ) + + return fn +} + +func (f *function) Done() error { + f.cancel() + return nil +} + +func (f *function) Handle(v interface{}) error { + return f.Service.Server().Handle( + f.Service.Server().NewHandler(v), + ) +} + +func (f *function) Subscribe(topic string, v interface{}) error { + return f.Service.Server().Subscribe( + f.Service.Server().NewSubscriber(topic, v), + ) +} diff --git a/function_test.go b/function_test.go new file mode 100644 index 00000000..49209648 --- /dev/null +++ b/function_test.go @@ -0,0 +1,55 @@ +package micro + +import ( + "sync" + "testing" + + "github.com/micro/go-micro/registry/mock" + proto "github.com/micro/go-micro/server/debug/proto" + + "golang.org/x/net/context" +) + +func TestFunction(t *testing.T) { + var wg sync.WaitGroup + wg.Add(1) + + // create service + fn := NewFunction( + Name("test.function"), + Registry(mock.NewRegistry()), + AfterStart(func() error { + wg.Done() + return nil + }), + ) + + // we can't test fn.Init as it parses the command line + // fn.Init() + + go func() { + // wait for start + wg.Wait() + + // test call debug + req := fn.Client().NewRequest( + "test.function", + "Debug.Health", + new(proto.HealthRequest), + ) + + rsp := new(proto.HealthResponse) + + err := fn.Client().Call(context.TODO(), req, rsp) + if err != nil { + t.Fatal(err) + } + + if rsp.Status != "ok" { + t.Fatalf("function response: %s", rsp.Status) + } + }() + + // run service + fn.Run() +} diff --git a/go-micro.go b/go-micro.go index dd982100..818b3476 100644 --- a/go-micro.go +++ b/go-micro.go @@ -22,6 +22,18 @@ type Service interface { String() string } +// Function is a one time executing Service +type Function interface { + // Inherits Service interface + Service + // Done signals to complete execution + Done() error + // Handle registers an RPC handler + Handle(v interface{}) error + // Subscribe registers a subscriber + Subscribe(topic string, v interface{}) error +} + // Publisher is syntactic sugar for publishing type Publisher interface { Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error @@ -49,6 +61,11 @@ func NewContext(ctx context.Context, s Service) context.Context { return context.WithValue(ctx, serviceKey{}, s) } +// NewFunction returns a new Function for a one time executing Service +func NewFunction(opts ...Option) Function { + return newFunction(opts...) +} + // NewPublisher returns a new Publisher func NewPublisher(topic string, c client.Client) Publisher { if c == nil {