servicy 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d9048f3bb1f1521a29ceeb800dd7b3eb5149c9b7
4
- data.tar.gz: 1c474e01e90d51469893fe19be368004cc4654d8
3
+ metadata.gz: 037bc07d1d2d5f3347fbe3502ef0b03d09286cc3
4
+ data.tar.gz: 804eb780db64765afe2902d4359e71b6c00637ed
5
5
  SHA512:
6
- metadata.gz: 7eb75dfafdc14c501aeb551579d839991aa312b3d554cadcc1efb410f1c9636651f9d6a8516b88415fe0739b867f242c95fd5e0adec9c067941d95c094e54d9b
7
- data.tar.gz: 4986b4d40c49b4bb5b78f427fb5ccb25da068cb22bb225c09ca4adc245a8805af3f6ce62acb1f5b44153599a5cb740064a01700a172e9c6796ac23a5a964b126
6
+ metadata.gz: 5255eb13f70e5a1ec8df4341040d0c04a8ff673f4d86c1ac67dd3b542ff44eb2c9053f41876bd04a0b154832995d136bf55e276e192bd6e567c505d3e67dca02
7
+ data.tar.gz: 457ec866aafbcc3ea6be1cb41de07553ea2d8e7c4a691b42be1cb54c6b6d2005ebdfd2e487369c7fc85debc9ccc381c12342b186c69f03d5edb039a466395d70
data/README.md CHANGED
@@ -8,242 +8,101 @@ SOA application configuration to register themselves by name and/or API, and
8
8
  allow service consumers to find instances of services to use, and be relatively
9
9
  sure of up-time, routing, etc.
10
10
 
11
- Servicy could possibly used as a router as well, although that may be out of
12
- scope we will see.
11
+ The second goal of Servicy is to build a framework that makes it easy to write
12
+ services that are easy to write, use and test. In most cases, simply including
13
+ Servicy in a class containing your business logic. It is my opinion that a
14
+ programmer should focus on solving business problems, not architectural ones
15
+ (where possible), and that code should be, above all else, easy to read and
16
+ maintain.
13
17
 
