Skip to content

Commit

Permalink
Merge branch 'main' into nuget_packages_update_22140773
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxDac authored Aug 16, 2023
2 parents a259e87 + d30366a commit 63adcce
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 1 deletion.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Microsoft OMEX

This repository contains source code for shared components used by the
This repository contains source code for shared components used by the
team, which is part of the Office organization, at Microsoft to build scalable and highly available distributed systems.

The code is released under the [MIT license](https://github.com/microsoft/Omex/blob/main/LICENSE).
Expand All @@ -17,6 +17,7 @@ Additional source code from the OMEX team can be located at <https://github.com/
* [__System__](https://github.com/microsoft/Omex/tree/main/src/System) - This library contains shared code for OMEX libraries. You'll find there utilities for logging,
argument validation, resource management, caching and more.
* [__System.UnitTests.Shared__](https://github.com/microsoft/Omex/tree/main/src/System.UnitTests.Shared) - This library contains abstractions and utilities used for creating unit tests.
* [__Diagnostics.HealthChecks__](https://github.com/microsoft/Omex/tree/main/src/Diagnostics.HealthChecks) - This library offers a set of default composable classes to implement custom health checks implementing the .NET `IHealthCheck` interface.

Please contribute to this repository via [pull requests](https://github.com/Microsoft/Omex/pulls) against the __main__ branch.

Expand Down
11 changes: 11 additions & 0 deletions src/Diagnostics.HealthChecks.AspNetCore/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Health Check ASP.NET Core helpers and extensions

This project introduces features specifically designed for the ASP.NET Core WebAPI Framework. This will not introduce other composable classes, but it will leverage them to provide a new factory method to create a **Liveness Health Check**, an health check that will only verify that the endpoint is reachable without letting it execute its logic, short-circuiting it using an implementatin of the `ActionFilterAttribute`.

#### Liveness Health Check

The liveness health check is a special type of health check that limits its probing inspection to the reachability of the endpoint. This means that it will not execute the endpoint logic, that will be short-circuited by the `LivenessCheckActionFilterAttribute`. The mechanic of this interaction is the following:

- The Health Check implementor will call the `CreateLivenessHttpHealthCheck` method in the [`HealthCheckComposablesExtensions`](./HealthCheckComposablesExtensions.cs) class. This factory method will create a default Http Health Check, but it will tweak the Activity by injecting the `activityMarker` function in the constructor, adding a key to the `Activity` baggage.
- The Activity will be sent to the endpoint.
- The endpoint will have to be marked with the [`LivenessCheckActionFilterAttribute`](./LivenessCheckActionFilterAttribute.cs). This filter will check whether there's an `Activity` injected in the request, and whether the `Activity` has the required key in its baggage: if that is the case, the endpoint execution will be short-circuited, returning an HTTP 200 status code, otherwise the method will be executed normally.
107 changes: 107 additions & 0 deletions src/Diagnostics.HealthChecks/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
## Omex Health Checks

This project and the [`Diagnostics.HealthChecks.AspNetCore`](../Diagnostics.HealthChecks.AspNetCore/Readme.md) offer a range of composable classes, all of which implement the .NET `IHealthCheck` interface. Developers can build their own Health Checks by reusing these classes behaviors' with the help of extension methods.

This approach enables the ability to create new composable objects to enrich the single Health Check with different behaviour, or substitute an existing one in the composition chain, offering more flexibility than the previous inheritance-based approach, where each class would have had to implement the Health Check following a rigid chain of inheritance.

In the following, each different composable classes provided by the package will be listed, followed by utilisation examples.

The last section of this guide will describe the old approach and its porting to the new implementation.

### Composable Health Checks classes

#### [`ObservableHealthCheck`](./Composables/ObservableHealthCheck.cs)

This Health Check provides a default implementation for exception handling for the built health check. This implementation also offers Activity creation and marking as health check, enabling the implementation of custom behaviours in the targeted controller endpoint.

"Marking" an activity means that the `Activity`'s baggage will be populated with a key and a corresponding value; WebAPI framework automatically sends the `Activity` information along with the request from an `HttpClient` call, allowing the receiver to fetch the `Activity` baggage values and acting accordingly. This feature will be leveraged when instantiating a [Liveness health check](../Diagnostics.HealthChecks.AspNetCore/HealthCheckComposablesExtensions.cs#liveness-health-check).
The `ObservableHealthCheck` will mark the `Activity` using the `MarkAsHealthCheck` method in the [`ActivityExtensions`](../Abstractions/Activities/ActivityExtensions.cs) class.

#### [`StartupHealthCheck`](./Composables/StartupHealthCheck.cs)

This implementation offers the capability of executing the health check until it succeeds, then it will re-use the last cached successful result without repeating the Health Check execution. This behaviour is useful when an health check must be executed only at startup, to check for instance for configuration errors, reporting them back to Service Fabric engine while deploying to mark the new service as faulty, enabling the orchestrator to rollback the deployment.

The developer must keep in mind that a health check that includes the startup health check in its implementation will keep executing if the health check result results in a warning or in an error state; this can happen when the service the health check it's implemented into is created by Service Fabric in response to an automatic scale-up.

The developer must also keep in mind that to use this composable Health Check they will have to provide an implementation of the [`IMemoryCache`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.imemorycache?view=dotnet-plat-ext-7.0) interface through Dependency Injection.

#### [`HttpEndpointHealthCheck`](./Composables/HttpEndpointHealthCheck.cs)

This implementation covers the most common usage of health checks. It will send a request to a controller endpoint in the context of the Service Fabric service (i.e. it will perform the call to `localhost`), it will handle the most common exceptions linked to HTTP calls using `HttpClient`, and it will eventually check the response for it to respond to the requirements.

This health check offers extensibility through the definition of different functions:

- `requestBuilder`: this function will have to return the request message that will be used by the `HttpClient` to perform the request.
- `healthCheckResponseChecker`: this function will receive in input all the information about the HTTP call response; its responsibility will be to report the Health Check Status as a response, and it will be used to determine the result of the Health Check.

In an effort to keep the Health Check as general as possible, the constructor will need an instance of the following:

- `IHttpClientFactory`: it will be the responsibility of the implementor to provide a way to create the `HttpClient` instance. This will be addressed in the [Implementation examples](#implementation-examples).
- `ActivitySource`: this Health Check will create an `Activity` instance. If a wrapper implementation in the composition classes chain created one already, it should be linked to the newly created one inside this health check.

The `HttpEndpointHealthCheck` offers the possibility to "mark" the created `Activity` with custom markers by defining the `activityMarker` function in the constructor.

### Implementation examples

Implementing an Health Check using the composable classes provided by default can be done following two patterns:

- **Leveraging the extension methods provided in the project**: this project provides a set of extension methods located in the [`HealthCheckComposablesExtensions`](./Composables/HealthCheckComposablesExtensions.cs) class that will complement the functionality offered by the default composable classes with default composition building for different use cases.
- **Creating the classes manually**: this approach consists on creating the classes manually inside the constructor of the Health Check that will be registered for the application. The resulting Health Check will have to declare the join set of dependencies needed by all the default classes used, along with its own.

In each case, having that the Health Check must be registered with the DI, the implementor will have to create a class implementing the `IHealthCheck` interface directly, composing the different health checks in the constructor and storing the reference in a private field in the class.

#### Composing using extension methods

The [`HealthCheckComposablesExtensions`](./Composables/HealthCheckComposablesExtensions.cs) class offers a number of health check factories that will create composed classes ready to be used for a number of different cases. If the developer wants to implement a simple HTTP Health Check, for instance, they can leverage the `CreateHttpHealthCheck` method:

```csharp
public class CustomHealthCheck : IHealthCheck
{
private IHealthCheck m_composed;

public CustomHealthCheck(IHttpClientFactory factory, ...)
{
// Instead of manually creating the classes, the constructor calls the static factory method that will create those classes for it.
m_composed = HealthCheckComposableExtensions.CreateHttpHealthCheck(
RequestBuilder,
HealthCheckComposableExtensions.CheckResponseStatusCodeAsync,
...);
}

// The method simply forward the health check implementation to the composed instance.
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) =>
m_composed.CheckHealthAsync(context, cancellationToken);
}
```

This implementation is currently equivalent to creating intances of the different Health Checks composable classes, as shown in the [next section](#manual-composition).

This approach is less flexible than the manual composition, but it provides more readability out of the box by representing exactly what the health check should do through the static factory method name.

More factory methods will be presented in the [Diagnostics.HealthChecks.AspNetCore](../Diagnostics.HealthChecks.AspNetCore/Readme.md) project.

#### Manual composition

In this case, the final Health Check class will have to instantiate the Health Checks composable classes manually in the constructor.

Let's say for instance that the Health Check will have to constantly query an endpoint in the service, like in the [previous example](#composition-using-extension-methods), the class constructor can be implemented as:

```csharp
public class CustomHealthCheck : IHealthCheck
{
private IHealthCheck m_composed;

public CustomHealthCheck(IHttpClientFactory factory, ...)
{
// In the constructor, the health check instance will be built manually, passing along all the necessary dependencies.
IHealthCheck httpHealthCheck = new HttpEndpointHealthCheck(...);
m_composed = new ObservableHealthCheck(httpHealthCheck, ...);
}

// The method simply forward the health check implementation to the composed instance.
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) =>
m_composed.CheckHealthAsync(context, cancellationToken);
}
```

This approach offers more flexibility and customisation, but it introduces more complexity and coupling with this project.

0 comments on commit 63adcce

Please sign in to comment.