Skip to content
This repository has been archived by the owner on Dec 5, 2017. It is now read-only.

Service Tutorial

nairboon edited this page Mar 29, 2013 · 11 revisions

We are going to walk through creating a basic service and what each thing means, this will just brush the surface of how services work. For more detailed information on services please refer to the Services Documentation page

As a first step you may want to check out the examples/service directory. We have also included the completed service in the examples/tutorials/service directory for your reference

Setup

First you need to create a directory and a .go file, define it as part of package main and import skynet and skynet/service

package main

import (
	"github.com/skynetservices/skynet"
	"github.com/skynetservices/skynet/service"
)

Creating your service

Setup

All services must implement the service.ServiceDelegate interface

type ServiceDelegate interface {
	Started(s *Service)
	Stopped(s *Service)
	Registered(s *Service)
	Unregistered(s *Service)
}

These are really nothing more than callbacks to notify your service when events take place, they can be empty methods for now.

Our service is going to look like this:

type MyService struct {
}

func (s *MyService) Registered(service *service.Service)   {}
func (s *MyService) Unregistered(service *service.Service) {}
func (s *MyService) Started(service *service.Service)      {}
func (s *MyService) Stopped(service *service.Service) {
}
Our Logic

First we want to create 2 data types to represent our request and response. These can be any type of interface that can be marshalled to BSON. We could also just use a map etc. Anything that is not a standard type (int, string, etc.)

type TutorialRequest struct {
  Value int
}

type TutorialResponse struct {
  Value int
}

Next we want to define the methods we want to be made available to the cluster. In order for a method to be exposed via RPC to Skynet it must implement this interface

func Foo(ri *skynet.RequestInfo, req interface{}, resp interface{}) error {
  • The method must be exported
  • It must have 3 parameters
    • a pointer to skynet.RequestInfo which will hold the unique UUID for this request, it's passed to each machine that touches a request.
    • a map/array or struct of your own that will be the request sent from the client. This can be a pointer
    • a map/array, or a pointer to struct of your own that will be the response to send back to the client
  • It must have a single return value of the type error for failures when a response cannot be sent

Valid

func (s *MyService) Foo(ri *skynet.RequestInfo, req *MyRequest, resp *MyResponse) error {
func (s *MyService) Foo(ri *skynet.RequestInfo, req MyRequest, resp *MyResponse) error {
func (s *MyService) Foo(ri *skynet.RequestInfo, in map[string]interface{}, out map[string]interface{}) (err error) {

Our method will be simple and will just add 1 to the provided integer and return it to the client

func (f *TutorialService) AddOne(ri *skynet.RequestInfo, req *TutorialRequest, resp *TutorialResponse) (err error) {
  resp.Value = req.Value + 1

  return nil
}

Main()

That's all that needs to be done for setting up our service class. Now we just need to create our function main() so that Go makes this an executable.

func main(){
	tutorial := &TutorialService{}
	config, _ := skynet.GetServiceConfig()

  	config.Name = "TutorialService"
  	config.Version = "1"
  	config.Region = "Development"

	service := service.CreateService(tutorial, config)

	defer func() {
		service.Shutdown()
	}()

	waiter := service.Start(true)
	waiter.Wait()
}

We'll walk through this a line at a time:

Create an instance of our class

tutorial := &TutorialService{}

This is a helper method so that you don't need to create a config object yourself and set all the properties yourself, including bind ip's for RPC, Admin RPC, doozer flags etc. This will allow you to grab them from the Environment Variables or flags supplied on the command line when starting the service. All the available flags can be found on the Services Documentation page

config, _ := skynet.GetServiceConfig()

With these lines we are just hardcoding values so we override the values passed through the command line flags, and don't end up using the defaults if we don't supply them

config.Name = "TutorialService"
config.Version = "1"
config.Region = "Development"

Here we are passing in our service, and the config and are returned a skynet service wrapper around our service.

service := service.CreateService(tutorial, config)

We want to defer the service.Shutdown() call so that we ensure that we wait for current requests to finish, and that we deregister ourselves from the cluster when we shutdown

defer func() {
	service.Shutdown()
}()

This is the point where our server starts binding to ports for it's RPC requests, and Admin. The boolean flag passed is whether or not to mark the service as Registered and ready for requests once it has bound to the ports. You can pass false here, do more work then call service.Register() at a later point.

waiter := service.Start(true)

The previous call returned a sync.WaitGroup for us, we call Wait() on this so that we block until we are shutdown, and requests have finished.

waiter.Wait()

That's It

Our service in it's entirety looks like this

package main

import (
	"github.com/skynetservices/skynet"
	"github.com/skynetservices/skynet/service"
)

type TutorialService struct {
}

func (s *TutorialService) Registered(service *service.Service)                      {}
func (s *TutorialService) Unregistered(service *service.Service)                    {}
func (s *TutorialService) Started(service *service.Service)                         {}
func (s *TutorialService) Stopped(service *service.Service)                         {}
func (s *TutorialService) MethodCalled(method string)                               {}
func (s *TutorialService) MethodCompleted(method string, duration int64, err error) {}

type TutorialRequest struct {
	Value int
}

type TutorialResponse struct {
	Value int
}

func (f *TutorialService) AddOne(ri *skynet.RequestInfo, req *TutorialRequest, resp *TutorialResponse) (err error) {
	resp.Value = req.Value + 1

	return nil
}

func main() {
	tutorial := &TutorialService{}
	config, _ := skynet.GetServiceConfig()

	config.Name = "TutorialService"
	config.Version = "1"
	config.Region = "Development"

	service := service.CreateService(tutorial, config)

	defer func() {
		service.Shutdown()
	}()

	waiter := service.Start(true)
	waiter.Wait()
}

Starting your service

Assuming you have doozer running and you have either set your SKYNET_DZHOST Environment Variable or you pass in the --doozer flag from the Service Documentation. If neither is supplied it will look for doozer running on it's default port at 127.0.0.1

You can just do the following

go build
./service

skynet: 2012/09/12 14:24:48 Created service "TutorialService"
skynet: 2012/09/12 14:24:48 Registered methods: [AddOne]
skynet: 2012/09/12 14:24:48 Service "TutorialService" listening on 127.0.0.1:9000
skynet: 2012/09/12 14:24:48 Service "TutorialService" listening for admin on 127.0.0.1:9001
skynet: 2012/09/12 14:24:48 Connected to doozer at 192.168.126.101:8046
skynet: 2012/09/12 14:24:48 Discovered new doozer WE7CFFRW4CGMYL5O at 192.168.126.101:8046

Moving On

Next you'll want to create a client to interact with your service. Checkout the Creating a simple client tutorial

Clone this wiki locally