
Tom Smykowski - I have people skills!
Tom Smykowski, Tom for short, is a gem that takes the specifications from the customers and brings them down to the engineers. He has people skills. He is good at dealing with people. If this does not make sense to you, please refer to this introductory video and, of course, the movie.
To go into technical detail: Tom uses Goliath to dispatch HTTP requests to multiple other APIs (via Adapters) in parallel. In a next step, a Merger merges the result and responds to the clients request.
All you have to do is define some Adapters that get activated for certain routes and some Mergers for certain routes.
As you have seen in the video above, Tom Smykowski talks extra much when consultants are present who might fire him. So this gem will log to STDOUT when you're in development mode or the BOBS environment variable is set. For example, you could
BOBS=present ruby yourapp.rb
Useful for debugging, but nothing you really want in production/testing.
Also, Tom is yard documented. Just run yard --server and direct your
browser to http://localhost:8808.
TL;DR
The general flow goes like this:
TIME Request
| |
| Dispatcher
| .__________|___________.
| | | |
| Adapter1 Adapter3 AdapterN (in parallel)
| |________. | ._________|
| | | |
| Merger
| |
V Response
So per request there can be many Adapters that talk to different APIs, and one Merger that combines the responses of all APIs to one response.
How to use
Tom::Dispatcher
This class basically does what is pictured in the flow above:
- Take the request
- Find all
Adapters that registered for a matching route - Dispatch the requests to them
- Collect results
- Merge results into one and respond
To add APIs or change the behavior of Tom, you don't have to touch this class, though. Adapter and Merger is what you're looking for.
Adapters are optional, so it's completely valid for a route to only have a merger. If, however, there is no merger registered for a given route, a 404 will be emitted.
Tom::Adapter
The Adapter class comes with the class methods:
host=(set the api to talk to)rewrite_request(overwrite if you don't want to pass on URIs 1:1)forward_request(takes env, calls therewrite_requestmethod and returns the result)
To hook Tom up to an API, you would first create an Adapter that inherits from the Tom::Adapter class, register some routes (they become regular expressions) and finally implement the logic by overwriting the handle method:
class Sheldon < Tom::Adapter
register_route "/nodes/*"
def self.handle(env)
# Insert biz logic here
forward_request(env)
end
end
The handle method takes a rack env, does some stuff like removing things that are not needed in Sheldon and then forwards the request on to the API.
In your initializer you can configure Adapters like this:
Sheldon.host = 'http://localhost:9292'
This causes the aforementioned rewrite_request method to use that host name. The forward_request method uses an instance variable called @request to know what to do. rewrite_request, for example, takes a rack env and initializes @request with :host, :method and :uri.
If the method is POST or PUT, then you should also set the appropriate @request[:body] if you want to use the automatic forward_request method.
Tom::Merger
Mergers work very similar to Adapters. But all you have to do here is subclass it and implement the merge method. For example we could create a Merger that always ignores everything and just returns the response from Sheldon:
class OnlySheldon < Tom::Merger
register_route "^/nodes/[0-9]+$"
def self.merge(env, responses)
responses[Sheldon]
end
end
You can use the rack env to decide what to do, and you get a hash of responses that has the Adapter class as keys and their respective rack responses`:
{ MyAdapter => [[200,{},"first response" ],
[200,{},"second response"]],
MyOtherAdapter => [
On registering routes
If you call
class Foo < Tom::Adapter
register_route "^/nodes/[0-9]+$"
the adapter will be registered for all methods (namely head, get, put, post, delete). If you just want to register for some methods, you can do that with
register_route "^/nodes/[0-9]_$", :get, :put
Same goes for mergers.
Todo
- document special headers we inject (e.g. which adapters were used, etc)
- handle adapter errors/states in mergers
- use Goliath::Rack::Heartbeat
- think about consensus protocols
- ...