Manage and setup Thrift services through configuration
This little jar will allow you configure Thrift services using an xml or text driven configuration, there are two main advantages on this approach:
- Determine which services start on your application through configuration, no need to modify code
- Change the transport, ports, service implementation... all from the configuration
There are two pieces of configuration, services setup and server configuration. Both of them are specified at construction time.
The server configuration provides both Server and Transport. thrift-service-manager requires of a factory for those in order to operate, this need to be provided a construction time. The jar gives two different servers out of the box but you can create in 5 minutes all the others (see below Using Other Servers).
- ServiceThreadPoolWrapper. This implementation is based on a TThreadedSelectorServer server over a TNonblockingServerSocket transport layer
- SecuredThreadPoolWrapper. This implementation uses a public/private key pair in order to secure the server/client communication. It is based on a TThreadPoolServer server over a TSSLTransport transport layer.
Using any other combination of Server/Transport it's very easy. Implement AbstractRunnableServiceWrapper, including the factory inner class, and you are good to go. There is only one mandatory method: getServer, which should return the server you want to use. The Factory class should returns a new instance of it each time getServiceWrapper is invoked.
Just copy ServiceThreadPoolWrapper as an starting point and go from there. If you do so, please contribute by providing the code as a Pull Request.
The are two ways to initialize services with thrift-service-manager: through XML configuration and csv lists. On both cases you need to provide the configuration at construction time.
Create an serviceConfiguration.xml file, such as follow
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<thirftServiceConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="thriftServiceDefinition.xsd">
<service name="MathService" port="10904">
<serviceDefinition interface="org.sergilos.servicemanager.remote.test.MathTestServiceAddition" implementation="org.sergilos.servicemanager.remote.test.MathServiceAdditionImpl" />
<serviceDefinition interface="org.sergilos.servicemanager.remote.test.MathTestServiceSubtraction" implementation="org.sergilos.servicemanager.remote.test.MathServiceSubtractionImpl" />
</service>
<service name="ThreadService" port="10905">
<serviceDefinition interface="org.sergilos.servicemanager.remote.test.ThreadTestService" implementation="org.sergilos.servicemanager.remote.test.ThreadServiceImpl" />
</service>
</thirftServiceConfiguration>
The above configuration defines three services, two of them (Addition and Subtraction) on the port 10904 and the third on 10905
To start the service just specify the configuration file at construction time:
ServiceServerManager serviceManager = new ServiceServerManager("/whatever/your/path/is/testServiceConfiguration.xml", new ServiceThreadPoolWrapper.ServiceThreadPoolWrapperFactory());
serviceManager.startupServer();
You need to provide 4 csv strings:
- Service Names: The name of the services
- Service Interfaces: Thrift interfaces (autogenerated code)
- Services Implementations: The implementations for the interfaces
- Ports: The ports used. Notice that you can group several services in one single port (you can setup all the services in the same port if you want). This is because we are using TMultiplexedProtocol (a protocol decorator).
Example:
String serviceNames = "MathService,MathService,ThreadService";
String serviceInterfaces = "org.sergilos.servicemanager.remote.test.MathTestServiceAddition,org.sergilos.servicemanager.remote.test.MathTestServiceSubtraction,org.sergilos.servicemanager.remote.test.ThreadTestService";
String serviceImplementations = "org.sergilos.servicemanager.remote.test.MathServiceAdditionImpl,org.sergilos.servicemanager.remote.test.MathServiceSubtractionImpl,org.sergilos.servicemanager.remote.test.ThreadServiceImpl";
String servicePorts = "10902,10902,10903";
ServiceServerManager serviceManager = new ServiceServerManager(serviceNames, serviceInterfaces, serviceImplementations, servicePorts, true, new ServiceThreadPoolWrapper.ServiceThreadPoolWrapperFactory());
If you are using Spring in your project you can take advantage of the dependency injection capabilities that come with it. Simply use the @Autowired annotation in your services implementation and setup the ServiceServerManager in the application context.
Spring applicationContext.xml example:
<beans .... >
<!-- [...] -->
<!-- Examples of the two available Wrapper factories. Use any of them or create your own -->
<bean id="securedServiceWrapperFactory"
class="org.sergilos.servicemanager.wrappers.SecuredThreadPoolWrapper$SecuredThreadPoolWrapperFactory"
factory-method="getServerInstance">
<constructor-arg name="keystoreFile" value="/path/to/keystore.jks"/>
<constructor-arg name="keystorePass" value="yourKeystorePassword"/>
</bean>
<bean id="serviceThreadPoolWrapperFactory"
class="org.sergilos.servicemanager.wrappers.ServiceThreadPoolWrapper$ServiceThreadPoolWrapperFactory">
<constructor-arg name="numSelectorThreads" value="5"/>
<constructor-arg name="numWorkerThreads" value="2"/>
</bean>
<!-- ServiceServerManager requires the path to your config file and the WrapperFactory -->
<bean id="serviceManager" class="org.sergilos.servicemanager.ServiceServerManager" destroy-method="stopServices"
init-method="startupServer">
<constructor-arg name="xmlConfigurationLocation" value="/path/to/xml/config"/>
<constructor-arg name="serviceWrapperFactory" ref="serviceThreadPoolWrapperFactory"/>
</bean>
<!-- [...] -->
</beans>
Example code to start using your services once they are up:
ServiceThreadPoolWrapper.ServiceThreadPoolWrapperFactory serviceWrapper = new ServiceThreadPoolWrapper.ServiceThreadPoolWrapperFactory();
// Host and port is the location of your host
TProtocol serviceProtocol = serviceWrapper.getClientProtocol(MathTestServiceAddition.class.getName(), "localhost", 10500);
MathTestServiceAddition.Client mathAdditionClient = new MathTestServiceAddition.Client(serviceProtocol); // This is your client ready to use
Notice that serviceInterfaceClassName refers to the full class path of the interface you are trying to get. For example, on the example above (see XML Configuration) it would be org.sergilos.servicemanager.remote.test.MathTestServiceAddition
- Can I use thrift-service-manager without Spring? In theory it would work without Spring, however it does have some Spring dependencies so you will get ClassNotFoundException all over the place. Remove those dependencies is very straight forward (be my guest), making them optional is not that easy and that is why I have not done it yet.