Module: Servicy

Defined in:
lib/client.rb,
lib/api.rb,
lib/formats.rb,
lib/service.rb,
lib/servicy.rb,
lib/transports.rb,
lib/formats/json.rb,
lib/load_balancer.rb,
lib/server/server.rb,
lib/transport/messages.rb,
lib/load_balancer/random.rb,
lib/server/service_searcher.rb,
lib/transport/tcp_transport.rb,
lib/transport/null_transport.rb,
lib/load_balancer/round_robin.rb,
lib/transport/in_memory_transport.rb

Overview

A transport which does nothing

Defined Under Namespace

Modules: ExtraMethods, Formats Classes: API, Client, Format, InMemoryTransport, LoadBalancer, NilTransport, RandomLoadBalancer, RoundRobinLoadBalancer, Server, Service, ServiceSearcher, TCPTransport, Transport

Class Method Summary collapse

Class Method Details

.configObject



160
161
162
# File 'lib/servicy.rb', line 160

def self.config
  @_config ||= configure
end

.configure(&block) ⇒ Object

Use this method to configure Servicy for other services



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/servicy.rb', line 139

def self.configure(&block)
  conf = Configurable.new

  # Give some reasonable defaults
  conf.server.host          = 'localhost'
  conf.server.transport     = Servicy::Transport.InMemory.new
  conf.server.load_balancer = Servicy::RoundRobinLoadBalancer.new
  conf.server.logger_stream = File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
  conf.server.config_file   = File.expand_path("~/.servicy.conf")
  # To set something for a given object, you would do something like:
  # conf.transport.port = 1234

  conf.client.transport = Servicy::Transport.InMemory.new
  conf.transport.format = Servicy::Formats.JSON

  conf = yield conf if block_given?

  # Set the options
  @_config = conf
end

.included(mod) ⇒ Object

After this method gets done running, you either have the state of the world exactly the same as it was, or your object is replaced in the global Object scope with a new object that will proxy methods to a remote service provider.

To define a service provider is a bit more work, and less auto-magic because the exact details of the infrastructure and architecture of your setup will be very different from others. So Servicy doesn’t make assumptions about how you want to do things. That said, it’s relatively simple:

# In some server class
Servicy::Server.new(...).go!

# In some service provider, possibly on a different box
api = Servicy::Client.create_api_for(MyServiceClass)
Servicy::Client.register_service(api)

FIXME: @__api, and it’s ilk, get overridden in the even that there is more than one.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/servicy.rb', line 40

def self.included(mod)
  make_service_listener(mod)

  # Attempt to find the service
  client = Servicy::Client.new(config.server.transport)
  raise 'go to bottom' unless client.connected?

  # Create a new api object
  @__api = Servicy::Client.create_api_for(mod)
  port = Servicy.config.send(mod.to_s.to_sym).port
  Servicy.logger.debug("Chose port, #{port} for service, #{mod.to_s}")
  @__api.const_set("PORT", port) unless port.nil?
  # TODO: for the other things as well

  @__service = client.find_service(@__api.search_query)
  raise 'go to bottom' unless @__service

  # If all went well, then we can wrap the object to call to the remote
  # service.
  client = Servicy::Client.new(config.client.transport.class.new(port: port))
  @__api.set_remote(client)

  # ... and delegate that ish. We do it by killing off the original object,
  # and replacing it with our own.
  Object.send(:remove_const, mod.name.to_sym)
  Object.const_set(mod.name.to_sym, @__api)
rescue => e
  # Something went wrong, just give up silently, but don't do anything
  # else; use this object as a regular-ass object. This is to allow you to
  # use your API objects as normal objects in an environment where the
  # object is bundled as part of a gem (,say, ) and there is no remote
  # handler for it: use it locally
end

.loggerObject

Get a logger instance that we can do something useful with.



13
14
15
16
17
18
19
# File 'lib/servicy.rb', line 13

def self.logger
  @logger ||= begin
                stream = config.server.logger_stream
                stream = stream || File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
                Logger.new(stream)
              end
end

.make_service_listener(mod) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/servicy.rb', line 74

def self.make_service_listener(mod)
  # If we didn't manage to connect to a remote client, then we are, by
  # definition, the provider. We should start the transports api_server, and
  # dispatch requests here.
  mod.send(:define_singleton_method, :start_servicy_server) do
    begin
      Thread.new do
        begin
          port      = Servicy.config.send(self.to_s.to_sym).port    || Servicy.config.server.port
          transport = Servicy.config.service.transport
          transport = transport.nil? ? Servicy.config.server.transport : transport
          client = Servicy::Client.new(transport.class.new(port: port))

          client.transport.start_api do |request|
            method = nil
            begin
              method = self.method(request.method_name.to_sym)
            rescue
              method = self.instance_method(request.method_name.to_sym)
              method = method.bind(self.new)
            end
            things = transport.unformat(request.args)
            args = method.parameters.map do |param|
              things[param.last.to_s]
            end

            Servicy.logger.info "Calling #{method} with #{args.inspect}"
            begin
              result = method.call(*args)
              Servicy::Transport::Message.api_response(result)
            rescue => e
              Servicy::Transport::Message.error(e.to_s + "\n" + e.backtrace.join("\n"))
            end
          end

          client.transport.stop
        rescue => e
          Servicy::Transport::Message.error(e.to_s + "\n" + e.backtrace.join("\n"))
        end
      end

      Servicy.logger.info "Creating #{self.to_s} api"
      api = Servicy::Client.create_api_for(self)
      port = Servicy.config.send(mod.to_s.to_sym).port
      port = port.nil? ? Servicy.config.server.port : port
      api.const_set 'PORT', port
      # TODO: the other things, too
      Servicy.logger.debug("Chose port, #{port} for service, #{mod.to_s}")

      server_port = Servicy.config.server.port
      transport = Servicy.config.server.transport
      client    = Servicy::Client.new(transport.class.new(port: server_port))
      Servicy.logger.info "Registering api, #{api.to_s}"
      client.register_service api
    rescue => e
      Servicy.logger.error "An error occurred with the service: #{e.to_s}\n#{e.backtrace.join("\n")}"
    end
  end
rescue => e
  # Again, just let it die silently. There is no reason to make things not
  # work locally.
  Servicy.logger.error(e)
end