Hello,
This design question is not explicitly related to GRPC but applicable to any message based communication system, say REST or WSDL.
I want to maintain nested contexts on the server side. I use the term "context" in a general way, not necessarily mean the "Grpc Context Helper Class".
In my design, each call can create a new context, and the upcoming calls will use the parent context. See the example proto:
syntax = "proto3";
import "google/protobuf/empty.proto";
package com.example.threatservice;
service ThreatService {
rpc registerClient (ClientRegistrationRequest) returns (ClientRegistrationResponse);
rpc subscribeClientCallback (ClientSubscriptionRequest) returns (stream ThreatEvent);
rpc initiateThreat(ThreatInitiationRequest) returns (ThreatInitiationResponse);
rpc associateIpAddressWithThreat(ThreatIpAssociationRequest) returns (google.protobuf.Empty);
rpc publishThreat(ThreatPublishRequest) returns (google.protobuf.Empty);
}
message ClientRegistrationRequest {
string clientId = 1;
}
message ClientRegistrationResponse {
string registrationContextId = 1;
}
message ClientSubscriptionRequest {
string clientId = 1;
string registrationContextId = 2;
}
message ThreatEvent {
string eventContextId = 1;
uint64 threatStartDate = 2;
}
message ThreatInitiationRequest {
string clientId = 1;
string registrationContextId = 2;
string eventContextId = 3;
}
message ThreatInitiationResponse {
string threatContextId = 1;
}
message ThreatIpAssociationRequest {
string clientId = 1;
string registrationContextId = 2;
string eventContextId = 3;
string threatContextId = 4;
string ipAddress = 10;
}
message ThreatPublishRequest {
string clientId = 1;
string registrationContextId = 2;
string eventContextId = 3;
string threatContextId = 4;
}
The ThreatService holds the registration of clients, and asks them for new threats from time to time.
Client is responsible to pass the parent context id while creating a new context. Since we know the graph on the server side it is not obligatory for the client to track all the parent contexts to the root. So the client requests can be simplified to:
message ClientRegistrationRequest {
string clientId = 1;
}
message ClientSubscriptionRequest {
string registrationContextId = 2;
}
message ThreatInitiationRequest {
string eventContextId = 3;
}
message ThreatInitiationResponse {
string threatContextId = 1;
}
message ThreatIpAssociationRequest {
string threatContextId = 4;
string ipAddress = 10;
}
message ThreatPublishRequest {
string threatContextId = 4;
}
So, writing a client is not that difficult. Also writing a server that would only handle happy paths.
But when the rainy path occurs, it is easy to create a mess on the server side. In the following scenario:
* Client registers itself -> Server creates record for the registration in memory
* Client subscribe to events -> Server creates a record for the subscription in memory
* Client initiates a new threat -> Server creates a record for the threat in memory
* Client associates an ip address to threat -> Server creates a record for the ip address in memory
* Client associates another ip address to the threat -> Server creates a record for the ip address in memory
* Client fails and connection drops
So, on the server side we need the get rid of the all the garbage.
It would have been so easy if the client was a synchronous, in-process Java implementation; the garbage collector would get rid of the all the data.
I need a systematic approach to solve the problem. It would probably use the
Context class, and some tree structure on the server side, but I don't want to reinvent the wheel or try to solve the incorrect problem. It might be just better not to create nested context for this example, say the
ThreatPublishRequest can hold all the ip addresses inside the message, but I suspect I won't be able to avoid contexts all together.
Any ideas?
Thanks