14
- General architecture ideas
15
- ==========================
18
+ An example of Servicy can be found [here](https://github.com/thomasluce/servicy_example).
16
19
 
17
- Servicy would be broken into a few pieces:
20
+ Configuring Servicy
21
+ ===================
18
22
 
19
- 1. A daemon which can handle registration and query requests
20
- 2. An in-memory database of services to facilitate queries
21
- 3. A persistent cache of services to allow for fast restarts
22
- 4. (possibly) A service/API router
23
- 5. Reporting service to report on service health and usage.
24
- 6. Client library for service discovery and connection
25
- 7. Service library for registration, API discovery and building
23
+ To configure Servicy for your project, there are a few options. The easiest,
24
+ and recommended way, is to use code similar to the following:
26
25
 
27
- One big issue that will have to be resolved is version dependencies. If there
28
- are two versions of a service, each with a different public API, then Servicy
29
- should know about the differences and send back to a service requester the
30
- correct version based on their requirements. For this, Servicy should have a
31
- way of discovering and/or services defining their API to Servicy.
32
-
33
- Each service should also be able to define multiple instances, and Servicy
34
- should have multiple strategies for choosing instances of a given service. For
35
- example, round-robin, load-based (would require machine-level meta-data to be
36
- reported by the services), even requests, etc. To start with, a simple
37
- round-robin scheme will likely be the only implementation. Furthermore, Servicy
38
- should have knowledge of if a service is up or down, and automatically remove
39
- from its list of available providers, or add it back in as needed.
40
-
41
- Also, Servicy should maintain statistics on usage for services and be able to
42
- report that information back to a user. Stats should include number of
43
- discoveries, cache hits/misses, service up-times, provider failures, and, if it
44
- works as a router, request latency. In the event that I end up _not_ making it
45
- a router (seems likely at this point), it should still report latency for
46
- service discovery and query operations that require back-and-forth with the
47
- service.
48
-
49
- Protocol ideas
50
- ==============
51
-
52
- Servicy is protocol agnostic, and the exact transport mechanism used to
53
- communicate between client and server doesn't matter. The data is sent as JSON
54
- underneath it all (for now; I may even change things to be format agnostic in
55
- the future). You can see examples of the JSON by looking in the
56
- Servicy::Transport::Message class.
57
-
58
- Service registration
59
- --------------------
60
-
61
- Service registration allows for a service to declare that they exist, how to
62
- reach them, and what functionality they provide. Services are named using a
63
- Java-style, inverted domain name. For example:
64
-
65
- ```
66
- com.mycompany.users.authentication
67
- ```
68
-
69
- might be the name of a user authentication service provided internally. This
70
- allows for services to be broken up by what other services they interact with,
71
- as well as be easy to read for users browsing the registry.
72
-
73
- Beyond the name, a service must provide some other information for its
74
- registry. This includes:
75
-
76
- 1. Host to connect to
77
- 2. Port on the host to connect to
78
- 3. Protocol used to communicate (HTTP, HTTPS, Thrift, etc.)
79
- 4. Version number
80
- 5. Heartbeat port
81
-
82
- The host can be either an IP address or a fully-qualified hostname. In either
83
- case it should be route-able in the context of the Servicy host. In other
84
- words, if Servicy cannot connect with it, it won't add it to the registry. The
85
- port should be an integer between 1 and 65535. The protocol can be anything,
86
- however some standards are:
87
-
88
- * HTTP - _Must_ use un-encrypted HTTP
89
- * HTTPS - _Must_ use TLS over HTTP
90
- * HTTP/S - Can use either HTTP or HTTPS
91
- * Thrift
92
- * TCP - Direct, telnet-like communication
93
- * UDP - Direct connection, but don't expect replies
94
-
95
- The protocol says nothing about what information needs to be sent or received
96
- by a client consuming the service. It is assumed that if the client is looking
97
- for the service, it knows how to use it once it is found, and will only use the
98
- protocol information to select between possible options provided by the
99
- consuming library. Protocols can also be provided as a list of possible
100
- options, ordered with most preferred first. At some point in the future, there
101
- may be a mechanism by which a service can describe itself in a more useful way,
102
- and client libraries can use that information to dynamically build an internal
103
- API.
104
-
105
- The version number must follow the format:
26
+ Servicy global settings
27
+ -----------------------
106
28
 
107
- ```
108
- major.minor.revision-patch
109
- ```
29
+ ```ruby
30
+ Servicy.configure do |config|
31
+ config.server.host = 'localhost'
32
+ config.server.transport = Servicy::Transport.InMemory.new
33
+ config.server.load_balancer = Servicy::RoundRobinLoadBalancer.new
34
+ config.server.logger_stream = File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
35
+ config.server.config_file = File.expand_path("~/.servicy.conf")
110
36
 
111
- The "-patch" part can be omitted. For example:
37
+ config.client.transport = Servicy::Transport.InMemory.new
38
+ config.transport.format = Servicy::Formats.JSON
112
39
 
40
+ config
41
+ end
113
42
  ```
114
- 1.0.2-p143
115
- ```
116
-
117
- The heartbeat port is a port that Servicy can connect to to ensure that the
118
- service is still functioning. In many cases this will be the same port as the
119
- primary connection port; Servicy does not attempt to use the heartbeat
120
- connection for anything. It simply connects, and if successful, disconnects
121
- again. For this reason, your services should be resilliant to this type of
122
- traffic.
123
-
124
- Along with the required information already listed, a service can provide some
125
- optional information about the API that they provide. This information comes
126
- with some pieces of information for each API endpoint or action provided. The
127
- information is:
128
-
129
- 1. Endpoint identifier
130
- 2. Parameter information
131
- 3. Return information
132
- 4. (optional) Human-readable documentation.
133
43
 
134
- The endpoint identifier can be anything, and is just a string. For example, in
135
- an HTTP/S RESTful API, it may be something like:
136
-
137
- ```
138
- /api/v1/user/login
44
+ The available options are:
45
+ * server.host
46
+ The host to connect to when registering or finding services.
47
+ * server.transport
48
+ The transport protocol to use when speaking with the server
49
+ * server.load_balancer
50
+ The load-balancing strategy to be used by the server when multiple providers
51
+ for the same service are available.
52
+ * server.logger_stream
53
+ An IO object to write logs to
54
+ * server.config_file
55
+ The path of the place to save and load configurations from.
56
+ * client.transport
57
+ The transport protocol for a created service to use.
58
+ * transport.format
59
+ The format to send over the wire, regardless of communication protocol.
60
+
61
+ You can also set service-specific options. For example, if you have a service
62
+ called "Hello", you could use:
63
+
64
+ ```ruby
65
+ ...
66
+ config.Hello.language = "english"
67
+ ...
139
68
  ```
140
69
 
141
- The parameter information is information about each parameter that is expected
142
- by this endpoint. This data is structured, and requires information about name,
143
- data type, and weather or not the parameter is required.
144
-
145
- The return information is simply a description of the data that should be
146
- expected to return.
147
-
148
- The documentation is an arbitrary-length text string which can be used as
149
- documentation by a developer developing against the API.
150
-
151
- Multiple service providers can provide the same service. In that case, they are
152
- simply added to a pool of possible providers that is selected from using some
153
- load-balancing strategy such as round-robin or random selection.
154
-
155
- Any service provider that has registered will periodically get ICMP pinged by
156
- Servicy. If a response does not come back, it will be removed from the pool of
157
- providers. If a response does come back, Servicy will then attempt to connect
158
- to the heartbeat port to determine if the service is still functioning on the
159
- box. If it is not, the provider will be removed from the pool, otherwise it
160
- will be left in. This two-step process is to facilitate better logging and
161
- debugging of issues.
162
-
163
- Service de-registration
164
- -----------------------
165
-
166
- A service can remove itself from the pool voluntarily be simply sending the
167
- name, version, host, and port of the service to be removed. However, this is
168
- not strictly needed as the periodic heartbeat check will notice if a service
169
- goes away. These checks happen every second.
170
-
171
- A note on security
172
- ++++++++++++++++++
173
-
174
- For the time-being, it is assumed that you will have Servicy and all the
175
- services it interacts with installed on internal, secured infrastructure. As
176
- such, Servicy takes a very trusting policy. However, that means that someone
177
- with malintent and access to your network could de-register services, or
178
- register a bunch of fake ones. Future versions of Servicy may attempt to
179
- resolve this issue. We'll see...
180
-
181
- Service discovery
182
- -----------------
70
+ and then read the value in your service using `Servicy.config.Hello.language`.
71
+ See the [example project](https://github.com/thomasluce/servicy_example) for
72
+ more information.
183
73
 
184
- Once services are registered, a client needs to be able to discover them.
185
- Generally, they will discover a service and hold on to that information for the
186
- lifetime of their use, only requesting a new handle should the old one be lost.
187
- However, there is no reason why they couldn't request a new one every time.
188
-
189
- *NOTE* In the future, it may be possible for Servicy to act as a router by
190
- simply returning connection information for itself, and doing blind
191
- pass-through with some strategy to the other providers. In that case, the
192
- policy that a client uses to cache or not cache their connection information
193
- may have to change.
194
-
195
- A service consumer can query using one or both of two different dimensions:
196
-
197
- 1. API name
198
- 2. API functionality
199
-
200
- When using the name dimension, you can also query by version using either an
201
- exact match, a list of preferences, a range, a minimum, or a maximum.
202
-
203
- When querying using functionality, you query using one or more API method
204
- fingerprints. The fingerprints are in the following format:
74
+ Per-service settings
75
+ --------------------
205
76
 
206
- ```
207
- method_name#arg-type,arg-type,...#return_type
208
- ```
77
+ If you are extracting an existing application into multiple services, you can
78
+ also define constants on your service classes that will be used when
79
+ automatically creating API's.
209
80
 
210
- where method_name is the name of the method you would like to call (or service
211
- end-point, or whatever. This is the same as the API endpoint in the service
212
- functionality registration information.), the `arg-type` pairs are the name of
213
- an argument (or an empty string in the case of un-named arguments), followed by
214
- a dash, followed by the argument type, and the return_type being the data-type
215
- for the return value.
81
+ ```ruby
82
+ class MySuperAwesomeService
83
+ PORT = 1234
84
+ HEARTBEAT_PORT = 1235
85
+ VERSION = '1.2.6-p1098'
86
+ DOMAIN = 'com.mycompany'
216
87
 
217
- For example, a query could look something like this:
88
+ ...
218
89
 
219
- ```
220
- user_create#username-string,password-string#User
90
+ include Servicy
91
+ end
221
92
  ```
222
93
 
223
- Behind the scenes, Servicy will use this information to find all possible
224
- services that can fulfill this request. This will include any service that can
225
- perform this request but that may have more optional parameters. Type
226
- information can also be provided as a list of types, in the case of API's that
227
- can handle multiple types of information:
228
-
229
- ```
230
- user_create#username-string,password-string|signed_string#User
231
- ```
94
+ Note that in all of these cases, we `include Servicy` at the end of the
95
+ class/module so that the `.included` hook has maximum access to defined
96
+ methods. It still will not catch any methods that are added on in code that is
97
+ loaded after Servicy is included.
232
98
 
233
- The possible other types that the data can be are separated by a pipe, '|'.
99
+ Other options
100
+ -------------
234
101
 
235
- Finally, the service consumer can provide a boolean flag indicating weather or
236
- not they wish to receive API information about the returned providers, or if
237
- they simply want connection information. If the flag is set to true, they will
238
- get full API documentation and definitions along with connection information.
102
+ Most settings are also changeable in creation of the objects themselves through
103
+ argument hashes, in the case where you just want to use one small part of
104
+ Servicy. See the full documentation and/or code for more information.
239
105
 
240
- The query system will either provide a successful "error"-code, along with a
241
- list of one or more service providers, a "not-found" error-code indicating that
242
- the query was valid, but that there are no available services which meet the
243
- requirements, or an "error" error-code that indicates that something was wrong
244
- with the provided query. In the last two queries, additional error information
245
- may be provided, but should be considered for human-use only; ie, showing an
246
- error message to a user.
247
106
 
248
107
  Notes on design of your objects
249
108
  ===============================
@@ -256,7 +115,7 @@ design.
256
115
  Servicy's goal is to make it simple for you to build microservices in Ruby that
257
116
  can either run locally or remotely without the developer having to worry about
258
117
  or care which. That said, as the developer of a service, it is _your_
259
- responsibility to make sure that you don't make services that suck (tm).
118
+ responsibility to make sure that you don't make Services That Suck (tm).
260
119
 
261
120
  Don't build your objects to have iterators that require multiple-traversals
262
121
  over the wire. Don't build objects that lazy-evaluate things. Don't build
@@ -271,11 +130,10 @@ TODO
271
130
 
272
131
  Things left to do:
273
132
 
274
- 1. In the command-line tool, there is some kind of bug where the registration
275
- message never returns back to the client. This causes registration to hang
276
- forever...
277
- 1. I should sit down and really think out code structure and refactor where
278
- needed. Things are getting a little difficult to follow if you are jumping
279
- around between files a bunch.
280
- 1. I need to cut down on the number of latency and heartbeat entries that are
281
- saved. It gets a bit out of hand.
133
+ 1. More protocols and formatters
134
+ 1. Make service requests (optionally?) polling so that they can pick up on
135
+ services going up/down out of order.
136
+ 1. Optional service-to-local function reverting in the event of a remote
137
+ service failure.
138
+ 1. Work on code coverage.
139
+ 1. Fix all the default config options. transport.port?! WTF was I thinking?!
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: servicy 0.0.5 ruby lib
5
+ # stub: servicy 0.0.6 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "servicy"
9
- s.version = "0.0.5"
9
+ s.version = "0.0.6"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Thomas Luce"]
14
- s.date = "2014-08-15"
14
+ s.date = "2014-08-19"
15
15
  s.description = "A service registration and discovery framework, as a server and client library."
16
16
  s.email = "thomas.luce@gmail.com"
17
17
  s.executables = ["servicy"]
@@ -25,7 +25,6 @@ Gem::Specification.new do |s|
25
25
  "bin/servicy",
26
26
  "lib/api.rb",
27
27
  "lib/client.rb",
28
- "lib/configurable.rb",
29
28
  "lib/formats.rb",
30
29
  "lib/formats/json.rb",
31
30
  "lib/hash.rb",
@@ -37,6 +36,10 @@ Gem::Specification.new do |s|
37
36
  "lib/server/service_searcher.rb",
38
37
  "lib/service.rb",
39
38
  "lib/servicy.rb",
39
+ "lib/servicy/config.rb",
40
+ "lib/servicy/configurable.rb",
41
+ "lib/servicy/magic.rb",
42
+ "lib/servicy/servicy_logger.rb",
40
43
  "lib/transport/in_memory_transport.rb",
41
44
  "lib/transport/messages.rb",
42
45
  "lib/transport/null_transport.rb",
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.5
1
+ 0.0.6
data/lib/api.rb CHANGED
@@ -121,7 +121,6 @@ module Servicy
121
121
  Object.const_set(class_name, magic_class)
122
122
  end
123
123
 
124
- # TODO: API wrappers that create a server of some kind to serve things up
125
124
  # TODO: A gem/library generator based on the API
126
125
  end
127
126
 
@@ -28,7 +28,7 @@ module Servicy
28
28
  end
29
29
 
30
30
  if !args[:port].is_a?(Fixnum) || args[:port] < 1 || args[:port] > 65535
31
- raise ArgumentError.new("Service port must be an integer betwee 1-65535")
31
+ raise ArgumentError.new("Service port must be an integer betwee 1-65535. Got: #{args[:port].inspect}")
32
32
  end
33
33
 
34
34
  args[:heartbeat_port] ||= args[:port]
@@ -118,7 +118,7 @@ module Servicy
118
118
  end
119
119
  record_heartbeat(1)
120
120
  return true
121
- rescue
121
+ rescue => e
122
122
  record_heartbeat(0)
123
123
  return false
124
124
  ensure
@@ -181,12 +181,12 @@ module Servicy
181
181
  @transport.unformat(result)
182
182
  end
183
183
 
184
- private
185
-
186
184
  def skip_heartbeat?
187
185
  !!no_heartbeat
188
186
  end
189
187
 
188
+ private
189
+
190
190
  def transport_from_class_or_string(transport)
191
191
  transport.is_a?(Servicy::Transport) ? transport : transport_from_string(transport)
192
192
  end
@@ -198,12 +198,12 @@ module Servicy
198
198
  # These are just to keep us from filling up memory.
199
199
  def record_latency(t1, t2)
200
200
  @latencies << t2.to_i - t1.to_i
201
- @latencies = @latencies[0...10] if @latencies.length > 10
201
+ @latencies.slice!(2...10) if @latencies.length > 10
202
202
  end
203
203
 
204
204
  def record_heartbeat(h)
205
205
  @heartbeats << h
206
- @heartbeats = @heartbeats[0...10] if @heartbeats.length > 10
206
+ @heartbeats.slice!(2...10) if @heartbeats.length > 10
207
207
  end
208
208
 
209
209
  # The api is broken into two kinds of methods; instance and class. Each can
@@ -5,159 +5,6 @@ require 'formats'
5
5
  require 'server'
6
6
  require 'client'
7
7
  require 'api'
8
- require 'configurable'
9
- require 'logger'
10
-
11
- module Servicy
12
- # Get a logger instance that we can do something useful with.
13
- def self.logger
14
- @logger ||= begin
15
- stream = config.server.logger_stream
16
- stream = stream || File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
17
- Logger.new(stream)
18
- end
19
- end
20
-
21
- # After this method gets done running, you either have the state of the world
22
- # exactly the same as it was, or your object is replaced in the global Object
23
- # scope with a new object that will proxy methods to a remote service
24
- # provider.
25
- #
26
- # To define a service provider is a bit more work, and less auto-magic
27
- # because the exact details of the infrastructure and architecture of your
28
- # setup will be very different from others. So Servicy doesn't make
29
- # assumptions about how you want to do things. That said, it's relatively
30
- # simple:
31
- #
32
- # # In some server class
33
- # Servicy::Server.new(...).go!
34
- #
35
- # # In some service provider, possibly on a different box
36
- # api = Servicy::Client.create_api_for(MyServiceClass)
37
- # Servicy::Client.register_service(api)
38
- # FIXME: @__api, and it's ilk, get overridden in the even that there is more
39
- # than one.
40
- def self.included(mod)
41
- make_service_listener(mod)
42
-
43
- # Attempt to find the service
44
- client = Servicy::Client.new(config.server.transport)
45
- raise 'go to bottom' unless client.connected?
46
-
47
- # Create a new api object
48
- @__api = Servicy::Client.create_api_for(mod)
49
- port = Servicy.config.send(mod.to_s.to_sym).port
50
- Servicy.logger.debug("Chose port, #{port} for service, #{mod.to_s}")
51
- @__api.const_set("PORT", port) unless port.nil?
52
- # TODO: for the other things as well
53
-
54
- @__service = client.find_service(@__api.search_query)
55
- raise 'go to bottom' unless @__service
56
-
57
- # If all went well, then we can wrap the object to call to the remote
58
- # service.
59
- client = Servicy::Client.new(config.client.transport.class.new(port: port))
60
- @__api.set_remote(client)
61
-
62
- # ... and delegate that ish. We do it by killing off the original object,
63
- # and replacing it with our own.
64
- Object.send(:remove_const, mod.name.to_sym)
65
- Object.const_set(mod.name.to_sym, @__api)
66
- rescue => e
67
- # Something went wrong, just give up silently, but don't do anything
68
- # else; use this object as a regular-ass object. This is to allow you to
69
- # use your API objects as normal objects in an environment where the
70
- # object is bundled as part of a gem (,say, ) and there is no remote
71
- # handler for it: use it locally
72
- end
73
-
74
- def self.make_service_listener(mod)
75
- # If we didn't manage to connect to a remote client, then we are, by
76
- # definition, the provider. We should start the transports api_server, and
77
- # dispatch requests here.
78
- mod.send(:define_singleton_method, :start_servicy_server) do
79
- begin
80
- Thread.new do
81
- begin
82
- port = Servicy.config.send(self.to_s.to_sym).port || Servicy.config.server.port
83
- transport = Servicy.config.service.transport
84
- transport = transport.nil? ? Servicy.config.server.transport : transport
85
- client = Servicy::Client.new(transport.class.new(port: port))
86
-
87
- client.transport.start_api do |request|
88
- method = nil
89
- begin
90
- method = self.method(request.method_name.to_sym)
91
- rescue
92
- method = self.instance_method(request.method_name.to_sym)
93
- method = method.bind(self.new)
94
- end
95
- things = transport.unformat(request.args)
96
- args = method.parameters.map do |param|
97
- things[param.last.to_s]
98
- end
99
-
100
- Servicy.logger.info "Calling #{method} with #{args.inspect}"
101
- begin
102
- result = method.call(*args)
103
- Servicy::Transport::Message.api_response(result)
104
- rescue => e
105
- Servicy::Transport::Message.error(e.to_s + "\n" + e.backtrace.join("\n"))
106
- end
107
- end
108
-
109
- client.transport.stop
110
- rescue => e
111
- Servicy::Transport::Message.error(e.to_s + "\n" + e.backtrace.join("\n"))
112
- end
113
- end
114
-
115
- Servicy.logger.info "Creating #{self.to_s} api"
116
- api = Servicy::Client.create_api_for(self)
117
- port = Servicy.config.send(mod.to_s.to_sym).port
118
- port = port.nil? ? Servicy.config.server.port : port
119
- api.const_set 'PORT', port
120
- # TODO: the other things, too
121
- Servicy.logger.debug("Chose port, #{port} for service, #{mod.to_s}")
122
-
123
- server_port = Servicy.config.server.port
124
- transport = Servicy.config.server.transport
125
- client = Servicy::Client.new(transport.class.new(port: server_port))
126
- Servicy.logger.info "Registering api, #{api.to_s}"
127
- client.register_service api
128
- rescue => e
129
- Servicy.logger.error "An error occurred with the service: #{e.to_s}\n#{e.backtrace.join("\n")}"
130
- end
131
- end
132
- rescue => e
133
- # Again, just let it die silently. There is no reason to make things not
134
- # work locally.
135
- Servicy.logger.error(e)
136
- end
137
-
138
- # Use this method to configure Servicy for other services
139
- def self.configure(&block)
140
- conf = Configurable.new
141
-
142
- # Give some reasonable defaults
143
- conf.server.host = 'localhost'
144
- conf.server.transport = Servicy::Transport.InMemory.new
145
- conf.server.load_balancer = Servicy::RoundRobinLoadBalancer.new
146
- conf.server.logger_stream = File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
147
- conf.server.config_file = File.expand_path("~/.servicy.conf")
148
- # To set something for a given object, you would do something like:
149
- # conf.transport.port = 1234
150
-
151
- conf.client.transport = Servicy::Transport.InMemory.new
152
- conf.transport.format = Servicy::Formats.JSON
153
-
154
- conf = yield conf if block_given?
155
-
156
- # Set the options
157
- @_config = conf
158
- end
159
-
160
- def self.config
161
- @_config ||= configure
162
- end
163
- end
8
+ require 'servicy/servicy_logger'
9
+ require 'servicy/config'
10
+ require 'servicy/magic'
@@ -0,0 +1,31 @@
1
+ require 'servicy/configurable'
2
+
3
+ module Servicy
4
+ # Use this method to configure Servicy for other services
5
+ def self.configure(&block)
6
+ conf = Configurable.new
7
+
8
+ # Give some reasonable defaults
9
+ conf.server.host = 'localhost'
10
+ conf.server.transport = Servicy::Transport.InMemory.new
11
+ conf.server.load_balancer = Servicy::RoundRobinLoadBalancer.new
12
+ conf.server.logger_stream = File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
13
+ conf.server.config_file = File.expand_path("~/.servicy.conf")
14
+ # To set something for a given object, you would do something like:
15
+ # conf.transport.port = 1234
16
+
17
+ conf.client.transport = Servicy::Transport.InMemory.new
18
+ conf.transport.format = Servicy::Formats.JSON
19
+
20
+ conf = yield conf if block_given?
21
+ raise ArgumentError("Return the config object, please") unless conf.is_a?(Configurable)
22
+
23
+ # Set the options
24
+ @_config = conf
25
+ end
26
+
27
+ def self.config
28
+ @_config ||= configure
29
+ end
30
+ end
31
+
@@ -0,0 +1,120 @@
1
+ module Servicy
2
+ # After this method gets done running, you either have the state of the world
3
+ # exactly the same as it was, or your object is replaced in the global Object
4
+ # scope with a new object that will proxy methods to a remote service
5
+ # provider.
6
+ #
7
+ # To define a service provider is a bit more work, and less auto-magic
8
+ # because the exact details of the infrastructure and architecture of your
9
+ # setup will be very different from others. So Servicy doesn't make
10
+ # assumptions about how you want to do things. That said, it's relatively
11
+ # simple:
12
+ #
13
+ # # In some server class
14
+ # Servicy::Server.new(...).go!
15
+ #
16
+ # # In some service provider, possibly on a different box
17
+ # api = Servicy::Client.create_api_for(MyServiceClass)
18
+ # Servicy::Client.register_service(api)
19
+ # FIXME: @__api, and it's ilk, get overridden in the even that there is more
20
+ # than one.
21
+ def self.included(mod)
22
+ make_service_listener(mod)
23
+
24
+ # Attempt to find the service
25
+ client = Servicy::Client.new(config.server.transport)
26
+ raise 'go to bottom' unless client.connected?
27
+
28
+ # Create a new api object
29
+ @__api = Servicy::Client.create_api_for(mod)
30
+ port = Servicy.config.send(mod.to_s.to_sym).port
31
+ Servicy.logger.debug("Chose port, #{port} for service, #{mod.to_s}")
32
+ @__api.const_set("PORT", port) unless port.nil?
33
+ # TODO: for the other things as well
34
+
35
+ @__service = client.find_service(@__api.search_query)
36
+ raise 'go to bottom' unless @__service
37
+
38
+ # If all went well, then we can wrap the object to call to the remote
39
+ # service.
40
+ client = Servicy::Client.new(config.client.transport.class.new(port: port))
41
+ @__api.set_remote(client)
42
+
43
+ # ... and delegate that ish. We do it by killing off the original object,
44
+ # and replacing it with our own.
45
+ Object.send(:remove_const, mod.name.to_sym)
46
+ Object.const_set(mod.name.to_sym, @__api)
47
+ rescue => e
48
+ # Something went wrong, just give up silently, but don't do anything
49
+ # else; use this object as a regular-ass object. This is to allow you to
50
+ # use your API objects as normal objects in an environment where the
51
+ # object is bundled as part of a gem (,say, ) and there is no remote
52
+ # handler for it: use it locally
53
+ end
54
+
55
+ def self.make_service_listener(mod)
56
+ # If we didn't manage to connect to a remote client, then we are, by
57
+ # definition, the provider. We should start the transports api_server, and
58
+ # dispatch requests here.
59
+ mod.send(:define_singleton_method, :start_servicy_server) do
60
+ begin
61
+ Thread.new do
62
+ begin
63
+ port = Servicy.config.send(self.to_s.to_sym).port || Servicy.config.server.port
64
+ transport = Servicy.config.service.transport
65
+ transport = transport.nil? ? Servicy.config.server.transport : transport
66
+ client = Servicy::Client.new(transport.class.new(port: port))
67
+
68
+ client.transport.start_api do |request|
69
+ method = nil
70
+ begin
71
+ method = self.method(request.method_name.to_sym)
72
+ rescue
73
+ method = self.instance_method(request.method_name.to_sym)
74
+ method = method.bind(self.new)
75
+ end
76
+ things = transport.unformat(request.args)
77
+ args = method.parameters.map do |param|
78
+ things[param.last.to_s]
79
+ end
80
+
81
+ Servicy.logger.info "Calling #{method} with #{args.inspect}"
82
+ begin
83
+ result = method.call(*args)
84
+ Servicy::Transport::Message.api_response(result)
85
+ rescue => e
86
+ Servicy::Transport::Message.error(e.to_s + "\n" + e.backtrace.join("\n"))
87
+ end
88
+ end
89
+
90
+ client.transport.stop
91
+ rescue => e
92
+ Servicy::Transport::Message.error(e.to_s + "\n" + e.backtrace.join("\n"))
93
+ end
94
+ end
95
+
96
+ Servicy.logger.info "Creating #{self.to_s} api"
97
+ api = Servicy::Client.create_api_for(self)
98
+ port = Servicy.config.send(mod.to_s.to_sym).port
99
+ port = port.nil? ? Servicy.config.server.port : port
100
+ api.const_set 'PORT', port
101
+ # TODO: get port information from the class PORT stuff, too
102
+ # TODO: the other things, too
103
+ Servicy.logger.debug("Chose port, #{port} for service, #{mod.to_s}")
104
+
105
+ server_port = Servicy.config.server.port
106
+ server_port = server_port.nil? ? 1234 : server_port
107
+ transport = Servicy.config.server.transport
108
+ client = Servicy::Client.new(transport.class.new(port: server_port))
109
+ Servicy.logger.info "Registering api, #{api.to_s}"
110
+ client.register_service api
111
+ rescue => e
112
+ Servicy.logger.error "An error occurred with the service: #{e.to_s}\n#{e.backtrace.join("\n")}"
113
+ end
114
+ end
115
+ rescue => e
116
+ # Again, just let it die silently. There is no reason to make things not
117
+ # work locally.
118
+ Servicy.logger.error(e)
119
+ end
120
+ end
@@ -0,0 +1,16 @@
1
+ require 'logger'
2
+
3
+ module Servicy
4
+ # Get a logger instance that we can do something useful with.
5
+ def self.logger
6
+ @logger ||= begin
7
+ stream = config.server.logger_stream
8
+ stream = stream || File.open(File.join('/var', 'log', 'servicy_server.log'), 'a')
9
+ level = config.log_level
10
+ level = level.nil? ? Logger::WARN : level
11
+ l = Logger.new(stream)
12
+ l.level = level
13
+ l
14
+ end
15
+ end
16
+ end
@@ -28,12 +28,14 @@ module Servicy
28
28
  end
29
29
 
30
30
  def stop
31
- @server && @server.close
31
+ @server && @server.close rescue nil
32
32
  end
33
33
 
34
34
  def socket_path
35
35
  @socket_path ||= begin
36
- file = Tempfile.new(@config[:path] || Servicy.config.transport.path || 'servicy-in-memory-transport')
36
+ config_path = Servicy.config.transport.path
37
+ config_path = config_path.nil ? 'servicy-in-memory-transport' : config_path
38
+ file = Tempfile.new(@config[:path] || config_path)
37
39
  file.close
38
40
  path = file.path
39
41
  file.delete
@@ -65,7 +65,9 @@ module Servicy
65
65
  end
66
66
 
67
67
  def port
68
- @config[:port] || Servicy.config.transport.port
68
+ config_port = Servicy.config.transport.port
69
+ config_port = config_port.nil? ? 1234 : config_port
70
+ @config[:port] || config_port
69
71
  end
70
72
  end
71
73
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: servicy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Luce
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-15 00:00:00.000000000 Z
11
+ date: 2014-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: contraction
@@ -39,7 +39,6 @@ files:
39
39
  - bin/servicy
40
40
  - lib/api.rb
41
41
  - lib/client.rb
42
- - lib/configurable.rb
43
42
  - lib/formats.rb
44
43
  - lib/formats/json.rb
45
44
  - lib/hash.rb
@@ -51,6 +50,10 @@ files:
51
50
  - lib/server/service_searcher.rb
52
51
  - lib/service.rb
53
52
  - lib/servicy.rb
53
+ - lib/servicy/config.rb
54
+ - lib/servicy/configurable.rb
55
+ - lib/servicy/magic.rb
56
+ - lib/servicy/servicy_logger.rb
54
57
  - lib/transport/in_memory_transport.rb
55
58
  - lib/transport/messages.rb
56
59
  - lib/transport/null_transport.rb