Apache Thrift And its usage in C#
Services are based on the idea of RPC (Remote Procedure Calls). Apache Thrift is just another framework allowing us to write services but it makes it easier to write them by enabling polyglot development.
Apache Thrift enables writing cross language services development.
Why do we need Apache Thrift
One of the benefits of using thrifts is Data Normalization. If the services are implemented in different languages, there is no way a client can send an object which could be picked up by the receiver and she understands it unless it is serialized into a universal format understood by both.
The most famous universal formats for polyglot application development are JSON and Protobuf.
JSON is an excellent choice but there are problems with using JSON specially when we need compile time checking. Most of the time we end-up generating types in C#, Java or language of your choice to convert JSON into the object type. This is an extra work repeated for each project.
Protobuf allows us to write type definitions in a standard format in *.proto files. There are tools available to generate types for your language (Java / C# / Python) based on these definitions. But Protobuf is not a service framework. We need another framework to host services for us like WCF or a REST based framework.
Apache Thrift, on the other hand, provides support of serialization / deserialization in the format of our choice. It also provides a framework for hosting our services.
How Apache Thrift Handles it?
“All problems in software are solved by adding a layer of indirection”. Here the layer of indirection is introducing an Interface Definition Language, commonly referred to as IDL. This is used to define the types which we need to send on the wire. These are just like WCF data contracts but they are defined in a different syntax, plus this would be used to generate codes for different languages. We can then use thrift compiler to compile these types into the language of our choice. The compiler generates the source code for these types with complete serialization / deserialization logic. You can just add this code to your project.
Apache Thrift supports a number of serialization formats. They include:
- Binary: For better speed
- JSON: for readability of serialized objects
- Compact: For reduced size
Apache Thrift Services
Thrift also allows services to be defined in the same IDL formats. They can then be implemented in the languages of your choice. These services can then be hosted by one of the servers provided. The available servers are as follows:
All of them inherit TServer available in Thrift assembly.
In the service oriented scenario, first you would be defining all type used across the boundaries in IDL format. Now you can use Thrift compile to generate code for the languages used by clients and service. You can add the code generated for client in the client project. We also need to add logic to send message to the service.
Now you can implement service in the same IDL format. Again, we need to compile it into the language used for server implementation. Now we can use a server provided by thrift library to host this service. All the stage is set. When we call service methods. The client code serializes the objects using thrift logic, sends it over the wire to the Thrift service. On the service side, thrift deserializes the message and pass it to the server code.
Now let us compile the code into the language of our choice. Thrift compiler can generate code in a number of languages including CSharp (support for .net CLR), Java (support for JVM), Python, Java Script and a number of other languages. Here we are generating code for CSharp. Thrift creates a separate folder for each language. You can notice a folder gen-csharp, which contains the generated client and server code for csharp for the IDL service used.
IDL has a number of available primitive types. They are as follows:
- bool: A boolean value (true or false)
- byte: An 8-bit signed integer
- i16: A 16-bit signed integer
- i32: A 32-bit signed integer
- i64: A 64-bit signed integer
- double: A 64-bit floating point number
- string: A text string encoded using UTF-8 encoding
There is no default DateTime type available in thrift. But we can always keep the value of the DateTime in a standard numeric or string format and parse it on the other end of the service.
Required / Optional Fields
Abstract Data Types (classes) are defined in thrift using struct keyword. They can include primitive, enum and other struct types. All the fields are required (by default). Using ‘optional’ keyword is used to define non-required fields. All non-optional fields are added as constructor parameters of a type, which makes sense as if it is a required field then it is not possible to construct an instance without specifying the field’s value.
Apache Thrift Nuget Package
In order to use Thrift client or server, we first need to download the package required to handle thrift framework. For .net framework, the package is available as a nuget package.
Implementing Thrift Server in C#
The generated code in C# has an interface Iface with the signatures we specified in the service definition IDL file. We just need to implement the interface to provide service definition. But first we need to add the generated code in our C# Server project.
Now we can just provide the implementation of Iface interface to implement described service.
We need to host the service. As we discussed above, Thrift provides us the servers to host the generated services. Here we are using the above implementation of the service hosting it on port 9090. We are using TThreadPoolServer to host our service.
Implementing Thrift Client in C#
A thrift service hosted can be consumed by any client irrespective of the language as long as a generator is available for it. Since we want to consume the service in C# too, we can just add the same generated code to the client project as well. I would recommend to keep it in the same folder and reference the code as a link in both client and server projects.
Now we need to use the service to get the result of our requests. We can provide the following simple code to use the above service.
Now we just need to run the service. As we run the above client, the service provides the following responses, which we are formatting and printing to the console.