Services

erikstmartin edited this page Sep 13, 2012 · 2 revisions

Services are the heart of your Skynet cluster, they serve all RPC requests and return responses back to clients.

Service Delegate

The service delegate is your service, that has your callback methods and exported RPC methods.

All services must implement the service.ServiceDelegate interface

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

and any number of methods to be exported via RPC. In order for a method to be available to the skynet network it must meet the following criteria

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) {

Service Config

In order to turn your ServiceDelegate into a Skynet service, you will need to create a ServiceConfig.

type ServiceConfig struct {
	Log                  Logger `json:"-"`
	UUID                 string
	Name                 string
	Version              string
	Region               string
	ServiceAddr          *BindAddr
	AdminAddr            *BindAddr
	DoozerConfig         *DoozerConfig `json:"-"`
	DoozerUpdateInterval time.Duration `json:"-"`
}
  • Log - anything that implements the Logger interface, this will be used internally for all logging
  • UUID - a custom UUID for this instance, so that it can be identified later.
  • Name - a unique name for this specific service
  • Version - the version of this service
  • Region - we generally refer to region as a data center, but you could use this to group things however you'd like
  • ServiceAddr - This is an object that represents the ip address and port to bind to for serving rpc requests
  • AdminAddr - This is an object that represents the ip address and port to bind to for serving admin (stop/unregister/register) requests
  • DoozerConfig - Configuration for how this service should connect to doozer
  • DoozerUpdateInterval - this is the frequency in which the service will report it's number of connections and average response time etc to doozer
Logger
type Logger interface {
	Item(item interface{})
	Panic(item interface{})
	Println(items ...interface{})
BindAddr / AdminAddr

This is pretty self explanatory, aside from the MaxPort. If given a Port and MaxPort it will hunt for an open port in that range.

type BindAddr struct {
	IPAddress string
	Port      int
	MaxPort   int
}
DoozerConfig
type DoozerConfig struct {
	Uri          string
	BootUri      string
	AutoDiscover bool
}
  • Uri - doozer uri to connect to
  • BootUri - if you're using a DZNS cluster this will be the uri for that
  • AutoDiscover - whether or not new instances of doozer that come online should be added to our internal list of instances to connect to in case of connection problems to our current instance.
Flags

To save a bit of repetitive effort, and help encourage consistency. There are some helpers available to you.

This will parse everything you need for a ServiceConfig from the command line flags, and Environment Variables set as defaults. We recommend taking this approach, and then setting the config values on the config object that are returned that you would like to be hardcoded and not overrideable.

config, _ := skynet.GetServiceConfig()

Doozer Flags:

  • --doozer - ip:port of doozer instance
  • --doozerboot - ip:port of doozer dzns cluster
  • --autodiscover - use new doozer instances that come online

Service Flags

  • --uuid - unique id for the current instance of the service
  • --region - the region the current instance of the service is running in
  • --version - the version of the service
  • --dzupdate - the interval that the service should update doozer

Skynet Service

To create a skynet service that can be run you just need to pass in your ServiceConfig, and ServiceDelegate to service.CreateService

service := service.CreateService(delegate, config)

Now you have a Skynet Service wrapper, which will delegate RPC calls to the delegate that you created, and handle all startup/shutdown/register/unregister, and connectivity with clients.

Only 1 thing left to do. We need to call Start so that the service will bind its ip's and notify the cluster. Passing true will register the service to take requests once initiaization has finished, if you pass false, you will need to call service.Register() later in order for your service to start taking requests.

Start() returns a sync.WaitGroup for you to wait on to keep your app up and taking requests, when the service is killed or interrupted it will finish all current requests and shutdown, and Wait() will return.

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

Be sure that in your main function, and anywhere else that is neccessary that you call

service.Shutdown()

This will ensure your service cleans up after itself, stops taking new requests, finishes current requests, and removes itself from the cluster. Don't worry if something terribly bad happens the daemon that started this service will clean up after it, but we prefer services to cleanup after themselves, and not loose requests if at all possible.