Skip to content

Commit

Permalink
Add retrier example app (#1284)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK authored May 7, 2021
1 parent 4c410dc commit 560e141
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 0 deletions.
8 changes: 8 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,11 @@ Code-first is a good choice if an app is written entirely in .NET. Code contract
* Configure [protobuf-net.Grpc](https://github.com/protobuf-net/protobuf-net.Grpc)
* Create a code-first gRPC service
* Create a code-first gRPC client

## [Retrier](./Retrier)

The retrier example shows how to configure a client to use gRPC retries to retry failed calls. gRPC retries enables resilient, fault tolerant gRPC apps in .NET.

##### Scenarios:

* Configure [gRPC retires](https://docs.microsoft.com/aspnet/core/grpc/retries)
16 changes: 16 additions & 0 deletions examples/Retrier/Client/Client.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Protobuf Include="..\Proto\retry.proto" GrpcServices="Client" Link="Protos\retry.proto" />

<PackageReference Include="Grpc.Net.Client" Version="$(GrpcDotNetPackageVersion)" />
<PackageReference Include="Google.Protobuf" Version="$(GoogleProtobufPackageVersion)" />
<PackageReference Include="Grpc.Tools" Version="$(GrpcPackageVersion)" PrivateAssets="All" />
</ItemGroup>

</Project>
125 changes: 125 additions & 0 deletions examples/Retrier/Client/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#endregion

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Grpc.Net.Client.Configuration;
using Retry;

namespace Client
{
public class Program
{
static async Task Main(string[] args)
{
using var channel = CreateChannel();
var client = new Retrier.RetrierClient(channel);

await UnaryRetry(client);

Console.WriteLine("Shutting down");
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}

private static async Task UnaryRetry(Retrier.RetrierClient client)
{
Console.WriteLine("Delivering packages...");
foreach (var product in Products)
{
try
{
var package = new Package { Name = product };
var call = client.DeliverPackageAsync(package);
var response = await call;

#region Print success
Console.ForegroundColor = ConsoleColor.Green;
Console.Write(response.Message);
Console.ResetColor();
Console.Write(" " + await GetRetryCount(call.ResponseHeadersAsync));
Console.WriteLine();
#endregion
}
catch (RpcException ex)
{
#region Print failure
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex.Status.Detail);
Console.ResetColor();
#endregion
}

await Task.Delay(TimeSpan.FromSeconds(0.2));
}
}

private static GrpcChannel CreateChannel()
{
var methodConfig = new MethodConfig
{
Names = { MethodName.Default },
RetryPolicy = new RetryPolicy
{
MaxAttempts = 10,
InitialBackoff = TimeSpan.FromSeconds(0.5),
MaxBackoff = TimeSpan.FromSeconds(0.5),
BackoffMultiplier = 1,
RetryableStatusCodes = { StatusCode.Unavailable }
}
};

return GrpcChannel.ForAddress("http://localhost:5000", new GrpcChannelOptions
{
ServiceConfig = new ServiceConfig { MethodConfigs = { methodConfig } }
});
}

private static async Task<string> GetRetryCount(Task<Metadata> responseHeadersTask)
{
var headers = await responseHeadersTask;
var previousAttemptCount = headers.GetValue("grpc-previous-rpc-attempts");
return previousAttemptCount != null ? $"(retry count: {previousAttemptCount})" : string.Empty;
}

private static readonly IList<string> Products = new List<string>
{
"Secrets of Silicon Valley",
"The Busy Executive's Database Guide",
"Emotional Security: A New Algorithm",
"Prolonged Data Deprivation: Four Case Studies",
"Cooking with Computers: Surreptitious Balance Sheets",
"Silicon Valley Gastronomic Treats",
"Sushi, Anyone?",
"Fifty Years in Buckingham Palace Kitchens",
"But Is It User Friendly?",
"You Can Combat Computer Stress!",
"Is Anger the Enemy?",
"Life Without Fear",
"The Gourmet Microwave",
"Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean",
"The Psychology of Computer Cooking",
"Straight Talk About Computers",
"Computer Phobic AND Non-Phobic Individuals: Behavior Variations",
"Net Etiquette"
};
}
}
31 changes: 31 additions & 0 deletions examples/Retrier/Proto/retry.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

import "google/protobuf/wrappers.proto";

package retry;

service Retrier {
rpc DeliverPackage (Package) returns (Response);
}

message Package {
string name = 1;
}

message Response {
string message = 1;
}
31 changes: 31 additions & 0 deletions examples/Retrier/Retrier.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29230.61
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{534AC5F8-2DF2-40BD-87A5-B3D8310118C4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{48A1D3BC-A14B-436A-8822-6DE2BEF8B747}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{534AC5F8-2DF2-40BD-87A5-B3D8310118C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{534AC5F8-2DF2-40BD-87A5-B3D8310118C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{534AC5F8-2DF2-40BD-87A5-B3D8310118C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{534AC5F8-2DF2-40BD-87A5-B3D8310118C4}.Release|Any CPU.Build.0 = Release|Any CPU
{48A1D3BC-A14B-436A-8822-6DE2BEF8B747}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48A1D3BC-A14B-436A-8822-6DE2BEF8B747}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48A1D3BC-A14B-436A-8822-6DE2BEF8B747}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48A1D3BC-A14B-436A-8822-6DE2BEF8B747}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D22B3129-3BFB-41FA-9FCE-E45EBEF8C2DD}
EndGlobalSection
EndGlobal
38 changes: 38 additions & 0 deletions examples/Retrier/Server/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#endregion

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace Server
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
13 changes: 13 additions & 0 deletions examples/Retrier/Server/Server.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Protobuf Include="..\Proto\retry.proto" GrpcServices="Server" Link="Protos\retry.proto" />

<PackageReference Include="Grpc.AspNetCore" Version="$(GrpcDotNetPackageVersion)" />
</ItemGroup>

</Project>
44 changes: 44 additions & 0 deletions examples/Retrier/Server/Services/RetrierService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#endregion

using System;
using System.Threading.Tasks;
using Grpc.Core;
using Retry;

namespace Server
{
public class RetrierService : Retrier.RetrierBase
{
private readonly Random _random = new Random();

public override Task<Response> DeliverPackage(Package request, ServerCallContext context)
{
const double deliveryChance = 0.5;
if (_random.NextDouble() > deliveryChance)
{
throw new RpcException(new Status(StatusCode.Unavailable, $"- {request.Name}"));
}

return Task.FromResult(new Response
{
Message = $"+ {request.Name}"
});
}
}
}
48 changes: 48 additions & 0 deletions examples/Retrier/Server/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#endregion

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Server
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<RetrierService>();
});
}
}
}
10 changes: 10 additions & 0 deletions examples/Retrier/Server/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Grpc": "Information",
"Microsoft": "Information"
}
}
}
13 changes: 13 additions & 0 deletions examples/Retrier/Server/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
}
}

0 comments on commit 560e141

Please sign in to comment.