kiyohime 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +34 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +128 -0
- data/Rakefile +6 -0
- data/bin/console +18 -0
- data/bin/setup +8 -0
- data/examples/services/service_with_bespoke_handler.rb +16 -0
- data/examples/services/service_with_generic_handler.rb +9 -0
- data/kiyohime.gemspec +37 -0
- data/lib/kiyohime.rb +15 -0
- data/lib/kiyohime/containers/service_registration.rb +14 -0
- data/lib/kiyohime/exceptions/registry_error.rb +13 -0
- data/lib/kiyohime/exceptions/subscriber_error.rb +15 -0
- data/lib/kiyohime/exceptions/unsupported_channel_name.rb +7 -0
- data/lib/kiyohime/exceptions/unsupported_interface.rb +7 -0
- data/lib/kiyohime/parsers/channel_parser.rb +45 -0
- data/lib/kiyohime/publisher.rb +23 -0
- data/lib/kiyohime/publisher_helper.rb +15 -0
- data/lib/kiyohime/registry.rb +92 -0
- data/lib/kiyohime/stores/redis_store.rb +23 -0
- data/lib/kiyohime/version.rb +3 -0
- metadata +182 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 53324caf2d1e0c1d072aca1d030c8adab4079e17
|
4
|
+
data.tar.gz: 6190d412823f68eb6ffe3ccb692abfe09a7a3141
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fa6c6845785f576466c9f8d3369a274f7e4e331c410b8a36a9e935c01ba2d79572dee2a09bde8593c2f99cd796c02e4a2777c9095950f6ce9f9eb1e484166c13
|
7
|
+
data.tar.gz: bd86c5a0b051733e5e24a85291ea8d58354bbda6349d8dcf88feb3dce58ada7e2f1c7d8cddd2979456b56b2fef9d8e6f54eb7b4745cddde2fd8fe1dae16b7414
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
ClassLength:
|
2
|
+
Max: 120
|
3
|
+
|
4
|
+
LineLength:
|
5
|
+
Max: 200
|
6
|
+
|
7
|
+
MethodLength:
|
8
|
+
CountComments: false # count full line comments?
|
9
|
+
Max: 15
|
10
|
+
|
11
|
+
Metrics/AbcSize:
|
12
|
+
Max: 30
|
13
|
+
|
14
|
+
Encoding:
|
15
|
+
Description: 'Use UTF-8 as the source file encoding.'
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Style/GuardClause:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Lint/UnusedBlockArgument:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
AllCops:
|
25
|
+
Exclude:
|
26
|
+
- bin/**/*
|
27
|
+
- db/**/*
|
28
|
+
- config/**/*
|
29
|
+
- Rakefile
|
30
|
+
- script/**/*
|
31
|
+
- spec/factories/**/*
|
32
|
+
- spec/test_app/**/*
|
33
|
+
- node_modules/**/*
|
34
|
+
- !ruby/regexp /old_and_unused\.rb$/
|
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at shirren@me.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Shirren Premaratne
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# Kiyohime
|
2
|
+
|
3
|
+
Kiyohime is a microservice framework written in Ruby. In Ruby microservices are traditionally written as RESTful services using established frameworks (e.g. Rails, Sinatra, Grape etc). At present there appears to be no elegant microservice framework for the Ruby language that works on a publish/subscribe model. Kiyohime is an attempt to fill this gap.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'kiyohime'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install kiyohime
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Kiyohime works by providing a registry. The registry is why services register their interest to receive messages from publishers. The simplest subscriber can be a PORO (plain old ruby object) which implements a method named handle. So for example we could write the following service;
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class MySubscriber
|
27
|
+
def handle(message)
|
28
|
+
puts "My subscriber handle invoke: #{message}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
To register this service and it's method as a subscriber we first need to create an instance of the Registry which we can then use to register our MySubscriber service. The registry is currently powered by Redis. The example below demonstrates how to use the registry;
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
service = MySubscriber.new
|
37
|
+
store = Kiyohime::Stores::RedisStore.new
|
38
|
+
registry = Kiyohime::Registry.new('My Registry', store)
|
39
|
+
registry.register_services(service)
|
40
|
+
```
|
41
|
+
|
42
|
+
Runnin the code above should print the following statements to the console;
|
43
|
+
|
44
|
+
```
|
45
|
+
Registering service: kiyohime.registry.deregister
|
46
|
+
Registering service: mysubscriber
|
47
|
+
```
|
48
|
+
|
49
|
+
Now what we have is a channel named 'mysubscriber' to which we can publish a message. To publish a message one can use Kiyohime, but as we use Redis, a message can be published to our Ruby service in a platform and language agnostic manner. So for example if we have the Redis CLI installed we could send a message to our service using the following command;
|
50
|
+
|
51
|
+
```
|
52
|
+
redis-cli publish "mysubscriber" "test message"
|
53
|
+
```
|
54
|
+
|
55
|
+
If you would like to send/publish a message using Kiyohime we can do use as shown below;
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
publisher = Kiyohime::Publisher.new
|
59
|
+
publisher.publish('mysubscriber', 'another test message')
|
60
|
+
```
|
61
|
+
|
62
|
+
It is important to note that both the publisher and the registry can take a custom Redis object if required. So what if want to write a more sophisticated service, instead of a simple service with the prescriptive interface with a handle method? Well this is certainly possible. Suppose we have the following service definition;
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
class MyComplexSubscriber
|
66
|
+
def method1(message)
|
67
|
+
puts "My subscriber method1 invoke: #{message}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def method2(hash_arg)
|
71
|
+
puts "My subscriber method2 invoke: #{hash_arg[:key1]}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def method3(hash_arg)
|
75
|
+
puts "My subscriber method3 invoke: #{hash_arg[:key1]}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
`MyComplexSubscriber` has 3 methods, and I'd like to register method1 and method2 only as subcribers. To do this we need to use a class called the service container. The code below illustrates how to use Kiyohime service container to register method1 and method2 from MyComplexSubscriber.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
my_complex_service = MyComplexSubscriber.new
|
84
|
+
service_container = Kiyohime::Containers::ServiceRegistration.new(my_complex_service, :method1, :method2)
|
85
|
+
store = Kiyohime::Stores::RedisStore.new
|
86
|
+
registry = Kiyohime::Registry.new('My Registry', store)
|
87
|
+
registry.register_containers(service_container)
|
88
|
+
```
|
89
|
+
|
90
|
+
Running the code above should print the following to your console;
|
91
|
+
|
92
|
+
```
|
93
|
+
Registering service: kiyohime.registry.deregister
|
94
|
+
Registering service: mycomplexsubscriber.method1
|
95
|
+
Registering service: mycomplexsubscriber.method2
|
96
|
+
```
|
97
|
+
|
98
|
+
If the statements do not appear in the order show above do not worry, as long as they appear your service subscribers have been setup correctly. Note how `method3` is not listed. We can see that when we created the `ServiceRegistration` we are also specifying which methods to setup as subscribers only.
|
99
|
+
|
100
|
+
Our examples use simple datatypes and collection types in the example. At present this is all we have tested, but in theory complex types can be passed in the message as long as the size of the complex type does not exceed 256kb, the default limit in Redis.
|
101
|
+
|
102
|
+
The methods `register_async` and `register_containers_async` can also take an array of subscribers. So for example if I wanted to register both my `MySubscriber` and `MyComplexSubscriber` in one go? Well the code sample below illustrates how this can be achieved;
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
service = MySubscriber.new
|
106
|
+
service_container1 = Kiyohime::Containers::ServiceRegistration.new(service, :handle)
|
107
|
+
my_complex_service = MyComplexSubscriber.new
|
108
|
+
service_container2 = Kiyohime::Containers::ServiceRegistration.new(my_complex_service, :method1, :method2)
|
109
|
+
store = Kiyohime::Stores::RedisStore.new
|
110
|
+
registry = Kiyohime::Registry.new('My Registry', store)
|
111
|
+
registry.register_containers_async(service_container1, service_container2)
|
112
|
+
```
|
113
|
+
|
114
|
+
## Development
|
115
|
+
|
116
|
+
After checking out the repo, run `rspec spec` to run the specs. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
117
|
+
|
118
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
119
|
+
|
120
|
+
## Contributing
|
121
|
+
|
122
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/shirren/kiyohime. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
123
|
+
|
124
|
+
|
125
|
+
## License
|
126
|
+
|
127
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
128
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'kiyohime/containers/service_registration'
|
5
|
+
require 'kiyohime/exceptions/subscriber_error'
|
6
|
+
require 'kiyohime/publisher'
|
7
|
+
require 'kiyohime/registry'
|
8
|
+
require 'kiyohime/stores/redis_store'
|
9
|
+
|
10
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
11
|
+
# with your gem easier. You can also use a different console, if you like.
|
12
|
+
require './examples/services/service_with_generic_handler'
|
13
|
+
|
14
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
15
|
+
require 'byebug'
|
16
|
+
|
17
|
+
require 'irb'
|
18
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Services
|
2
|
+
# Example service with bespoke handlers
|
3
|
+
class ServiceWithBespokeHandler
|
4
|
+
def method1(message)
|
5
|
+
puts "ServiceWithBespokeHandler Recived message #{message} on message1 receiver"
|
6
|
+
end
|
7
|
+
|
8
|
+
def method2(message)
|
9
|
+
puts "ServiceWithBespokeHandler Recived message #{message} on message2 receiver"
|
10
|
+
end
|
11
|
+
|
12
|
+
def method3(message)
|
13
|
+
puts "ServiceWithBespokeHandler Recived message #{message} on message3 receiver"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/kiyohime.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kiyohime/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'kiyohime'
|
8
|
+
spec.version = Kiyohime::VERSION
|
9
|
+
spec.authors = ['Shirren Premaratne']
|
10
|
+
spec.email = ['shirren@me.com']
|
11
|
+
|
12
|
+
spec.summary = 'Kiyohime is a library which provides a wrapper around Redis for pubsub.'
|
13
|
+
spec.description = 'Kiyohime is a library which provides a wrapper around Redis for pubsub.'
|
14
|
+
spec.homepage = 'http://github.com/shirren/kiyohime'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
|
+
# delete this section to allow pushing this gem to any host.
|
19
|
+
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.' unless spec.respond_to?(:metadata)
|
20
|
+
|
21
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
|
+
spec.bindir = 'exe'
|
23
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
|
+
spec.require_paths = ['lib']
|
25
|
+
|
26
|
+
# Add runtime depenencies here
|
27
|
+
spec.add_runtime_dependency 'em-hiredis', '~> 0.3'
|
28
|
+
spec.add_runtime_dependency 'redis', '~> 3.3'
|
29
|
+
|
30
|
+
# Add development dependencies here
|
31
|
+
spec.add_development_dependency 'byebug', '~> 8.2'
|
32
|
+
spec.add_development_dependency 'factory_girl', '~> 4.2'
|
33
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
34
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
35
|
+
spec.add_development_dependency 'rubocop', '~> 0.39'
|
36
|
+
spec.add_development_dependency 'simplecov', '~> 0.11'
|
37
|
+
end
|
data/lib/kiyohime.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'kiyohime/version'
|
2
|
+
require 'kiyohime/containers/service_registration'
|
3
|
+
require 'kiyohime/exceptions/subscriber_error'
|
4
|
+
require 'kiyohime/exceptions/unsupported_channel_name'
|
5
|
+
require 'kiyohime/exceptions/unsupported_interface'
|
6
|
+
require 'kiyohime/publisher'
|
7
|
+
require 'kiyohime/publisher_helper'
|
8
|
+
require 'kiyohime/registry'
|
9
|
+
require 'kiyohime/stores/redis_store'
|
10
|
+
|
11
|
+
# Kiyohime is a simple Microservice framework for Ruby which is built on the publish/subscribe
|
12
|
+
# paradigm
|
13
|
+
module Kiyohime
|
14
|
+
# Your code goes here...
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Kiyohime
|
2
|
+
module Containers
|
3
|
+
# Services can be registered via this container. It is principally used for services
|
4
|
+
# which add bespoke methods to the registry
|
5
|
+
class ServiceRegistration
|
6
|
+
attr_reader :service, :methods
|
7
|
+
|
8
|
+
def initialize(service, *methods)
|
9
|
+
@service = service
|
10
|
+
@methods = methods
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Kiyohime
|
2
|
+
module Exceptions
|
3
|
+
# This error is used to signal when a subscriber throws an error
|
4
|
+
class SubscriberError < StandardError
|
5
|
+
attr_reader :message, :action, :data
|
6
|
+
|
7
|
+
def initialize(message, action = nil, data = nil)
|
8
|
+
@message = message
|
9
|
+
@action = action
|
10
|
+
@data = data
|
11
|
+
super(message)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'kiyohime/exceptions/unsupported_channel_name'
|
2
|
+
|
3
|
+
module Kiyohime
|
4
|
+
module Parsers
|
5
|
+
# Microservices receive and send messages along channels, channel names can be derived
|
6
|
+
# from the name of the type
|
7
|
+
class ChannelParser
|
8
|
+
# Convert a type name to a channel name. A type name can be a Ruby type or a string
|
9
|
+
# representation of the type name
|
10
|
+
def parse_type_to_channel_name(type_name)
|
11
|
+
type_name = type_name.to_s.downcase.gsub(/::/, '.')
|
12
|
+
if type_name.empty? || !type_name_valid?(type_name)
|
13
|
+
raise Kiyohime::Exceptions::UnsupportedChannelName
|
14
|
+
else
|
15
|
+
type_name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Convert a type name and method name to a channel name. A type name can be a Ruby type or a string
|
20
|
+
# representation of the type name. A method name can be a symbol or a string
|
21
|
+
def parse_type_and_method_to_channel_name(type_name, method_name)
|
22
|
+
type_name = type_name.to_s.downcase.gsub(/::/, '.')
|
23
|
+
method_name = method_name.to_s.downcase
|
24
|
+
if type_name.empty? || method_name.empty? ||
|
25
|
+
!type_name_valid?(type_name) || !method_name_valid?(method_name)
|
26
|
+
raise Kiyohime::Exceptions::UnsupportedChannelName
|
27
|
+
else
|
28
|
+
"#{type_name}.#{method_name}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Validate the type name to make sure we can convert it to a valid channel name
|
35
|
+
def type_name_valid?(channel_name)
|
36
|
+
(/^[a-z]+(\.[a-z]+)*(\.[a-z]+){0,1}$/ =~ channel_name).nil? ? false : true
|
37
|
+
end
|
38
|
+
|
39
|
+
# Validate the method name to make sure we can convert it to a valid channel name
|
40
|
+
def method_name_valid?(method_name)
|
41
|
+
(/^[a-z]+[_0-9a-zA-Z]*$/ =~ method_name).nil? ? false : true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'kiyohime/stores/redis_store'
|
3
|
+
|
4
|
+
module Kiyohime
|
5
|
+
# A publisher can be used to publish a message to a specific function. It also has the facility to
|
6
|
+
# list the names of the available functions
|
7
|
+
class Publisher
|
8
|
+
attr_reader :store
|
9
|
+
|
10
|
+
# Initialises the publisher by obtaining a Redis connection
|
11
|
+
def initialize(store = nil)
|
12
|
+
@store = store || Kiyohime::Stores::RedisStore.new.redis
|
13
|
+
end
|
14
|
+
|
15
|
+
# A message can be published to a service/function, a message can be a simple type, or at
|
16
|
+
# present a JSON compliant type
|
17
|
+
def publish(service, message)
|
18
|
+
if store
|
19
|
+
puts "Published message: #{message} to service: #{service}" if store.publish(service, message)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'kiyohime/stores/redis_store'
|
3
|
+
require 'kiyohime/publisher'
|
4
|
+
|
5
|
+
module Kiyohime
|
6
|
+
# This helper can be mixed into classes which want to just simply open a Redis connection
|
7
|
+
# and then publish a message
|
8
|
+
module PublisherHelper
|
9
|
+
# Create a redis publisher
|
10
|
+
def publisher
|
11
|
+
@redis_connection ||= Kiyohime::Stores::RedisStore.new.redis
|
12
|
+
Kiyohime::Publisher.new(@redis_connection)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'kiyohime/exceptions/subscriber_error'
|
2
|
+
require 'kiyohime/parsers/channel_parser'
|
3
|
+
|
4
|
+
module Kiyohime
|
5
|
+
# A registry is a listing of services/functions which are accessible via a publish/subscribe paradigm. This
|
6
|
+
# services/functions are accessible through the Publisher
|
7
|
+
class Registry
|
8
|
+
attr_reader :name, :channel_parser, :store, :pubsub
|
9
|
+
|
10
|
+
# A registry should be provided with a name, and an underlying store. If the hiredis flag
|
11
|
+
# is set to true we use the hiredis wrapper around redis instead
|
12
|
+
def initialize(name, store)
|
13
|
+
@name = name
|
14
|
+
@store = store
|
15
|
+
@channel_parser = Kiyohime::Parsers::ChannelParser.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Non blocking registration of services. The run block creates an event loop, this allows
|
19
|
+
# the process to continue and service publications via registered subscribers
|
20
|
+
def register_services(*services)
|
21
|
+
EM.run do
|
22
|
+
initialise_registry
|
23
|
+
subscribe_deregister
|
24
|
+
services.each { |service| register(service) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Non blocking registration of services. The run block creates an event loop, this allows
|
29
|
+
# the process to continue and service publications via registered subscribers
|
30
|
+
def register_containers(*service_containers)
|
31
|
+
EM.run do
|
32
|
+
initialise_registry
|
33
|
+
subscribe_deregister
|
34
|
+
service_containers.each { |service_container| register_container(service_container) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# A service should be able to register it's global interest in receiving all messages to a
|
41
|
+
# particular channel, this means, the object must define a generic handler named handle. Multiple
|
42
|
+
# services are registered in a single go using an evented publish subscribe approach
|
43
|
+
def register(service)
|
44
|
+
channel = channel_parser.parse_type_to_channel_name(service.class)
|
45
|
+
if service.respond_to?(:handle)
|
46
|
+
puts "Registering service: #{channel}"
|
47
|
+
pubsub.subscribe(channel) do |message|
|
48
|
+
begin
|
49
|
+
service.handle(message)
|
50
|
+
rescue => e
|
51
|
+
puts "Kiyohime Error: #{e}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# A service should be able to register it's global interest in receiving all messages to a
|
58
|
+
# channel name derived using the service name and method name. This unlike the generic register
|
59
|
+
# method which assumes the service implements a particular interface
|
60
|
+
def register_container(service_container)
|
61
|
+
service_container.methods.each do |method_name|
|
62
|
+
channel = channel_parser.parse_type_and_method_to_channel_name(service_container.service.class, method_name.to_sym)
|
63
|
+
puts "Registering service: #{channel}"
|
64
|
+
pubsub.subscribe(channel) do |message|
|
65
|
+
if service_container.service.respond_to?(method_name.to_sym)
|
66
|
+
begin
|
67
|
+
service_container.service.send(method_name.to_sym, message)
|
68
|
+
rescue => e
|
69
|
+
puts "Kiyohime Error: #{e}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# When the first series of services are registered via the registry, the deregister subscriber
|
77
|
+
# is also registered, this is used to un-subscribe services
|
78
|
+
def subscribe_deregister
|
79
|
+
dereg_channel = channel_parser.parse_type_and_method_to_channel_name(self.class, :deregister)
|
80
|
+
puts "Registering service: #{dereg_channel}"
|
81
|
+
pubsub.subscribe(dereg_channel) do |message|
|
82
|
+
puts "Unsubscribing from channel #{message}"
|
83
|
+
pubsub.unsubscribe(message)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Initialisation of the registry within the scope of an EventMachine block
|
88
|
+
def initialise_registry
|
89
|
+
@pubsub = store.hiredis
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'em-hiredis'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
module Kiyohime
|
5
|
+
module Stores
|
6
|
+
# A rudimentary abstraction of the store which will advance over time no doubt
|
7
|
+
class RedisStore
|
8
|
+
attr_reader :url
|
9
|
+
|
10
|
+
def initialize(uri = nil)
|
11
|
+
@url = uri || ENV['REDIS_URL'] || ENV['REDISCLOUD_URL'] || 'redis://127.0.0.1:6379/0'
|
12
|
+
end
|
13
|
+
|
14
|
+
def redis
|
15
|
+
Redis.new(url: url)
|
16
|
+
end
|
17
|
+
|
18
|
+
def hiredis
|
19
|
+
EM::Hiredis.connect(url).pubsub
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kiyohime
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.7
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shirren Premaratne
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: em-hiredis
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redis
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '8.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '8.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: factory_girl
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.39'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.39'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.11'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.11'
|
125
|
+
description: Kiyohime is a library which provides a wrapper around Redis for pubsub.
|
126
|
+
email:
|
127
|
+
- shirren@me.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".gitignore"
|
133
|
+
- ".rspec"
|
134
|
+
- ".rubocop.yml"
|
135
|
+
- ".travis.yml"
|
136
|
+
- CODE_OF_CONDUCT.md
|
137
|
+
- Gemfile
|
138
|
+
- LICENSE.txt
|
139
|
+
- README.md
|
140
|
+
- Rakefile
|
141
|
+
- bin/console
|
142
|
+
- bin/setup
|
143
|
+
- examples/services/service_with_bespoke_handler.rb
|
144
|
+
- examples/services/service_with_generic_handler.rb
|
145
|
+
- kiyohime.gemspec
|
146
|
+
- lib/kiyohime.rb
|
147
|
+
- lib/kiyohime/containers/service_registration.rb
|
148
|
+
- lib/kiyohime/exceptions/registry_error.rb
|
149
|
+
- lib/kiyohime/exceptions/subscriber_error.rb
|
150
|
+
- lib/kiyohime/exceptions/unsupported_channel_name.rb
|
151
|
+
- lib/kiyohime/exceptions/unsupported_interface.rb
|
152
|
+
- lib/kiyohime/parsers/channel_parser.rb
|
153
|
+
- lib/kiyohime/publisher.rb
|
154
|
+
- lib/kiyohime/publisher_helper.rb
|
155
|
+
- lib/kiyohime/registry.rb
|
156
|
+
- lib/kiyohime/stores/redis_store.rb
|
157
|
+
- lib/kiyohime/version.rb
|
158
|
+
homepage: http://github.com/shirren/kiyohime
|
159
|
+
licenses:
|
160
|
+
- MIT
|
161
|
+
metadata: {}
|
162
|
+
post_install_message:
|
163
|
+
rdoc_options: []
|
164
|
+
require_paths:
|
165
|
+
- lib
|
166
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
167
|
+
requirements:
|
168
|
+
- - ">="
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: '0'
|
171
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
172
|
+
requirements:
|
173
|
+
- - ">="
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
requirements: []
|
177
|
+
rubyforge_project:
|
178
|
+
rubygems_version: 2.4.8
|
179
|
+
signing_key:
|
180
|
+
specification_version: 4
|
181
|
+
summary: Kiyohime is a library which provides a wrapper around Redis for pubsub.
|
182
|
+
test_files: []
|