Hydra is a JVM based DispatcherComposer Engine.
Basically, you define the tasks to be performed and the input required for each of those tasks. From then on Hydra takes over - It creates the dependency graph, performs tasks in required order and composes the final output.
For each task to be performed, the input can be composed on any of the initial input parameters or output from already completed tasks. Same is the case with final response.
Dispatcher
is the top-level orchestrator. A Hydra Dispatcher
execution comprises of -
- Already realized, initial map of parameters -
Map<String, Object>
- An unordered map of
Task
s to be performed -Map<String, Task>
- A response curator -
Composer
Hydra provides a default implementation of
Dispatcher
(DefaultDispatcher
) which can be initialized with or without aExecutor
DefaultDispatcher
allows for the use of aObject
context (which is used to instantiate aDefaultComposer
on the fly) as the response curator.
A Task
is a combination of a Composer
and a Callable
- The
Composer
is the input curator for theCallable
. - Output from
call()
of thisCallable
is the response of thisTask
.
Hydra provides a default implementation of
Task
(DefaultTask
) which is initialized with -
- A
Callable
class that is instantiated on the fly. This class needs to have a single argument constructor which is used to instantiate it with aComposer
output.- A
Composer
or aObject
context (which is used to instantiate aDefaultComposer
on the fly)
A Composer
composes the given structure (Map
, List
, String
or Number
) based on previously realized values.
Hydra provides a default implementation of
Composer
(DefaultComposer
) which can take a nested collection and can extract out required dependencies andExpression
s that need to be realized.
Expression
s to be parsed are represented by sandwiching aString
between{{
and}}
and are converted toDefaultExpression
.
{{$someVar}}
{{$someMap.someKey}}
An Expression
is a string representation of a data-path.
Hydra provides a default implementation of
Expression
(DefaultExpression
) which represents an expression asString
.
Every variable in the expression starts with a
$
.
An
Expression
can be marked as optional by starting it with#
. An optional expression means thatnull
is a valid output. And an exception faced while realizing this expression will also respond back withnull
.
someConstant
or1.0
or1
are static expressions with no variables$someVar
is a variable expression that returns the value ofsomeVar
$someMap.someKey
is a variable expression that returns value ofsomeKey
fromsomeMap
$someMap.someKey[$someList[0]]
is a complex expression depending on more than 1 variable#someMap.someKey
is an optional expression and will returnnull
ifsomeMap
isnull
or ifsomeMap.someKey
isnull
or non-existent.
Add following to <repositories/>
section of pom.xml -
<repository>
<id>clojars</id>
<name>Clojars repository</name>
<url>https://clojars.org/repo</url>
</repository>
Add following to <dependencies/>
section of your pom.xml -
<dependency>
<groupId>com.flipkart.hydra</groupId>
<artifactId>hydra-dispatcher</artifactId>
<version>1.2</version>
</dependency>
You can now, call Dispatcher
using following code
// Already resolved list of parameters
Map<String, Object> initialParams = ...;
// Map of tasks to be dispatched (mapped to the keys that will store the task response)
Map<String, Task> tasks = ...;
// Final response that needs to be realized
Map<String, Object> response = ...;
// Dispatcher Call
Dispatcher dispatcher = new DefaultDispatcher();
Object output = dispatcher.execute(initialParams, tasks, response);
- We already have an Employee Name.
- Assuming all employees have unique names, we want to fetch -
- Employee ID (provided by EmployeeIdentificationService)
- Employee Joining Date (provided by EmployeeInfoService)
- Now using Employee ID, we want to fetch -
- His department (provided by EmployeeDepartmentService)
- His salary (provided by EmployeeSalaryService)
- This information might be confidential and hence can throw UnauthorizedException
- We don't want to fail in this case
- His city (provided as part of response by EmployeeLocationService)
- His complete address (provided as part of response by EmployeeLocationService)
- Lat/Lng of all components of his address
- Now we want to respond back with all this information
Map<String, Object> initialParams = new HashMap<>();
initialParams.put("employeeName", "John Doe");
Expression expression1 = new DefaultExpression("{{$employeeName}}");
Composer composer1 = new DefaultComposer(expression1, true);
Task employeeIDTask = new DefaultTask(EmployeeIdentificationService.class, composer1);
// Short notation for creating expression on the fly
Composer composer2 = new DefaultComposer("{{$employeeName}}");
Task joiningDateTask = new DefaultTask(EmployeeInfoService.class, composer2);
// Short notation for creating composer on the fly
Task departmentTask = new DefaultTask(EmployeeDepartmentService.class, "{{$employeeID}}");
Task salaryTask = new DefaultTask(EmployeeSalaryService.class, "{{$employeeID}}");
Task locationTask = new DefaultTask(EmployeeLocationService.class, "{{$employeeName}}");
// MultiTask for executing callable once for each value of the provided looping composer
// Composer can use $__key and $__value while iterating
// We also need to provide a ExecutorService to MultiTask
Task latLngTask = new DefaultMultiTask(executor, LatLngService.class, "{{$location}}", "{{$__value}}");
Finally collecting all Task
s in a Map
Map<String, Task> tasks = new HashMap<>();
tasks.put("joiningDate", joiningDateTask);
tasks.put("salary", salaryTask);
tasks.put("department", departmentTask);
tasks.put("employeeID", employeeIDTask);
tasks.put("location", locationTask);
tasks.put("latlng", latLngTask);
Map<String, Object> responseContext = new HashMap<>();
responseContext.put("employeeName", "{{$employeeName}}");
responseContext.put("employeeID", "{{$employeeID}}");
responseContext.put("department", "{{$department}}");
responseContext.put("salary", "{{#$salary}}"); // Optional data - will not fail on null value
responseContext.put("city", "{{$location.city}}"); // Using expressions to extract part of data
responseContext.put("address", "{{$(join, $(values, $location))}}"); // Using provided data access functions
responseContext.put("latlng", "{{$latlng}}");
// This recursively iterates over the responseContext and parses any expression that it finds.
Composer response = new DefaultComposer(responseContext);
private static final ExecutorService executor = Executors.newCachedThreadPool();
// Passing Optional Parameter (ExecutorService) so we can use the same for MultiTask
Dispatcher dispatcher = new DefaultDispatcher(executor);
Object output = dispatcher.execute(initialParams, tasks, response);
Code for this example can be seen here.
- Easy to use interfaces
- Auto creation/resolution of dependencies graph
- Notation to express variables/expressions
The change log can be found here
For bugs, questions and discussions please use the Github Issues. Please follow the contribution guidelines when submitting pull requests.
Copyright 2015 Flipkart Internet, pvt ltd.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.