Tipos de Datos en Thrift
Apache Thrift permite definir interfaces de servicios y estructuras de datos de manera independiente al lenguaje. Los tipos de datos definidos en el archivo .thrift se mapean a sus equivalentes nativos en Java:
- Primitivos:
bool(boolean),byte(byte),i16(short),i32(int),i64(long),double(double),string(String codificado en UTF-8). - Especiales:
binary(ByteBuffer) utilizado para secuencias de bytes sin procesar. - Estructuras (Structs): Equivalen a objetos POJO en Java, agrupando múltiples campos.
- Contenedores:
list<T>: Colección ordenada que permite elementos duplicados.set<T>: Colección desordenada de elementos únicos.map<K,V>: Diccionario de pares clave-valor.
- Declaraciones: La palabra clave
namespace javadefine el paquete de destino, mientras queservicedeclara la interfaz remota con sus métodos.
Definición del Archivo de Interfaz
A conitnuación, se presenta un ejemplo de definición para un servicio de gestión de billeteras de criptomonedas. Este archivo debe guardarse con la extensión .thrift (por ejemplo, CryptoWallet.thrift):
namespace java com.ejemplo.wallet.rpc
struct WalletCreationRequest {
1: required string keystorePath,
2: required string passphrase,
3: optional string accountAlias
}
struct WalletCreationResponse {
1: string publicAddress,
2: string privateKey,
3: string publicKey,
4: double initialBalance,
5: i32 statusCode,
6: string statusMessage
}
struct TransferRequest {
1: string nodeUrl,
2: string keystorePath,
3: string senderAddress,
4: string passphrase,
5: string recipientAddress,
6: double amount,
7: optional string smartContractAddress
}
struct TransferResponse {
1: i32 statusCode,
2: string transactionHash,
3: string statusMessage
}
service CryptoWalletService {
WalletCreationResponse createWallet(1: WalletCreationRequest request)
TransferResponse transferFunds(1: TransferRequest request)
double checkBalance(1: string nodeUrl, 2: string publicAddress)
}
Generación del Código Java
Una vez descargado el compilador de Thrift, se debe ejecutar el siguiente comando en la terminal para generar las clases base. El archivo .thrift debe estar en el mismo directorio o especificar su ruta:
thrift -gen java CryptoWallet.thrift
Este proceso creará un directorio gen-java que contiene las clases, interfaces y constructores necesarios para el servidor y el cliente.
Configuración del Proyecto con Maven
Para integrar Thrift en un proyecto Java, es necesario incluir la dependencia principal en el archivo pom.xml:
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.16.0</version>
</dependency>
Implementación del Servidor
El servidor es responsable de exponer los métodos definidos en la interfaz. Se utiliza un modelo de servidor simple bloqueante para este ejemplo.
package com.ejemplo.wallet.server;
import com.ejemplo.wallet.rpc.CryptoWalletService;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
public class WalletRpcServer {
public static void main(String[] args) {
try {
int port = 9090;
TServerSocket serverTransport = new TServerSocket(port);
// Asociar el procesador con la implementación de la lógica de negocio
CryptoWalletService.Processor<CryptoWalletService.Iface> processor =
new CryptoWalletService.Processor<>(new WalletServiceHandler());
TServer.Args serverArgs = new TServer.Args(serverTransport)
.processor(processor)
.protocolFactory(new TBinaryProtocol.Factory());
TServer server = new TSimpleServer(serverArgs);
System.out.println("Iniciando servidor RPC en el puerto " + port);
server.serve();
} catch (TTransportException e) {
System.err.println("Error de transporte al iniciar el servidor: " + e.getMessage());
}
}
}
La lógica de negocio se implementa en una clase separada que implementa la interfaz generada por Thrift:
package com.ejemplo.wallet.server;
import com.ejemplo.wallet.rpc.*;
import org.apache.thrift.TException;
public class WalletServiceHandler implements CryptoWalletService.Iface {
@Override
public WalletCreationResponse createWallet(WalletCreationRequest request) throws TException {
WalletCreationResponse response = new WalletCreationResponse();
// Simulación de lógica de creación
response.setPublicAddress("0xABCDEF1234567890");
response.setPrivateKey("generated_private_key");
response.setPublicKey("generated_public_key");
response.setInitialBalance(0.0);
response.setStatusCode(200);
response.setStatusMessage("Billetera creada en: " + request.getKeystorePath());
return response;
}
@Override
public TransferResponse transferFunds(TransferRequest request) throws TException {
TransferResponse response = new TransferResponse();
// Simulación de lógica de transferencia
response.setStatusCode(200);
response.setTransactionHash("0xTXHASH987654321");
response.setStatusMessage("Transferencia de " + request.getAmount() + " tokens completada.");
return response;
}
@Override
public double checkBalance(String nodeUrl, String publicAddress) throws TException {
// Simulación de consulta a un nodo
return 2.75;
}
}
Implementación del Cliente
El cliente establece la conexión con el servidor utilizando sockets. Se recomienda emplear un bloque try-with-resources para asegurar que el transporte de red se cierre adecuadamente, incluso si ocurren excepciones durante la llamada remota.
package com.ejemplo.wallet.client;
import com.ejemplo.wallet.rpc.*;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
public class WalletRpcClient {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 9090;
public void executeOperations() {
// El transporte se cierra automáticamente al finalizar el bloque
try (TTransport transport = new TSocket(SERVER_HOST, SERVER_PORT)) {
transport.open();
TProtocol protocol = new TBinaryProtocol(transport);
CryptoWalletService.Client client = new CryptoWalletService.Client(protocol);
// 1. Solicitar creación de billetera
WalletCreationRequest createReq = new WalletCreationRequest();
createReq.setKeystorePath("/secure/data/keystore");
createReq.setPassphrase("strong_password_123");
createReq.setAccountAlias("PrimaryWallet");
WalletCreationResponse createRes = client.createWallet(createReq);
System.out.println("Resultado Creación: " + createRes.getStatusMessage());
// 2. Ejecutar transferencia de fondos
TransferRequest transferReq = new TransferRequest();
transferReq.setNodeUrl("https://mainnet.infura.io/v3/YOUR-PROJECT-ID");
transferReq.setKeystorePath("/secure/data/keystore");
transferReq.setSenderAddress(createRes.getPublicAddress());
transferReq.setPassphrase("strong_password_123");
transferReq.setRecipientAddress("0xRECIPIENT_ADDRESS");
transferReq.setAmount(1.25);
TransferResponse transferRes = client.transferFunds(transferReq);
System.out.println("Resultado Transferencia: " + transferRes.getTransactionHash());
// 3. Consultar saldo actual
double currentBalance = client.checkBalance("https://mainnet.infura.io", createRes.getPublicAddress());
System.out.println("Saldo Actual: " + currentBalance);
} catch (TException e) {
System.err.println("Error durante la comunicación RPC: " + e.getMessage());
}
}
public static void main(String[] args) {
WalletRpcClient rpcClient = new WalletRpcClient();
rpcClient.executeOperations();
}
}