cylons 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +101 -0
- data/Rakefile +1 -0
- data/bin/cylons +38 -0
- data/cylons.gemspec +40 -0
- data/lib/cylons.rb +57 -0
- data/lib/cylons/active_record_extensions.rb +59 -0
- data/lib/cylons/associations.rb +40 -0
- data/lib/cylons/attributes.rb +31 -0
- data/lib/cylons/configuration.rb +38 -0
- data/lib/cylons/connection.rb +44 -0
- data/lib/cylons/errors.rb +11 -0
- data/lib/cylons/interface.rb +8 -0
- data/lib/cylons/local_registry.rb +30 -0
- data/lib/cylons/railtie.rb +38 -0
- data/lib/cylons/registry_adapter.rb +22 -0
- data/lib/cylons/remote.rb +29 -0
- data/lib/cylons/remote_discovery.rb +39 -0
- data/lib/cylons/remote_proxy.rb +99 -0
- data/lib/cylons/remote_registry.rb +49 -0
- data/lib/cylons/remote_schema.rb +12 -0
- data/lib/cylons/rpc.rb +86 -0
- data/lib/cylons/service.rb +12 -0
- data/lib/cylons/service_manager.rb +44 -0
- data/lib/cylons/version.rb +3 -0
- data/spec/lib/cylons/registry_adapter_spec.rb +9 -0
- data/spec/lib/cylons/remote_registry.rb +7 -0
- data/spec/lib/cylons/service_manager_spec.rb +9 -0
- data/spec/spec_helper.rb +12 -0
- metadata +360 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Jason Ayre
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Cylons
|
2
|
+
|
3
|
+
Collectively intelligent remote models which behave very much like models they are claiming to be. (Zero configuration SOA Framework).
|
4
|
+
|
5
|
+
Cylons lets your models in one Rails app, act very much like your ActiveRecord models in another Rails app, providing a base for a SOA infrastructure. I hope to be able to make it applicable to more than just AR models as well, however, I also hope to make it past cool proof of concept stage that works, but isn't exactly suitable for a production system, WHICH IS THE STATE IT IS CURRENTLY IN. You've been warned.
|
6
|
+
|
7
|
+
### Quick Explanation
|
8
|
+
|
9
|
+
|
10
|
+
### Depends heavily on:
|
11
|
+
DCell
|
12
|
+
Zookeeper (for now, only even though )
|
13
|
+
|
14
|
+
### Heavily inspired by
|
15
|
+
This concept - http://www.youtube.com/watch?v=Hu-jvAWTZ9o&feature=autoplay&list=PLF88804CB7380F32C&playnext=1#t=4m59s
|
16
|
+
ActiveRemote - https://github.com/liveh2o/active_remote
|
17
|
+
DCell - https://github.com/celluloid/dcell
|
18
|
+
|
19
|
+
### Alternatives
|
20
|
+
Ill list more later, because I've literally tried at least the 90% majority of the ruby soa "solutions", but if you are looking for an "enterprise" level SOA framework, I'd recommend:
|
21
|
+
|
22
|
+
ActiveRemote - https://github.com/liveh2o/active_remote
|
23
|
+
Protobuf - https://github.com/localshred/protobuf
|
24
|
+
|
25
|
+
Add those two together == winning.
|
26
|
+
|
27
|
+
### Reasons for building
|
28
|
+
Originally it started as a way to communicate between my raspberry pis, because http is so last summer. Soon I found myself wanting a database connection. So I ended up making it more active_record like. Those aren't reasons but brain tired done typing yep.
|
29
|
+
|
30
|
+
|
31
|
+
### Quick start
|
32
|
+
|
33
|
+
|
34
|
+
### How it works?
|
35
|
+
|
36
|
+
### Install ZK, Clone and run the examples
|
37
|
+
|
38
|
+
```
|
39
|
+
git clone https://github.com/jasonayre/cylons_demo
|
40
|
+
|
41
|
+
```
|
42
|
+
|
43
|
+
open 4 new terminal windows/tabs
|
44
|
+
|
45
|
+
*Prepare the inventory management service, inventory*
|
46
|
+
|
47
|
+
```
|
48
|
+
cd inventory
|
49
|
+
SKIP_CYLONS=true bundle exec rake db:create && rake db:migrate && rake db:seed
|
50
|
+
```
|
51
|
+
|
52
|
+
*Prepare the user credentials service, identify*
|
53
|
+
```
|
54
|
+
cd identify
|
55
|
+
SKIP_CYLONS=true bundle exec rake db:create && rake db:migrate && rake db:seed
|
56
|
+
```
|
57
|
+
|
58
|
+
*Prepare the categorization service, taxon*
|
59
|
+
```
|
60
|
+
cd taxon
|
61
|
+
SKIP_CYLONS=true bundle exec rake db:create && rake db:migrate && rake db:seed
|
62
|
+
```
|
63
|
+
*Start Zookeeper, and start each service up with:*
|
64
|
+
```
|
65
|
+
bundle exec cylons start
|
66
|
+
```
|
67
|
+
|
68
|
+
*Load that sample data, note the admin app has no database at all, only remote models*
|
69
|
+
```
|
70
|
+
cd admin
|
71
|
+
bx rake db:seed
|
72
|
+
```
|
73
|
+
|
74
|
+
Note: there will be quite a few N+1 queries happening, intentionally. This is to demonstrate communication between the services, and speed. Each record that is created will make a call to Inventory, to check if that record already exists. If not, it will build the record by doing a Category.first_or_create (Taxon Service), to get the category_id, then it will make 3 additional calls to the Inventory service, 1 for manufacturer, 1 for department, and then finally one to actually create the Product.
|
75
|
+
|
76
|
+
|
77
|
+
## Installation
|
78
|
+
|
79
|
+
Add this line to your application's Gemfile:
|
80
|
+
|
81
|
+
gem 'cylons'
|
82
|
+
|
83
|
+
And then execute:
|
84
|
+
|
85
|
+
$ bundle
|
86
|
+
|
87
|
+
Or install it yourself as:
|
88
|
+
|
89
|
+
$ gem install cylons
|
90
|
+
|
91
|
+
## Usage
|
92
|
+
|
93
|
+
TODO: Write usage instructions here
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
|
97
|
+
1. Fork it
|
98
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
99
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
100
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
101
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/cylons
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'cylons'
|
5
|
+
class Services < ::Thor
|
6
|
+
class_option :app, :default => "./config/environment.rb"
|
7
|
+
|
8
|
+
desc "start", "Start cylon services"
|
9
|
+
def start
|
10
|
+
puts "Starting services"
|
11
|
+
require options[:app]
|
12
|
+
|
13
|
+
raise ::Cylons::CouldNotConnectToRegistry unless ::Cylons::Connection.connected?
|
14
|
+
|
15
|
+
::Dir.glob(::Rails.root.join('app', 'models', "*.rb")).each{ |file| load file }
|
16
|
+
|
17
|
+
::Cylons.logger.info "Cylon Remotes: #{::Cylons::RemoteRegistry.remotes}"
|
18
|
+
|
19
|
+
::Cylons::RemoteRegistry.register_schemas
|
20
|
+
|
21
|
+
::Cylons::ServiceManager.start
|
22
|
+
|
23
|
+
loop do
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
::Services.start
|
30
|
+
|
31
|
+
[:INT, :QUIT, :TERM].each do |signal|
|
32
|
+
trap(signal) do
|
33
|
+
::Cylons.logger.info "Stopping Cylon Services"
|
34
|
+
puts "Stopping Cylon Services"
|
35
|
+
exit 0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
data/cylons.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cylons/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cylons"
|
8
|
+
spec.version = Cylons::VERSION
|
9
|
+
spec.authors = ["Jason Ayre"]
|
10
|
+
spec.email = ["jasonayre@gmail.com"]
|
11
|
+
spec.description = %q{Collective intelligence meets service oriented architecture. Allows your remote models to act like your local models, while defining simple conventions to build self aware and reusable services. Currently reliant upon Zookeeper.}
|
12
|
+
spec.summary = %q{Collectively intelligent (and simple), Service Oriented Architecture framework for Rails}
|
13
|
+
spec.homepage = "https://github.com/jasonayre/cylons"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "active_attr"
|
22
|
+
spec.add_dependency "active_attr"
|
23
|
+
spec.add_dependency "dcell", "0.15.0"
|
24
|
+
spec.add_dependency "thor"
|
25
|
+
spec.add_dependency "zk"
|
26
|
+
spec.add_dependency 'pry'
|
27
|
+
spec.add_dependency "will_paginate"
|
28
|
+
spec.add_dependency "ransack"
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
31
|
+
spec.add_development_dependency "rake"
|
32
|
+
spec.add_development_dependency "pry"
|
33
|
+
spec.add_development_dependency "activerecord"
|
34
|
+
# spec.add_development_dependency "sqlite3"
|
35
|
+
spec.add_development_dependency "rake"
|
36
|
+
spec.add_development_dependency "rspec"
|
37
|
+
spec.add_development_dependency "rspec-pride"
|
38
|
+
spec.add_development_dependency "pry-nav"
|
39
|
+
spec.add_development_dependency "simplecov"
|
40
|
+
end
|
data/lib/cylons.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'active_attr'
|
2
|
+
require 'active_model'
|
3
|
+
require 'active_support/core_ext/array'
|
4
|
+
require 'active_support/core_ext/hash'
|
5
|
+
require 'active_support/inflector'
|
6
|
+
require 'active_support/json'
|
7
|
+
require 'will_paginate'
|
8
|
+
require "cylons/version"
|
9
|
+
|
10
|
+
require 'cylons/attributes'
|
11
|
+
require 'cylons/connection'
|
12
|
+
require 'cylons/configuration'
|
13
|
+
require 'cylons/errors'
|
14
|
+
require 'cylons/remote'
|
15
|
+
require 'cylons/remote_registry'
|
16
|
+
require 'cylons/remote_discovery'
|
17
|
+
require 'cylons/remote_proxy'
|
18
|
+
require 'cylons/registry_adapter'
|
19
|
+
require 'cylons/service'
|
20
|
+
require 'cylons/service_manager'
|
21
|
+
require 'dcell'
|
22
|
+
require 'socket'
|
23
|
+
require 'zk'
|
24
|
+
require 'dcell/registries/zk_adapter'
|
25
|
+
require 'cylons/railtie'
|
26
|
+
require 'pry'
|
27
|
+
# require 'cylons/railtie' if defined?(Rails)
|
28
|
+
|
29
|
+
module Cylons
|
30
|
+
#dont load remotes if test env or SKIP_CYLONS=true (such as when running rake tasks)
|
31
|
+
def self.skip_cylons?
|
32
|
+
!!ENV["SKIP_CYLONS"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.silence?
|
36
|
+
skip_cylons? || (defined?(Rails) && Rails.env == "test")
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
attr_accessor :configuration, :logger
|
41
|
+
|
42
|
+
def configuration
|
43
|
+
@configuration ||= ::Cylons::Configuration.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def configure
|
47
|
+
yield(configuration) if block_given?
|
48
|
+
|
49
|
+
@logger = configuration.logger
|
50
|
+
|
51
|
+
::ActiveSupport.run_load_hooks(:cylons, self)
|
52
|
+
end
|
53
|
+
|
54
|
+
alias_method :config, :configuration
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'cylons/remote_discovery'
|
2
|
+
|
3
|
+
module Cylons
|
4
|
+
module ActiveRecordExtensions
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
SEARCH_OPTION_KEYS = [:opts, :options].freeze
|
8
|
+
|
9
|
+
def reload_remotes!
|
10
|
+
::Cylons::RemoteDiscovery.load_remotes unless ::Cylons.silence?
|
11
|
+
end
|
12
|
+
|
13
|
+
def remote_schema
|
14
|
+
::Cylons::RemoteRegistry.get_remote_schema(self.name) unless ::Cylons.silence?
|
15
|
+
end
|
16
|
+
|
17
|
+
def remote_belongs_to(*args)
|
18
|
+
options = args.extract_options!
|
19
|
+
|
20
|
+
args.each do |arg|
|
21
|
+
options[:foreign_key] = "#{arg}_id"
|
22
|
+
association_hash = {:name => arg, :association_type => :belongs_to, :options => options}
|
23
|
+
self.remote_associations << association_hash
|
24
|
+
build_remote_belongs_to_association(association_hash)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
#store remote has many assoc globally, then define it locally.
|
29
|
+
def remote_has_many(*args)
|
30
|
+
options = args.extract_options!
|
31
|
+
|
32
|
+
args.each do |arg|
|
33
|
+
association_hash = {:name => arg, :association_type => :has_many, :options => options}
|
34
|
+
self.remote_associations << association_hash
|
35
|
+
build_remote_has_many_association(association_hash)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#TODO: Hacky, but something strange is going on here, and syntax will need to chagne for rails4... hrmmm
|
40
|
+
def scope_by(params = {})
|
41
|
+
search_options = params.extract!(*SEARCH_OPTION_KEYS)
|
42
|
+
|
43
|
+
search_options.delete_if {|k,v| v.nil? }
|
44
|
+
|
45
|
+
if search_options.present?
|
46
|
+
scoped_search = params.inject(scoped) do |combined_scope, param|
|
47
|
+
combined_scope.send("by_#{param.first}", param.last)
|
48
|
+
end.paginate(:page => search_options[:options][:page], :per_page => search_options[:options][:per_page])
|
49
|
+
else
|
50
|
+
scoped_search = params.inject(scoped) do |combined_scope, param|
|
51
|
+
combined_scope.send("by_#{param.first}", param.last)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
scoped_search
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Cylons
|
2
|
+
module Associations
|
3
|
+
module ClassMethods
|
4
|
+
#To make service load order a non issue, we need to be trixy hobbitses
|
5
|
+
#Here we catch uninitialized constant error and reload remotes if constant hasnt been defined yet
|
6
|
+
#i.e. if a Product service loads after category service, when Product calls the Category.search method
|
7
|
+
#it will throw constant error, we catch that, reload remotes and try one mo time
|
8
|
+
|
9
|
+
def build_remote_belongs_to_association(association_hash)
|
10
|
+
define_method(association_hash[:name]) do
|
11
|
+
begin
|
12
|
+
foreign_key = association_hash[:options].fetch(:foreign_key, association_hash[:name].to_s.underscore.to_sym)
|
13
|
+
association_hash[:name].to_s.classify.constantize.scope_by(:id => self[foreign_key]).first
|
14
|
+
rescue => e
|
15
|
+
if e.message == "uninitialized constant #{association_hash[:name].classify.to_s}"
|
16
|
+
::Cylons::RemoteDiscovery.load_remotes
|
17
|
+
association_hash[:name].to_s.classify.constantize.scope_by(:id => self["#{association_hash[:name].to_s}_id".to_sym]).first
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_remote_has_many_association(association_hash)
|
24
|
+
define_method(association_hash[:name]) do
|
25
|
+
begin
|
26
|
+
klass_name = association_hash[:options].fetch(:class_name, association_hash[:name].to_s.singularize.classify)
|
27
|
+
foreign_key = association_hash[:options].fetch(:foreign_key, "#{self.class.name.singularize}_id".downcase.to_sym)
|
28
|
+
klass = klass_name.constantize
|
29
|
+
klass.scope_by(foreign_key => self[:id])
|
30
|
+
rescue => e
|
31
|
+
if e.message == "uninitialized constant #{klass_name}"
|
32
|
+
::Cylons::RemoteDiscovery.load_remotes
|
33
|
+
klass.scope_by(:id => self["#{association_hash[:name].to_s.singularize}_id"].to_sym)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Cylons
|
2
|
+
module Attributes
|
3
|
+
def read_attribute(name)
|
4
|
+
name = name.to_s
|
5
|
+
|
6
|
+
if @attributes.has_key?(name) || self.respond_to?(name)
|
7
|
+
@attributes[name]
|
8
|
+
else
|
9
|
+
raise ::ActiveAttr::UnknownAttributeError, "unknown attribute: #{name}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
alias_method :[], :read_attribute
|
13
|
+
|
14
|
+
# Override #write_attribute (along with #[]=) so we can provide support for
|
15
|
+
# ActiveModel::Dirty.
|
16
|
+
#
|
17
|
+
def write_attribute(name, value)
|
18
|
+
__send__("#{name}_will_change!") if value != self[name]
|
19
|
+
|
20
|
+
name = name.to_s
|
21
|
+
|
22
|
+
if @attributes.has_key?(name) || self.respond_to?(name)
|
23
|
+
@attributes[name] = value
|
24
|
+
else
|
25
|
+
raise ::ActiveAttr::UnknownAttributeError, "unknown attribute: #{name}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
alias_method :[]=, :write_attribute
|
29
|
+
alias_method :attribute=, :write_attribute
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'cylons'
|
2
|
+
require 'cylons/registry_adapter'
|
3
|
+
module Cylons
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :address,
|
6
|
+
:logger,
|
7
|
+
:port,
|
8
|
+
:registry_address,
|
9
|
+
:registry_port,
|
10
|
+
:registry_adapter,
|
11
|
+
:registry,
|
12
|
+
:remote_namespace
|
13
|
+
|
14
|
+
#NOTE: I am explicitly setting address to machine address instead of local host due to zookeeper or possibly dcell issue.
|
15
|
+
# Basically I have been unable to connect the machine to registry via
|
16
|
+
#:addr => "tcp://localhost:9001" or whatever, have needed to connect using
|
17
|
+
#:addr => "tcp://192.168.0.101:9001", not sure if its an id10t error, or a local setting, or zookeeper or dcell
|
18
|
+
# lightbulb: maybe because its trying to connect to remote registry, and its telling the remote machine to use localhost?
|
19
|
+
# bet thats probably it.. Although I dont think I had same prob w redis. IDONTREMEMBER.
|
20
|
+
|
21
|
+
#TODO look into that further.
|
22
|
+
def initialize
|
23
|
+
self.address = ::Cylons::Interface.primary
|
24
|
+
self.port = (9000 + rand(100))
|
25
|
+
self.logger = ::Rails.logger if defined?(Rails)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
#TODO: uhh refactor this, was having trouble w circular dependency and too tired to trace it down!
|
30
|
+
#This introduces order dependency when setting initializer=badmk
|
31
|
+
|
32
|
+
def registry_adapter=(adapter)
|
33
|
+
raise ::Cylons::InvalidRegistryAdapter unless ::Cylons::RegistryAdapter::VALID_REGISTRY_ADAPTERS.include?(adapter.to_sym)
|
34
|
+
@registry_adapter = adapter.to_sym
|
35
|
+
@registry = ::Cylons::RegistryAdapter.send(registry_adapter)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|