Practical .NET

Implementing State in .NET Core gRPC Messages with oneof

In the real world, you've been dealing with the State pattern every time you designed a set of database tables. The Protocol Buffers specification lets you do the same thing when you define the messages you send and receive from your gRPC Web Service.

In programming one of the ways we use the word "state" is to talk about methods and data that apply only in specific scenarios. A customer, for example, should only have an amount in their credit limit if their status allows them to buy things on credit. When coding, we usually handle "state-like" problems using State, Strategy, or Role patterns. In a database, we handle them through data integrity rules that allow you to specify that, based on the value of a column in the table, another column either must be null or must have a value.

Managing state is supported, at least partially, in gRPC services through ProtoBuf's oneof definition. A oneof definition allows you to specify that only one of a group of fields will have a value (or values).

For example, let's consider returning a status message from a service's AddCustomer method. If everything goes well, the service should return the new customer's Id (in a RESTful application, we'd return the URL to be used to retrieve the new customer). If things go wrong, however, it should return an error code. Using oneof, I would define the return message so that it either contains a Message or Cust_Id (but never both), with a definition like this:

message AddCustomerResponse {
   oneof ResponseType 
   {
      string Message = 1;
      int32 Cust_Id = 2;
   }
}

The code that uses my AddResponseCustomer object, would look something like this:

AddCustomerResponse acr = new AddCustomerResponse();
try
{
   //…code to update customer…
   acr.CustId = 123;
}
catch (Exception ex)
{
   acr.Message = "oops";}
}

As you can see, either the CustId or the Message property is set depending on the state of the service (in error or not), but both are never set.

Processing oneof Messages
Of course, when a client gets my AddCustomerResponse, both the Message and CustId properties are going to be present even though only one of them will be set. How does the client determine which property to use? And how does the service ensure that both are never set? The generated gRPC code deals with that both at the server (where this message is being created) and at the client (where this message would be processed).

On the server, the generated code guarantees that only one of the two properties will be set. Setting the CustId property automatically causes the Message property to be cleared and vice versa. If you repeatedly set the properties involved in the oneof, the last property set is the one that retains its value and triggers the other property being cleared.

It may be hard to detect that because, as is the case with all the properties generated from the .proto file fields, these properties aren't nullable: "clearing" a property means having it set to its default value. As a result, if the server doesn't set either property then Message will be set to string.Empty and CustId will be 0…which is exactly the same result that you'd get if the update was successful and the CustId was set to 0.

Fortunately, the code generation process also creates an indicator property that lets you distinguish between the results. In addition to the Message and CustId fields, the AddCustomerResponse class also has a read-only property called ResponseTypeCase. That property will have one of an enumerated set of values: None (indicating that none of the fields in the oneof have been set) and a named value for each of the fields in the oneof (in my case, values named CustId and Message).

This means that typical code on the client that works with the oneof properties will look like this:

switch (acr.ResponseTypeCase)
{
   case ResponseTypeOneofCase.None:
     //…do nothing…
     break;
   case ResponseTypeOneofCase.CustId:
     //…access the new customer object…
     break;
  case ResponseTypeOneofCase.Message:
     //…report an error…
     break;
}

By the way, my oneof declaration also generates a ClearResponseType method that sets all the properties in the oneof declaration to their default value and sets ResponseTypeCase to None.

Options
But your oneof fields aren't limited to scalar data types. In fact, I suspect that most state scenarios don't pick between single data values but between sets of data values. For example, rather than returning just the customer Id when everything goes well I could return all of the customer data. This oneof declaration picks between returning a set of status information and a set of customer information:

message AddCustomerResponse {
   oneof ResponseType 
   {
      StatusResponse status = 1;
      CustomerResponse cust = 2;
   }
}
message StatusResponse 
{
  bool Was_Successful = 1;
  int32 Status_Code = 2;
  string Message = 3;
}
message CustomerResponse 
{
  int32 Cust_Id = 1;
  string First_Name = 2;
  string lastName = 3;
…
There are other options I haven't discussed here. For example, while I've assumed that it's a message that's generated on the server that's using the oneof field, you can use it in messages generated on the client. And, while I have only one oneof field in my message, you can have as many as you want. As I discussed in an earlier post, along with other gRPC message structures oneof definitions can be imported and shared among definitions.

Really, this is a terrific solution and, like any cool technology, now you'll be looking for a problem where you can use it.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Hands On: New VS Code Insiders Build Creates Web Page from Image in Seconds

    New Vision support with GitHub Copilot in the latest Visual Studio Code Insiders build takes a user-supplied mockup image and creates a web page from it in seconds, handling all the HTML and CSS.

  • Naive Bayes Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the naive Bayes regression technique, where the goal is to predict a single numeric value. Compared to other machine learning regression techniques, naive Bayes regression is usually less accurate, but is simple, easy to implement and customize, works on both large and small datasets, is highly interpretable, and doesn't require tuning any hyperparameters.

  • VS Code Copilot Previews New GPT-4o AI Code Completion Model

    The 4o upgrade includes additional training on more than 275,000 high-quality public repositories in over 30 popular programming languages, said Microsoft-owned GitHub, which created the original "AI pair programmer" years ago.

  • Microsoft's Rust Embrace Continues with Azure SDK Beta

    "Rust's strong type system and ownership model help prevent common programming errors such as null pointer dereferencing and buffer overflows, leading to more secure and stable code."

  • Xcode IDE from Microsoft Archrival Apple Gets Copilot AI

    Just after expanding the reach of its Copilot AI coding assistant to the open-source Eclipse IDE, Microsoft showcased how it's going even further, providing details about a preview version for the Xcode IDE from archrival Apple.

Subscribe on YouTube

Upcoming Training Events