In our first article, we introduced Thrift and Protobuf and laid out our plan to build cross-language client/server applications. Now, it’s time to dive into a hands-on example with Thrift. In this article, we’ll define a simple service using Thrift’s IDL, implement a server in Java, and write a Python client that can call the server’s methods. This example sets the foundation for more complex scenarios we’ll tackle later in the series.
What We’re Building
To keep things simple, we’ll create a Calculator service that supports basic
arithmetic operations. Our service will have a couple of RPC methods, like add
and subtract
, which return the result of the given operations.
High-Level Steps:
- Write the Thrift IDL file that defines the service interface.
- Generate code for Java and Python using the Thrift compiler.
- Implement the server in Java.
- Implement the client in Python.
- Run the server and test the client calls.
Prerequisites
Before following along, ensure you have:
- Thrift Compiler: Installed and in your system’s
PATH
.
Refer to the Thrift Installation Guide for platform-specific instructions. - Java Development Kit (JDK): A recent JDK (e.g., OpenJDK 11 or later).
- Maven or Gradle (Optional): For building Java projects. This example will assume Maven.
- Python 3 and pip: For running the Python client.
- Thrift Python Library: Can be installed via
pip install thrift
once you have the generated code.
Step 1: Define the Thrift IDL
Create a file called calculator.thrift
(in a directory of your choosing) with
the following content:
namespace java com.example.calculator
namespace py calculator_py
service Calculator {
i32 add(1:i32 num1, 2:i32 num2)
i32 subtract(1:i32 num1, 2:i32 num2)
}
What’s happening here?
- namespace directives: Assign language-specific namespaces. For Java, we’ll
use
com.example.calculator
; for Python,calculator_py
. - service Calculator: Defines a service named
Calculator
. - Methods
add
andsubtract
: Each takes two integers and returns an integer.
Step 2: Generate Code
Use the Thrift compiler to generate Java and Python code from the
calculator.thrift
IDL.
Generate Java Code:
thrift --gen java calculator.thrift
This creates a gen-java
directory with Java classes for the Calculator service.
Generate Python Code:
thrift --gen py calculator.thrift
This creates a gen-py
directory containing Python modules for the service.
After generation, you’ll have:
gen-java/com/example/calculator
containing classes likeCalculator.java
,Calculator$Client.java
, etc.gen-py/calculator_py
containing Python modules likeCalculator.py
and supporting files.
Step 3: Implement the Java Server
We’ll create a simple Java Maven project to host the server. Inside your project directory, you might have a structure like:
.
├── pom.xml
├── calculator.thrift
└── src
└── main
└── java
└── com
└── example
└── server
└── CalculatorHandler.java
CalculatorHandler.java will implement the Calculator.Iface
interface
generated by Thrift.
CalculatorHandler.java:
package com.example.server;
import com.example.calculator.Calculator;
import org.apache.thrift.TException;
public class CalculatorHandler implements Calculator.Iface {
@Override
public int add(int num1, int num2) throws TException {
System.out.println("Received add request: " + num1 + " + " + num2);
return num1 + num2;
}
@Override
public int subtract(int num1, int num2) throws TException {
System.out.println("Received subtract request: " + num1 + " - " + num2);
return num1 - num2;
}
}
Server.java:
package com.example.server;
import com.example.calculator.Calculator;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
public class Server {
public static void main(String[] args) {
try {
CalculatorHandler handler = new CalculatorHandler();
Calculator.Processor<CalculatorHandler> processor = new Calculator.Processor<>(handler);
TServerTransport serverTransport = new TServerSocket(9090);
TServer server = new TSimpleServer(
new TServer.Args(serverTransport).processor(processor)
);
System.out.println("Starting the Thrift server on port 9090...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
}
What’s happening here?
- We create a
CalculatorHandler
that implements the server-side logic. - We create a
TSimpleServer
listening on port 9090. - When the server runs, it listens for requests and uses our handler to respond.
Make sure to include Thrift libraries in your pom.xml
. The Maven dependency
might look like:
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.15.0</version>
</dependency>
Compile and Run the Server:
mvn clean package
java -cp target/my-server.jar com.example.server.Server
You should see:
Starting the Thrift server on port 9090...
Step 4: Implement the Python Client
In Python, we’ll use the generated Python stubs and the Thrift runtime library.
Install the Thrift Python library if you haven’t already:
pip install thrift
client.py:
import sys
sys.path.append('gen-py') # Ensure Python can find the generated code
from calculator_py import Calculator
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
def main():
# Connect to the server
transport = TSocket.TSocket('localhost', 9090)
transport = TTransport.TBufferedTransport(transport)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
client = Calculator.Client(protocol)
transport.open()
# Test add method
result = client.add(10, 5)
print("add(10, 5) = ", result)
# Test subtract method
result = client.subtract(10, 5)
print("subtract(10, 5) = ", result)
transport.close()
if __name__ == '__main__':
main()
What’s happening here?
- We create a Thrift client to connect to
localhost:9090
. - We call the
add
andsubtract
methods on the server. - Results are printed to the console.
Run the Client:
With the server running, in another terminal:
python client.py
You should see:
add(10, 5) = 15
subtract(10, 5) = 5
The server console will show requests being processed.
Verifying Cross-Language Communication
We just demonstrated a fully functional RPC setup:
- A Java server implemented using Thrift-generated code.
- A Python client calling the server’s methods.
Both sides generated their code from the same calculator.thrift
file, ensuring
consistency and type safety. This is exactly the kind of cross-language
interoperability that Thrift enables.
Next Steps
You’ve successfully built a minimal Thrift-based system spanning two languages. This foundation will help you understand how to extend these principles to more complex services and multiple languages.
In the next article, we’ll expand our Thrift usage to include Rust, showing how we can integrate a third language into this ecosystem. Whether we add a Rust client calling the existing Java server or convert the server logic into Rust, we’ll deepen our understanding of interoperability and performance considerations.
Stay tuned for the next article where we introduce Rust into the Thrift environment, further demonstrating how flexible these frameworks can be across different programming languages.