cylons 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in skynext.gemspec
4
+ gemspec
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