Protobuf: streaming input with common data to all elements - protocol-buffers

Below is the service spec:
service Cooler {
rpc saveThing (stream SaveRequest) returns (SaveReply);
}
I need to stream messages to to Cooler.saveThing(). All the SaveRequests have a common field author and unique fields per a Thing are price and name. How can I send the author only once?
Not working attempt - Multiple inputs
It would be a solution but it is not supported by protobuf yet.
service Cooler {
rpc saveThing (stream SaveRequest, Author) returns (SaveReply);
}
Not working attempt - Nested message
Every received element of SaveRequest will still contain author and an array of Things.
message SaveRequest {
message Thing {
int price = 1;
string name = 2;
}
repeated Thing things = 1;
string author = 2;
}
Possible solution
Grpc headers.
Question
How can I send the author only once?

Related

gRPC endpoint that sends initial data and afterwards stream of data

I want to define a gRPC endpoint, that when called, returns some initial data and afterwards a stream of data. For example in a game it could create a lobby and return some initial data about the lobby to the creator and afterwards stream an event every time a player joins.
I want to achieve something like this:
message LobbyData{
string id = 1;
}
message PlayerJoin{
string id = 2;
}
service LobbyService {
rpc OpenLobbyAndListenToPlayerJoins(Empty) returns (LobbyData, stream PlayerJoin);
}
Unfortunately this is not possible so I have 2 options:
Option 1 (not what a want)
Creating two seperate RPCs, and call them sequentially on the client:
service LobbyService {
rpc OpenLobby(Empty) returns (LobbyData);
rpc ListenToPlayerJoins(LobbyData) returns (stream PlayerJoin);
}
This however creates the problem that players can join the lobby possibly before the second RPC from the client (ListenToPlayerJoins) reaches the server. So on the server we would need additional logic to open the lobby only after the ListenToPlayerJoins RPC from the creator has arrived.
Option 2 (also not what I want)
Use a single RPC with a sum type:
message LobbyDataOrPlayerJoin{
oneof type {
LobbyData lobby_data = 1;
PlayerJoin player_join = 2;
}
}
service LobbyService {
rpc OpenLobbyAndListenToPlayerJoins(Empty) returns (stream LobbyDataOrPlayerJoin);
}
This would allow for just one RPC, where the first element of the stream is a LobbyData object and all subsequent elements are PlayerJoins. What is not nice about this is that all streamed events after the first one are PlayerJoins but the client receives them still as the sum type LobbyDataOrPlayerJoin. Which is not clean.
Both options seem to me like workarounds. Is there a real solution to this problem?

Can I change the numbered tags in a proto file?

This post describes what the numbered tags in proto files are for, essentially match fields when serializing and deserializing the data. My question is: what happens if I change the number of an existing field?
Taking the same example, say this is the original data
message SearchRequest {
required string query = 1;
// Pagination fields
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
And I want to add a new field, which logically makes sense to put before the pagination fields - Can I re-enumerate the fields as below, if I'm not using the page_number and result_per_page fields yet (though the message type SearchRequest is in use)?
message SearchRequest {
required string query = 1;
optional int32 new_data = 2;
// Pagination fields
optional int32 page_number = 3;
optional int32 result_per_page = 4;
}
Should I have given the pagination fields higher numbers from the start to allow for new fields?
The documentation says
These field numbers are used to identify your fields in the message
binary format, and should not be changed once your message type is in
use.
Changing field numbers is almost always a bad idea - you will get very odd behaviour unless all clients and servers are.deployed at exactly the same time and there is no persisted payload data anywhere (in files, databases, etc). In particular, the serializer will try to interpret data as a different thing: in some cases this will cause a serialization failure and in other cases it will silently and happily deserialize the data with garbage meaning, causing chaos.
There is no reason not to simply use field 5.

Spring Webflux: Extract value from Mono

I am new to spring webflux and am trying to perform some arithmetic on the values of two monos. I have a product service that retrieves account information by calling an account service via webClient. I want to determine if the current balance of the account is greater than or equal to the price of the product.
Mono<Account> account = webClientBuilder.build().get().uri("http://account-service/user/accounts/{userId}/",userId)
.retrieve().bodyToMono(Account.class);
//productId is a path variable on method
Mono<Product> product =this.productService.findById(productId);
When I try to block the stream I get an error
block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
//Causes Error
Double accountBalance = account.map(a->a.getBalance()).block():
Double productPrice = product.map(p->p.getPrice()).block();
///Find difference, send response accordingly....
Is this the correct approach of there is another, better way to achieve this? I was also thinking something along the lines of:
Mono<Double> accountBalance = account.map(a->a.getBalance()):
Mono<Double> productPrice = product.map(p->p.getPrice());
Mono<Double> res = accountBalance.zipWith(productPrice,(b,p)-> b-p);
//Something after this.....
You can't use block method on main reactor thread. This is forbidden. block may work when publish mono on some other thread but it's not a case.
Basically your approach with zipping two monos is correct. You can create some helper method to do calculation on them. In your case it may look like:
public boolean isAccountBalanceGreater(Account acc, Product prd) {
return acc.getBalance() >= prd.getPrice();
}
And then in your Mono stream you can pass method reference and make it more readable.
Mono<Boolean> result = account.zipWith(productPrice, this::isAccountBalanceGreater)
The question is what you want to do with that information later. If you want return to your controller just true or false that's fine. Otherwise you may need some other mappings, zippings etc.
Update
return account.zipWith(productPrice, this::createResponse);
...
ResponseEntity createResponse(Account acc, Product prd) {
int responseCode = isAccountBalanceGreater(acc, prd) ? 200 : 500;
return ResponseEntity.status(responseCode).body(prd);
}

Replace field with same type, but different meaning in Protocol Buffers

I'd like to update a message in Protocol Buffers:
message Person {
string name = 1;
}
Now, suppose that I don't want a name for a Person, but only its address:
message Person {
string address = 1;
}
Now, the id could remain 1 since the type is always a string, but I was wondering if it's better to rewrite the message in this way:
message Person {
string address = 2;
reserved 1;
}
in order to have more readability between versions.
you can just change the field name safely(if you want to keep same id and same type), please check below post would help you.
Protocol buffer: does changing field name break the message?
and also in my opion it is always good have
required or optional
annotation to the message fields

xpath like query for protobuf messages

I am looking for a xpath like query language for protobuf messages. For example, for the Person message shown below [ borrowed from the Developer guide ]
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
I would like to have methods like
XPBQuery.get(person, "$.id") ==> returns the id
XPBQuery.get(person, "$.name") ==> returns the name
XPBQuery.get(person, "$.phone.number[0]") ==> returns the first phone number
One way is to convert the proto to Json and use a JsonPath/JsPath API's. But it may be expensive to convert to Json everytime especially for large Proto objects.
Any help is much appreciated.
Thanks,
Irfan
Support for that is coming in protobuf v3: https://github.com/google/protobuf/blob/4644f99d1af4250dec95339be6a13e149787ab33/src/google/protobuf/field_mask.proto
While looking for a solution to a similar problem I discovered:
PbQuery (python)
protobuf-utils (java)
protobuf-el (java)
(I did not use those libraries as my target language is C++, but hope this might help someone else)
Good luck!

Resources