module SMService

Constants

ENDPOINT_IN
ENDPOINT_OUT
KEEPALIVE_WAIT_TIME
LOGGER
REGISTER_RETRY
REGISTER_WAIT_TIME
VERSION

Attributes

service_name[R]
socket_in[RW]
socket_out[RW]

Public Class Methods

new(name:, actions: []) click to toggle source

When registering node, following parameters is mandatory:

name: name of service/node with its id; actions: list of actions, should be in lowercase and compatible to naming standard for ruby methods actions could be presented as array or as string if service registering only single action

Example: SMService::SomeService.new(name: 'dummy-ruby', actions: %w(a b c ping pong))

Following actions should be defined as methods for new service, i.e.

def action_ping(headers, message)

 LOGGER.info "#{self.class} - Processing action: PING with headers: #{headers.inspect} and message: #{message.inspect}"
 # ... business logic here
end

Prohibited names for actions is register and update, those are reserved to let service communicate with SM. Check SMService::Dummy source code (in examples folder) for detais

# File lib/smservice.rb, line 39
def initialize(name:, actions: [])
  @service_name = name
  @actions = [%w(ping), actions].flatten.sort.uniq

  @mutex = Mutex.new

  context = ZMQ::Context.new
  @socket_in = context.socket ZMQ::DEALER
  @socket_out = context.socket ZMQ::DEALER

  @socket_in.setsockopt ZMQ::IDENTITY, service_name
  @socket_in.connect ENDPOINT_IN

  @socket_out.setsockopt ZMQ::IDENTITY, service_name
  @socket_out.connect ENDPOINT_OUT
end

Public Instance Methods

action_ping(headers, message) click to toggle source
# File lib/smservice.rb, line 126
def action_ping(headers, message)
  LOGGER.info "#{self.class} - Processing action 'ping'. Headers: #{headers.inspect}, message: #{message.inspect}"
end
action_register(headers, message) click to toggle source
# File lib/smservice.rb, line 117
def action_register(headers, message)
  if headers['action'] == 'REGISTER' && message['result'] == 'OK'
    @registered = true
    LOGGER.info "#{self.class} - Action: REGISTER (successful), headers: #{headers.inspect}, message: #{message.inspect}"
  else
    LOGGER.info "#{self.class} - Action: REGISTER (failure), headers: #{headers.inspect}, message: #{message.inspect}"
  end   
end
action_reply(headers, message) click to toggle source
# File lib/smservice.rb, line 134
def action_reply(headers, message)
  LOGGER.info "#{self.class} - Processing action 'reply'. Headers: #{headers.inspect}, message: #{message.inspect}"
end
action_update(headers, message) click to toggle source
# File lib/smservice.rb, line 130
def action_update(headers, message)
  LOGGER.info "#{self.class} - Processing action 'update'. Headers: #{headers.inspect}, message: #{message.inspect}"
end
execute(action:, reply_to: nil, message: nil) click to toggle source

Requesting another service, registered at SM to execute given action, example: execute(action: 'create_customer_portal', message: {my_request: 'should create a customer', my_data: 'add whatever data needed'})

# File lib/smservice.rb, line 141
def execute(action:, reply_to: nil, message: nil)
  LOGGER.info "#{self.class} - SM execute request. Action: #{action.inspect}, message: #{message.inspect}"
  action = {service: action, reply_to: (reply_to || @service_name)}.to_msgpack
  message = message.to_msgpack
  @socket_out.send_strings [action, message]
end
keep_alive!(wait_time: KEEPALIVE_WAIT_TIME) click to toggle source
# File lib/smservice.rb, line 101
def keep_alive!(wait_time: KEEPALIVE_WAIT_TIME)
  Thread.start do
    LOGGER.info "#{self.class} - Starting registration update loop with periodic interval #{wait_time} sec"
    loop do
      break unless registered?
      sleep wait_time
      request(action: 'UPDATE')
    end
    LOGGER.info 'Registration update loop terminated'
  end
end
poller() click to toggle source
# File lib/smservice.rb, line 75
def poller
  loop do
    pull
  end
end
poller!() click to toggle source
# File lib/smservice.rb, line 69
def poller!
  Thread.start do
    poller
  end
end
register(wait_time: REGISTER_WAIT_TIME) click to toggle source
# File lib/smservice.rb, line 81
def register(wait_time: REGISTER_WAIT_TIME)
  if wait_time.to_f <= 0
    loop do
      register_service
      break if registered?
      # not registered, retrying in 10 seconds
      sleep REGISTER_RETRY
    end
  else
    Timeout::timeout(wait_time) do
      loop do
        register_service
        break if registered?
        # not registered, retrying in 10 seconds
        sleep REGISTER_RETRY
      end
    end
  end
end
registered?() click to toggle source
# File lib/smservice.rb, line 113
def registered?
  @registered
end
start!() click to toggle source

starting infinite loop in order to process service logic. start! or service_poller methods could be modified implementing different services, i.e. to run as threaded metod

# File lib/smservice.rb, line 58
def start!
  register
  keep_alive!
  poller!
  @started = true
end
started?() click to toggle source
# File lib/smservice.rb, line 65
def started?
  @started
end

Private Instance Methods

pull() click to toggle source
# File lib/smservice.rb, line 166
def pull
  response = []
  @mutex.synchronize do
    @socket_in.recv_strings(response)
  end

  headers, message = response
  headers = MessagePack.unpack(headers)
  message = MessagePack.unpack(message)

  LOGGER.info "#{self.class} - SM response received by pull. Headers: #{headers.inspect}, message: #{message.inspect}"

  # Validates is action_name exists as class method and calling it.
  # In case, if Service Manager return headers['action'] == 'METHOD_NAME',
  # ruby expecting that somethinf like this would be defined:
  #
  # def action_method_name(headers, message)
  #   # ... some business logic here
  # end
  #
  # Two actions defined as part of module, it is :
  # - action_register, that confirms registration of node with Service Manager, and
  # - action_update, that confirms successfule registration update, we need it to support keep-alive
  #
  action_name = "action_#{(headers['action'] || headers['service']).downcase}".to_sym
  if respond_to? action_name.to_sym
    send(action_name, headers, message)
  else
    LOGGER.error "#{self.class} - Method #{action_name} does not exist in class instance for action name #{headers['action']}"
  end
end
register_service() click to toggle source
# File lib/smservice.rb, line 150
def register_service
  LOGGER.info "#{self.class} - Registering service #{service_name}"
  request(action: 'REGISTER', message: {services: @actions})
  pull
end
request(action:, message: nil) click to toggle source

Sending request to the Service Manager

# File lib/smservice.rb, line 157
def request(action:, message: nil)
  LOGGER.info "#{self.class} - SM request. Action: #{action.inspect}, message: #{message.inspect}"
  action = {action: action}.to_msgpack
  message = message.to_msgpack
  @mutex.synchronize do
    @socket_in.send_strings [action, message]
  end
end