King_soa is a project mainly written in Ruby, based on the MIT license.
SOA for your apps - call methods in your cloud
= KingSoa
KingSoa orchestrates a SOA landscape, by knowing where services live and how to call them. It exposes service calls as ruby methods (KingSoa.my_method) which are transfered into http rpc or local method calls. KingSoa lives on the sender and receiver side.
== Why?
While your app is growing you stuff more and more in it, unrelated to its core business intelligence. You'll end up with an monolythic clumsy piece, getting harder to handle every day. Just think of: scalability, testing, deployment, ..
To keep it clean a division of responsibilities, and with it a separation into smaller parts, is needed. But now you've got a communication problem, because each part needs to know about the others. This is where KingSoa enters the stage.
== Install
gem install king_soa
=== Gem Dependencies
== Usage
Take your time and READ the code, its only three files with combined 180 lines of code and lots of documentation. The tests with a high coverage serve as another great source of information.
Simply put: RTFC(Read The Fucking Code) and RTFT
=== The Service Registry
The service registry(KingSoa::Registry) is keeping track of available services and behaves more or less like an array. Be aware that KingSoa::Registry is a singleton, so that the rack middleware and your app are seeing the same.
=== A Service
A service(KingSoa::Service) has a name and two settings(queue or url) defining how it is being called. The actual service class can be located local, remote(http) or somewhere in a worker when beeing queued. The service call is received by a self.perform method of a class named like the CamelCased service.name. It is up to you define service arguments or define and use return values.
You can define services anywhere in your app and add them to the service registry. This should most likely be done in an initalizer(Rails).
a = KingSoa::Service.new( :name => :increment_usage, :url => 'http://localhost:4567', :auth => '12345')
b = KingSoa::Service.new(:name => :crunch_image)
c = KingSoa::Service.new(:name => :delete_user, :queue => :deletions)
KingSoa::Registry << a << b << c
KingSoa.crunch_image(image_path)
KingSoa.increment_usage(12)
KingSoa.delete_user(user_id)
A simple service class, located in the app above
class IncrementUsage self.perform(value) current_user.update_attribute(:usage, value) end end
==== Remote Services
All transport is done over http. An http call can have an authentication key to provide some minimal access restriction. To make it secure you should either use https in public or hide the endpoints somewhere on your farm.
Service endpoints receiving authenticated calls should use the provided rack middleware. As it is doing the authentication, executing the service class and returns values or errors.
KingSoa::Service.new( :name => :increment_usage, :url => 'http://localhost:4567', :auth => '12345')
Service options
The remote side is always called the following url parameters. And its up to you to handle those, if you are NOT using the rack middleware.
http://my_uri?name=increment_usage &auth=1234 &args={12} #json encoded args as string
=== Local Services
A local service calls a class with the CamelCased service name. This class MUST have a self.perform method which receives all of the given arguments. It is up to you to check or validate the existence of arguments and work with return values.
KingSoa::Service.new(:name => :crunch_image)
KingSoa.crunch_image('/path')
CrunchImage.perform('/path')
==== Queued Services
The service is put onto a resque queue and somewhere in your cloud you should have a worker looking for it. The service class should also have the resque @queue attribute set so the job can be rescheduled if it fails. Queued methods can not have return values => fire & forget
KingSoa::Service.new(:name => :delete_user, :queue => :deletions)
:queue => name of the queue as defined by resque. Actually used as prefix in redis
Example of a receiving resque worker class:
class DeleteUser @queue = :deletions self.perform(user_id) User.destroy(user_id) end end
==== Gotchas
== Rack Middleware
The included middleware provides some convinience on the receiving side. It does the authentication and relies on json for incoming requests and outgoing responses. For incomming request the url param[args] is automaticly json decoded.
Response on success HTTP/1.0 200 OK Content-Type: application/json Content-Length: 1234 '{result:{whatever you return .. if you care}}'
Response on error HTTP/1.0 500 Server Error Content-Type: application/json Content-Length: 1234 '{error:'error msg as string'}'
The middleware, by default, grabs all incoming request to my_url/soa. This behaviour can be customized when setting it up:
config.middleware.use KingSoa::Rack::Middleware, :endpoint_path =>'/incoming_soa'
grabs all requests to: my_url/incoming_soa
== Integration
Just think of a buch of small sinatra or specialized rails apps, each having some internal services and consuming services from others.
=== Rails
Add the middleware(application.rb/environment.rb) if you want to receive calls. require 'king_soa' config.middleware.use KingSoa::Rack::Middleware
Setup services for example in an initializer reading a services.yml file. For now there is no convinience loading method and no proposed yml format.
sign_document:
queue: signings
send_sms:
url: 'http://messaging.localhost:3000'
auth: '12345678'
# king_soa_init
service_defs = YAML.load_file(File.join(RAILS_ROOT, 'config', 'services.yml'))
service_defs.each do |k, v|
opts = { :name => k }
[:url, :auth, :queue].each do |opt|
opts[opt] = v[opt.to_s] if v[opt.to_s]
end
KingSoa::Registry << KingSoa::Service.new(opts)
end
=== Sinatra
Take a look at spec/server/app where you can see a minimal sinatra implementation The base is just:
require 'king_soa'
use KingSoa::Rack::Middleware
The service definition should of course also be done, see rails example.
== ToDo
== Note on Patches/Pull Requests
== Copyright
Copyright (c) 2010 Georg Leciejewski. See LICENSE for details.