Using Ice and IceRPC Together
IceRPC is our new cutting-edge RPC framework, with a modular design, support for QUIC, an improved Slice language, and more.
While Ice is available in many languages, IceRPC language support is much more limited.
As an Ice user, there are broadly 3 ways you can adopt IceRPC:
You port your existing Ice application to IceRPC, and then don’t look back.
With this scenario, you can take full advantage of the new features provided by IceRPC. Your clients and servers can use thequic
transport, or thetcp
transport through theslic
layer. And you can use new Slice features such as structs with optional fields, enums with fields, and custom types.
Such a port requires a significant effort since the IceRPC API is very different from the Ice API: while the concepts remain similar, you’ll need to rework your entire communication layer.You port only a portion of your existing Ice application to IceRPC: some components (clients, servers) use Ice while others use IceRPC.
You extend an existing Ice application with new components (clients, servers) implemented using IceRPC. The existing components remain Ice-based.
This page focuses on #2 and #3, where the Ice and IceRPC clients and servers communicate with each other using the Ice protocol and rely on the original Slice language understood by Ice.
Terminology
We tried to keep the IceRPC terminology in sync with the Ice terminology, but occasionally we found a better term or the Ice and IceRPC concepts aren’t an exact match.
Ice | IceRPC equivalent |
---|---|
Communicator | Connection cache (roughly) |
Proxy | Proxy in the IceRPC+Slice add-on |
Stringified proxy | Service address, service address URI |
Endpoint | Server address, server address URI |
Connection | Protocol connection (for advanced users) |
Transport | Transport |
Object adapter | Server and Router (roughly) |
Ice object | Service |
Servant | Service |
Dispatcher | Dispatcher |
Middleware | Middleware |
Locator | (none) |
Router | (none) |
Properties | (none) |
Protocol and Transport
IceRPC implements two RPC protocols: ice
and icerpc
, while Ice implements a single RPC protocol, the Ice Protocol. ice
is identical to the Ice Protocol, so in your IceRPC components, make sure to use the ice
protocol.
Ice and IceRPC have two transports in common: tcp
and ssl
. You can’t use udp
or bt
(since they are not supported by IceRPC), and you can’t use quic
(since it’s not supported by Ice).
Missing Protocol Features
IceRPC’s implementation of the Ice Protocol (ice
) is fairly complete, except it does not include two features:
batched requests
protocol compression
An Ice client can’t send batched requests to an IceRPC server (they won’t be received and processed). Likewise, an Ice client can’t send compressed requests to an IceRPC server.
Slice Migration
IceRPC includes support for an improved - but also fairly different - revision of the Slice language.
New Slice Syntax
The syntax of IceRPC’s Slice (New Slice) is quite different from the Ice’s Slice syntax (Original Slice):
// Original Slice syntax
interface Greeter
{
string greet(string name);
}
// New Slice syntax
interface Greeter {
greet(name: string) -> string
}
Other than the formatting, you’ll notice that the New Slice syntax uses a Rust-like syntax for parameters and return values. Original Slice and New Slice have also different file structures:
Original Slice definitions are stored in
.ice
files and are processed as translation units with#include
and other C/C++ preprocessing directives. The parsing is similar to C++, in particular, you can only use a type you’ve seen previously in your translation unit.New Slice definitions are stored in
.slice
files and are processed in groups of source files and reference files. The parsing is similar to C# and Java, with no textual inclusion.
The slicec
compiler provided with IceRPC only understands the New Slice syntax and file structure, while the slice2xxx
compilers provided by Ice only understand the Original Slice syntax and its C++ like file structure.
Converting Slice Definitions with ice2slice
The ice2slice
tool helps you convert Original Slice definitions in .ice
files into New Slice definitions in .slice
files. ice2slice
is an Original Slice compiler that produces New Slice definitions.
For example:
ice2slice Greeter.ice
produces a file named Greeter.slice
in the same directory.
Once you’ve converted your Slice definitions into .slice
files, you should review these definitions and make the following adjustments:
Remove ?
for parameter and field types, where appropriate
With Original Slice, a parameter or field with a proxy type or a class type is always nullable. Original Slice doesn’t provide a way to specify “this parameter is never null”. As a result, when you expect only non-null values, you need to write extra checking code to verify the argument or field you’ve received is not null. With New Slice, you can express “this parameter is never null” and the Slice library implements the check for you.
For example, let’s take this Original Slice interface:
// Original Slice
module ClearSky
{
class AtmosphericConditions
{
....
}
interface WeatherStation
{
/// Reports a new reading to the weather station.
void report(string sensorId, AtmosphericConditions reading);
}
}
We want the reading
parameter to be always non-null: it doesn’t make sense to report a “null” reading. But this constraint can’t be expressed in Original Slice – a class parameter is always nullable.
ice2slice
converts this definition into:
// New Slice generated by ice2slice
mode = Slice1
module ClearSky
class AtmosphericConditions {
...
}
interface WeatherStation {
/// Reports a new reading to the weather station.
report(sensorId: string, reading: AtmosphericConditions?)
}
And during the review process, we can make reading
non-optional by removing the ?
:
// New Slice after review
mode = Slice1
module ClearSky
interface WeatherStation {
/// Reports a new reading to the weather station.
report(sensorId: string, reading: AtmosphericConditions)
}
Remove unused proxy types
In Original Slice, whenever you define an interface Name
, you implicitly define a proxy type, Name*
. In practice, you rarely use the proxy types for all your interfaces. And indeed, many applications never transmit any proxies through Slice operations; for such applications, all these proxy types are superfluous.
When you define an interface Name
in new Slice, you don’t define implicitly the corresponding proxy type. If you need this proxy type, you need to define it explicitly with a custom
type: see https://docs.icerpc.dev/slice1/language-guide/using-proxies-as-slice-types.
ice2slice
can’t detect which proxy types you need, so it produces proxy types for all interfaces in the Original Slice files “just in case”. For example, ice2slice
generates the following definitions for the Greeter
interface:
// New Slice
mode = Slice1
module VisitorCenter
/// Represents a simple greeter.
interface Greeter {
/// Creates a personalized greeting.
/// @param name: The name of the person to greet.
/// @returns: The greeting.
greet(name: string) -> string
}
[cs::type("VisitorCenter.GreeterProxy")]
custom GreeterProxy
We never use this GreeterProxy
type, so it’s clearer (and cleaner) to delete it from the New Slice definitions.
Simplify with anonymous containers
In Original Slice, sequence and dictionary are always named types, such as:
// Original Slice
// Define Lines as a sequence of strings.
sequence<string> Lines;
ice2slice
converts these named sequences and dictionaries into type aliases. For example, Lines
becomes:
// New Slice
typealias Lines = Sequence<string>
This is correct and the most direct translation. Nevertheless, you may want to take advantage of the anonymous container support in New Slice and make your Slice definitions more compact. Continuing with our example:
// New Slice
interface File : Node {
idempotent read() -> Lines
}
can be simplified into:
// New Slice
interface File : Node {
idempotent read() -> Sequence<string> // anonymous sequence
}
Services
IceStorm
You can use IceRPC to implement an IceStorm publisher or an IceStorm subscriber. As far as the IceStorm server is concerned, publishers and subscribers are just regular clients and servers.
IceGrid
IceRPC provides client-side support for Locators and indirectly IceGrid: an IceRPC application can find objects in IceGrid-managed servers and send requests to these objects.
However, you cannot reimplement an IceGrid-managed server with IceRPC. IceGrid-managed servers rely heavily on Ice configuration files and IceRPC doesn’t support Ice configuration.
Glacier2
IceRPC does not currently provide support for Glacier2 clients. However, you can use IceRPC to implement a server called by Glacier2.
Discovery
The discovery plug-ins provided by Ice (IceDiscovery and IceLocatorDiscovery) rely on UDP multicast and IceRPC doesn’t provide any support for UDP. As a result, you can’t discover an IceRPC server using IceDiscovery
, and an IceRPC client can’t use discover an Ice server that uses IceDiscovery
.
See Also
IceRPC for Ice users in the IceRPC documentation