New features
An overview of the new features in IceRPC.
New protocol with streaming support
IceRPC adds a new protocol named icerpc
that supports streaming. Streaming allows you to send a stream of bytes or a stream of Slice-defined types with your request or response with no or very little overhead.
This new icerpc
protocol runs over multiplexed transports such as QUIC.
Modular API
IceRPC offers a much more modular API than Ice.
For example, with Ice, most of the invocation logic is implemented by Communicator
, and you can customize Communicator
's behavior through configuration.
With IceRPC, the invocation logic consists of multiple objects that you compose to form an invocation pipeline: you include only what you need in this invocation pipeline, and you can easily insert your own custom logic in this pipeline.
Async API
Ice's API is based on the assumption that async APIs are well-suited for RPCs but hard to use and occasionally slower; as a result, Ice offers synchronous/blocking APIs in addition to async APIs, for ease of use and sometimes slightly better performance.
For example, with Ice, the Slice operation sayHello
is mapped to two methods on proxies generated by the Slice compiler:
sayHello
, which blocks the caller until the response is received and decodedsayHelloAsync
, which does not block the caller but returns the response through a task, future or callback depending on the language mapping
When you make an invocation, you can choose to call sayHello
(easy to code but you block the calling thread) or sayHelloAsync
(you don't block the calling thread but it's harder to obtain the response).
IceRPC takes a different, more modern approach: the async/await syntax makes async programming so easy there is no need to provide a redundant synchronous API. The async/await syntax also makes it clear that an RPC is a call that can take a while, throw exceptions, and should not be confused with a local call that completes very quickly.
With IceRPC:
- methods that may take a while because their implementations can wait for I/O are async (e.g. proxy methods)
- methods that never wait for I/O are synchronous (e.g. string-parsing methods)
Optional IDL
With Ice, you can't separate Ice's IDL (Slice) from Ice: the Ice API is generated from local Slice definitions and you always use some Slice even when if you are only sending or receiving bytes.
Conversely, IceRPC sends and receives requests and responses with byte stream payloads, and doesn't know how these byte streams are encoded. This allows you to use IceRPC with Slice, or with another IDL such as Protobuf, or with no IDL at all.
New Slice
IceRPC comes with a brand new Slice, with a new syntax, a new serialization format, and even a new file extension.
This new Slice is independent from IceRPC: you can naturally use it with IceRPC, but you can also use it without an RPC framework. Other RPC frameworks may provide support for this new Slice in the future.
The Ice compilation model for its Slice files is very much like C++: each Slice file needs to #include
the Slice files with the definitions it depends on. You can use a forward declaration to introduce a type without fully defining it. And Slice files use the .ice
file extension.
On the other hand, the compilation model for the new Slice files is more like C# and Java: the compilation uses a set of reference files specified as argument to the compiler, and there is no #include
preprocessing directive or forward declarations. These new Slice files use the .slice
extension.
Another difference is the syntax for parameters and fields: Ice's Slice uses a C-like syntax, while the new Slice syntax is more like Rust and Swift:
enum File { A, B, C, D, E, F, G, H }enum Rank { R1, R2, R3, R4, R5, R6, R7, R8 }
struct Position{ File file; Rank rank;}
exception ChessException {}
interface ChessPiece{ Position currentPosition();
void move(Position newPosition) throws ChessException;}
interface ChessPiece { // It's ok to use a type before defining it. currentPosition() -> Position
move(newPosition: Position) throws ChessException}
compact struct Position { file: File rank: Rank}
exception ChessException {}enum File : uint8 { A, B, C, D, E, F, G, H }enum Rank : uint8 { R1, R2, R3, R4, R5, R6, R7, R8 }