Skip to content

Commit 9b3c07e

Browse files
committed
1.0.0 Java migration guide
1 parent 5c9b94b commit 9b3c07e

File tree

1 file changed

+293
-1
lines changed

1 file changed

+293
-1
lines changed

content/docs/migration_1.0/Java.md

Lines changed: 293 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,296 @@ menu:
66
parent: migration_1.0
77
---
88

9-
The Java API is still a work in progress. This migration guide will be updated as soon as the new Zenoh-Java API is released!
9+
# Java API migration guide for 1.0.0
10+
11+
The API has been extensively modified with the following goals in mind:
12+
- Match the API rework done through the Rust Zenoh API.
13+
- Abstracting users from the underlying native mechanisms.
14+
- Making the API more idiomatic, more "imperative".
15+
16+
## Remotion of the builder patterns and options parameters
17+
18+
Throughout the 0.11.0 API, we were exposing builders, for instance:
19+
20+
```java
21+
session.put(keyExpr, value)
22+
.congestionControl(CongestionControl.BLOCK)
23+
.priority(Priority.REALTIME)
24+
.res()
25+
```
26+
27+
This seemed odd, because "put" is an imperative statement. This could lead to confusions
28+
since it's not evident that instead of performing the put operation, that function returns
29+
a builder that must be built with a 'res()' (from resolve) function.
30+
After some deliberation, we opted for the following approach:
31+
- Making the `put` operation actually imperative, meaning that calling that function actually performs the put operation. No need for the `.res()` call anymore.
32+
- Provide configuration options with an optional parameter, in this case a `PutOptions` parameter:
33+
34+
Example:
35+
```java
36+
var putOptions = new PutOptions();
37+
putOptions.setCongestionControl(CongestionControl.BLOCK);
38+
putOptions.setPriority(Priority.REALTIME);
39+
//...
40+
session.put(keyExpr, payload, putOptions); // triggers the put
41+
```
42+
43+
If not provided, the default configuration is used:
44+
```
45+
session.put(keyExpr, payload);
46+
```
47+
48+
## Session opening
49+
50+
`Session.open(config: Config)` has now been replaced with `Zenoh.open(config: Config)`.
51+
52+
## Encoding rework
53+
54+
The `Encoding` class has been modified. In 0.11.0. it had the signature
55+
```java
56+
class Encoding(val knownEncoding: KnownEncoding, val suffix: String = "")
57+
```
58+
where `KnownEncoding` was an enum.
59+
60+
In 0.11.0. an encoding instance would be created as follows:
61+
```java
62+
var encoding = new Encoding(KnownEncoding.TEXT_JSON)
63+
```
64+
65+
In 1.0.0. we have implemented the following changes:
66+
- `KnownEncoding` enum is removed, instead we provide static `Encoding` instances containing an ID and a description.
67+
- Custom encodings can be created
68+
- The list of pre-defined encodings has been expanded.
69+
70+
In 1.0.0. the previous example would instead now become:
71+
72+
```kotlin
73+
var encoding = Encoding.TEXT_JSON
74+
```
75+
76+
## Session-managed declarations
77+
78+
Up until 0.11.0, it was up to the user to keep track of their variable declarations to keep them alive, because once the variable declarations were garbage collected, the declarations were closed. This was because each Kotlin variable declaration is associated with a native Rust instance, and in order to avoid leaking the memory of that Rust instance, it was necessary to free it upon dropping the declaration instance. However, this behavior could be counterintuitive, as users were expecting the declaration to keep running despite losing track of the reference to it.
79+
80+
In this release we introduce a change in which any session declaration is internally associated to the session from which it was declared. Users may want to keep a reference to the declaration in case they want to undeclare it earlier before the session is closed, otherwise, the declaration is kept alive.
81+
82+
For instance:
83+
84+
```java
85+
var keyExprA = KeyExp::tryFrom("A/B/C");
86+
var subscriber = session.declareSubscriber(keyExprA, { sample -> System.out.println("Receiving sample on 'A/B/C': " + sample.getPayload() + ")") });
87+
88+
var keyExprB = KeyExpr::tryFrom("A/C/D");
89+
session.declareSubscriber(keyExprB, { sample -> System.out.println("Receiving sample on 'A/C/D': " + sample.getPayload() + ")") }); // No variable is associated to the declared session, on 0.11.0 it would have been instantly dropped
90+
```
91+
92+
Therfore, when receiving a 'hello' message on `A/**` we would still see:
93+
94+
```
95+
>> Receiving sample on 'A/B/C': hello
96+
>> Receiving sample on 'A/C/D': hello
97+
```
98+
since both declarations are still alive.
99+
100+
Now the question is, for how long? What happens first, either when:
101+
- you call `undeclare()` or `close()` to the declaration
102+
- the session is closed, then all the associated declarations are automatically undeclared.
103+
104+
## Key Expression rework
105+
106+
KeyExpr instances are not bound to a native key expression anymore, unless they are declared from a session. It is safe to drop the reference to the key expression instance, but the memory management associated to a key expression will differ:
107+
- If the KeyExpr was not declared from a session, then the garbage collector simply claims back the memory.
108+
- If it was declared from a session, then the session keeps track of it and frees the native memory upon closing the session.
109+
110+
Declaring a KeyExpr on a session results in better performance, since the session is informed that we intend to use a key expression repeatedly.
111+
We also associate a native key expression to a Kotlin key expression instance, avoiding copies.
112+
113+
114+
## Config loading
115+
116+
When opening a session, it's now mandatory to provide a configuration to the session, even for a default config:
117+
```java
118+
var config = Config.loadDefault();
119+
var session = Zenoh.open(config);
120+
```
121+
122+
The `Config` class has been modified
123+
124+
- `Config.loadDefault(): Config`: returns the default config
125+
- `Config.fromFile(file: File): Config`: allows to load a config file.
126+
- `Config.fromPath(path: Path): Config`: allows to load a config file from a path.
127+
- `Config.fromJson(json: String): Config`: loads the config from a string literal with json format
128+
- `Config.fromJson5(json5: String): Config`: loads the config from a string literal with json5 format
129+
- `Config.fromYaml(yaml: String): Config`: loads the config from a string literal with yaml format
130+
- `Config.fromJsonElement(jsonElement: JsonElement): Config`: loads the config from a kotlin JsonElement.
131+
132+
In case of failure loading the config, an exception is thrown.
133+
134+
135+
## Packages rework
136+
137+
The package structure of the API is now aligned with Zenoh Rust package structure.
138+
139+
Changes:
140+
141+
- Removing the "prelude" package
142+
- QoS package now contains:
143+
- `CongestionCOntrol`
144+
- `Priority`
145+
- `Reliability`
146+
- `QoS`
147+
- Bytes package is created with:
148+
- `ZBytes`, `IntoZBytes`, `Encoding`
149+
- Config package:
150+
- `Config`, `ZenohId`
151+
- Session package:
152+
- `SessionInfo`
153+
- Query package:
154+
- contains `Query` and `Queryable`
155+
- removing queryable package
156+
157+
158+
## Reliability
159+
160+
The `Reliability` config parameter used on when declaring a subscriber, has been moved. It now must be specified when declaring a `Publisher` or when performing a `Put` or a `Delete` operation.
161+
162+
163+
## Logging
164+
165+
There are two main changes regarding logging, the interface and the mechanism.
166+
167+
Lets look at the following example, where we want to run the ZPub example with debug logging. On 1.0.0 we'll do:
168+
169+
```
170+
RUST_LOG=debug gradle ZPub
171+
```
172+
173+
If we wanted to enable debug logging and tracing for some specific package, for instance `zenoh::net::routing`, we'd do:
174+
175+
```
176+
RUST_LOG=debug,zenoh::net::routing=trace gradle ZPub
177+
```
178+
179+
However, this is not enabled by default.
180+
In order to enable logging, one of these newly provided functions must be called:
181+
182+
```java
183+
Zenoh.tryInitLogFromEnv();
184+
```
185+
186+
and
187+
188+
```java
189+
Zenoh.initLogFromEnvOr(fallbackFilter: String);
190+
```
191+
192+
This last function allows to programmatically specify the logs configuration if not provided as an environment variable.
193+
194+
## ZBytes serialization / deserialization & replacement of Value
195+
196+
We have created a new abstraction with the name of `ZBytes`. This class represents the bytes received through the Zenoh network. This new approach has the following implications:
197+
198+
- `Attachment` class is replaced by `ZBytes`.
199+
- `Value` is replaced by the combination of `ZBytes` and `Encoding`.
200+
- Replacing `ByteArray` to represent payloads
201+
202+
With `ZBytes` we have also introduced a Serialization and Deserialization for convenient conversion between `ZBytes` and Kotlin types.
203+
204+
### Serialization & Deserialization
205+
206+
We can serialize primitive types into a `ZBytes` instance, that is, converting the data into bytes processed by the zenoh network.
207+
208+
We'll see that for serialization and deserialization, we need to create instances of `ZSerializer` and `ZDeserializer` respectively.
209+
210+
#### Primitive types
211+
212+
(De)Serialization is supported by the following primitive types:
213+
214+
* Numeric: `Byte`, `Short`, `Integer`, `Long`, `Float`, and `Double`
215+
* `String`
216+
* `ByteArray`
217+
218+
For instance:
219+
```java
220+
Integer input = 123456;
221+
ZSerializer<Integer> serializer = new ZSerializer<>() {};
222+
ZBytes zbytes = serializer.serialize(input);
223+
224+
ZDeserializer<Integer> deserializer = new ZDeserializer<>() {};
225+
Integer output = deserializer.deserialize(zbytes);
226+
assert input.equals(output);
227+
```
228+
229+
This approach works as well for the other aforementioned types.
230+
231+
For serialization, `String` and `ByteArray` the functions `ZBytes::from(string: String)` and `ZBytes::from(bytes: ByteArray)` can be used respectively. Analogously, deserialization, `ZBytes::toString()` and `ZBytes::toByteArray()` can be used.
232+
For instance:
233+
```java
234+
var exampleString = "example string";
235+
var zbytes = ZBytes.from(exampleString);
236+
var output = zbytes.toString();
237+
assert exampleString.equals(output);
238+
```
239+
240+
#### Lists
241+
242+
Lists are supported, but they must be either:
243+
- List of numeric types : (`Byte`, `Short`, `Int`, `Long`, `Float`, `Double`)
244+
- List of `String`
245+
- List of `ByteArray`
246+
- List of another supported type
247+
248+
The serialize syntax must be used:
249+
```java
250+
List<Integer> input = List.of(1, 2, 3, 4, 5);
251+
ZSerializer<List<Integer>> serializer = new ZSerializer<>() {};
252+
ZBytes zbytes = serializer.serialize(input);
253+
254+
ZDeserializer<List<Integer>> deserializer = new ZDeserializer<>() {};
255+
List<Integer> output = deserializer.deserialize(zbytes);
256+
assert input.equals(output);
257+
```
258+
259+
#### Maps
260+
261+
Maps are supported as well, with the restriction that their inner types must supported primitives:
262+
- Numeric types
263+
- `String`
264+
- `ByteArray`
265+
- Map of another supported types
266+
267+
```java
268+
Map<String, Integer> input = Map.of("one", 1, "two", 2, "three", 3);
269+
ZSerializer<Map<String, Integer>> serializer = new ZSerializer<>() {};
270+
ZBytes zbytes = serializer.serialize(input);
271+
272+
ZDeserializer<Map<String, Integer>> deserializer = new ZDeserializer<>() {};
273+
Map<String, Integer> output = deserializer.deserialize(zbytes);
274+
assert input.equals(output);
275+
```
276+
277+
#### Parameterized types combinations
278+
279+
Combinations of all the above types is supported. For instance:
280+
281+
- List of lists
282+
```java
283+
List<List<Integer>> input = List.of(List.of(1, 2, 3));
284+
ZSerializer<List<List<Integer>>> serializer = new ZSerializer<>() {};
285+
ZBytes zbytes = serializer.serialize(input);
286+
287+
ZDeserializer<List<List<Integer>>> deserializer = new ZDeserializer<>() {};
288+
List<List<Integer>> output = deserializer.deserialize(zbytes18);
289+
assert input.equals(output);
290+
```
291+
292+
- List of maps
293+
```java
294+
List<Map<String, Integer>> input = List.of(Map.of("a", 1, "b", 2));
295+
ZSerializer<List<Map<String, Integer>>> serializer = new ZSerializer<>() {};
296+
ZBytes zbytes = serializer.serialize(input);
297+
298+
ZDeserializer<List<Map<String, Integer>>> deserializer = new ZDeserializer<>() {};
299+
List<Map<String, Integer>> output = deserializer.deserialize(zbytes);
300+
assert input.equals(output);
301+
```

0 commit comments

Comments
 (0)