Skip to content

Commit

Permalink
Merge pull request #126 from AntonioFalcao/release
Browse files Browse the repository at this point in the history
Adding Readiness and Liveness health checks.
  • Loading branch information
AntonioFalcaoJr authored Jan 25, 2021
2 parents 602b19a + 0191725 commit 277bd0a
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 17 deletions.
Binary file added .assets/img/Liveness.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .assets/img/LivenessMVC.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .assets/img/Readiness.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .assets/img/ReadinessMVC.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 33 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ After this, to configure the HTTP client, `init` secrets in [`./src/Dotnet5.Grap

```bash
dotnet user-secrets init
dotnet user-secrets set "HttpClient:Store" "http://localhost:5000/graphql"
dotnet user-secrets set "HttpClient:Store" "http://localhost:5000"
```

##### AppSettings
Expand All @@ -57,7 +57,7 @@ WebMCV
```json5
{
"HttpClient": {
"Store": "http://localhost:5000/graphql"
"Store": "http://localhost:5000"
}
}
```
Expand All @@ -84,7 +84,7 @@ WebMCV
```json5
{
"HttpClient": {
"Store": "http://webapi:5000/graphql"
"Store": "http://webapi:5000"
}
}
```
Expand Down Expand Up @@ -343,8 +343,36 @@ networks:
graphqlstore:
driver: bridge
```
### Health checks

## GraphQL Playground
Based on cloud-native concepts, **Readiness** and **Liveness** integrity verification strategies were implemented.

Web API

`http://localhost:5000/health/ready`
![Readiness](./.assets/img/Readiness.png)


`http://localhost:5000/health/live`
![Liveness](./.assets/img/Liveness.png)

---

Web MVC

`http://localhost:7000/health/ready`
![Readiness](./.assets/img/ReadinessMVC.png)

`http://localhost:7000/health/live`
![Liveness](./.assets/img/LivenessMVC.png)

---

### GraphQL Playground

By default **Playground** respond at `http://localhost:5000/ui/playground` but is possible configure the host and many others details in [`../...WebAPI/GraphQL/DependencyInjection/Configure.cs`](./src/Dotnet5.GraphQL3.Store.WebAPI/GraphQL/DependencyInjection/Configure.cs)
Expand Down Expand Up @@ -388,7 +416,6 @@ fragment comparisonFields on product {
description
}
```

RESULT

```json5
Expand All @@ -410,8 +437,8 @@ RESULT
}
```
___
#### Query named's and Variables

#### Query named's and Variables

QUERY

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="5.0.2" />
<PackageReference Include="GraphQL" Version="$(GraphQL_Version)" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="$(GraphQL_Server_Version)" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore.SystemTextJson" Version="$(GraphQL_Server_Version)" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Newtonsoft.Json;

namespace Dotnet5.GraphQL3.Store.WebAPI.Extensions.EndpointRouteBuilders
{
public static class EndpointRouteBuilderExtensions
{
public static void MapLivenessHealthChecks(this IEndpointRouteBuilder endpoints)
=> endpoints.MapHealthChecks(
pattern: "/health/live",
options: new HealthCheckOptions
{
AllowCachingResponses = false,
ResponseWriter = WriteHealthCheckLiveResponseAsync,
Predicate = registration => registration.Tags.Any() is false
});

public static void MapReadinessHealthChecks(this IEndpointRouteBuilder endpoints)
=> endpoints.MapHealthChecks(
pattern: "/health/ready",
options: new HealthCheckOptions
{
AllowCachingResponses = false,
ResponseWriter = WriteHealthCheckReadyResponseAsync,
Predicate = registration => registration.Tags.Contains("ready"),
ResultStatusCodes =
{
[HealthStatus.Healthy] = StatusCodes.Status200OK,
[HealthStatus.Degraded] = StatusCodes.Status500InternalServerError,
[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
}
});

private static Task WriteHealthCheckReadyResponseAsync(HttpContext httpContext, HealthReport healthReport)
{
httpContext.Response.ContentType = "application/json";

return httpContext.Response.WriteAsync(
JsonConvert.SerializeObject(new
{
OverallStatus = healthReport.Status.ToString(),
TotalCheckDuration = healthReport.TotalDuration.TotalSeconds.ToString("00:00:00.000"),
DependencyHealthChecks = healthReport.Entries.Select(
dependency => new
{
Name = dependency.Key,
Status = dependency.Value.Status.ToString(),
Duration = dependency.Value.Duration.TotalSeconds.ToString("00:00:00.000")
})
}));
}

private static Task WriteHealthCheckLiveResponseAsync(HttpContext httpContext, HealthReport healthReport)
{
httpContext.Response.ContentType = "application/json";
return httpContext.Response.WriteAsync(
JsonConvert.SerializeObject(new
{
OverallStatus = healthReport.Status.ToString(),
TotalCheckDuration = healthReport.TotalDuration.TotalSeconds.ToString("00:00:00.000")
}));
}
}
}
16 changes: 14 additions & 2 deletions src/Dotnet5.GraphQL3.Store.WebAPI/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Dotnet5.GraphQL3.Repositories.Abstractions.Extensions.DependencyInjection;
using Dotnet5.GraphQL3.Services.Abstractions.Extensions.DependencyInjection;
using Dotnet5.GraphQL3.Store.Repositories.Extensions.DependencyInjection;
using Dotnet5.GraphQL3.Store.WebAPI.Extensions.EndpointRouteBuilders;
using Dotnet5.GraphQL3.Store.WebAPI.GraphQL;
using Dotnet5.GraphQL3.Store.WebAPI.GraphQL.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
Expand All @@ -11,6 +12,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Hosting;

namespace Dotnet5.GraphQL3.Store.WebAPI
Expand All @@ -32,8 +34,12 @@ public void Configure(IApplicationBuilder app, DbContext dbContext)
app.UseDeveloperExceptionPage();

app.UseRouting()
.UseEndpoints(endpoints
=> endpoints.MapControllers());
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapLivenessHealthChecks();
endpoints.MapReadinessHealthChecks();
});

app.UseApplicationGraphQL<StoreSchema>();

Expand Down Expand Up @@ -62,6 +68,12 @@ public void ConfigureServices(IServiceCollection services)

services.Configure<KestrelServerOptions>(options
=> options.AllowSynchronousIO = true);

services.AddHealthChecks()
.AddSqlServer(
connectionString: _configuration.GetConnectionString("DefaultConnection"),
failureStatus: HealthStatus.Unhealthy,
tags: new[] {"ready"});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="5.0.1" />
<PackageReference Include="GraphQL.Client" Version="$(GraphQL_Client_Version)" />
<PackageReference Include="GraphQL.Client.Extensions" Version="3.0.1" />
<PackageReference Include="GraphQL.Client.Serializer.SystemTextJson" Version="$(GraphQL_Client_Version)" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Newtonsoft.Json;

namespace Dotnet5.GraphQL3.Store.WebMVC.Extensions.EndpointRouteBuilders
{
public static class EndpointRouteBuilderExtensions
{
public static void MapLivenessHealthChecks(this IEndpointRouteBuilder endpoints)
=> endpoints.MapHealthChecks(
pattern: "/health/live",
options: new HealthCheckOptions
{
AllowCachingResponses = false,
ResponseWriter = WriteHealthCheckLiveResponseAsync,
Predicate = registration => registration.Tags.Any() is false
});

public static void MapReadinessHealthChecks(this IEndpointRouteBuilder endpoints)
=> endpoints.MapHealthChecks(
pattern: "/health/ready",
options: new HealthCheckOptions
{
AllowCachingResponses = false,
ResponseWriter = WriteHealthCheckReadyResponseAsync,
Predicate = registration => registration.Tags.Contains("ready"),
ResultStatusCodes =
{
[HealthStatus.Healthy] = StatusCodes.Status200OK,
[HealthStatus.Degraded] = StatusCodes.Status500InternalServerError,
[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
}
});

private static Task WriteHealthCheckReadyResponseAsync(HttpContext httpContext, HealthReport healthReport)
{
httpContext.Response.ContentType = "application/json";

return httpContext.Response.WriteAsync(
JsonConvert.SerializeObject(new
{
OverallStatus = healthReport.Status.ToString(),
TotalCheckDuration = healthReport.TotalDuration.TotalSeconds.ToString("00:00:00.000"),
DependencyHealthChecks = healthReport.Entries.Select(
dependency => new
{
Name = dependency.Key,
Status = dependency.Value.Status.ToString(),
Duration = dependency.Value.Duration.TotalSeconds.ToString("00:00:00.000")
})
}));
}

private static Task WriteHealthCheckLiveResponseAsync(HttpContext httpContext, HealthReport healthReport)
{
httpContext.Response.ContentType = "application/json";
return httpContext.Response.WriteAsync(
JsonConvert.SerializeObject(new
{
OverallStatus = healthReport.Status.ToString(),
TotalCheckDuration = healthReport.TotalDuration.TotalSeconds.ToString("00:00:00.000")
}));
}
}
}
30 changes: 22 additions & 8 deletions src/Dotnet5.GraphQL3.Store.WebMVC/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
using System;
using Dotnet5.GraphQL3.Store.WebMVC.Clients;
using Dotnet5.GraphQL3.Store.WebMVC.Extensions.EndpointRouteBuilders;
using GraphQL.Client.Http;
using GraphQL.Client.Serializer.SystemTextJson;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Hosting;

namespace Dotnet5.GraphQL3.Store.WebMVC
{
public class Startup
{
public Startup(IConfiguration configuration)
private readonly IConfiguration _configuration;
private readonly IWebHostEnvironment _env;

public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
_env = env;
_configuration = configuration;
}

public IConfiguration Configuration { get; }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
public void Configure(IApplicationBuilder app)
{
if (env.IsDevelopment())
if (_env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
Expand All @@ -38,23 +42,33 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
endpoints.MapControllerRoute("default",
"{controller=Home}/{action=Index}/{id?}");

endpoints.MapReadinessHealthChecks();
endpoints.MapLivenessHealthChecks();
});
}

public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();

services.AddSingleton(provider
services.AddSingleton(_
=> new GraphQLHttpClient(
endPoint: new Uri(Configuration["HttpClient:Store"]),
endPoint: new Uri($"{_configuration["HttpClient:Store"]}/graphql"),
serializer: new SystemTextJsonSerializer(options =>
{
options.PropertyNameCaseInsensitive = true;
options.IgnoreNullValues = true;
})));

services.AddSingleton<IStoreGraphClient, StoreGraphClient>();

services.AddHealthChecks()
.AddUrlGroup(
uri: new Uri($"{_configuration["HttpClient:Store"]}/health/ready"),
name: "Store Web API",
failureStatus: HealthStatus.Unhealthy,
tags: new[] {"ready"});
}
}
}
2 changes: 1 addition & 1 deletion src/Dotnet5.GraphQL3.Store.WebMVC/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"HttpClient": {
"Store": "http://webapi:5000/graphql"
"Store": "http://webapi:5000"
},
"Logging": {
"LogLevel": {
Expand Down

0 comments on commit 277bd0a

Please sign in to comment.