Skip to content

[API Proposal]: Public abstract HttpDependencyMetadataResolver for custom request classification strategies #6840

@rainsxng

Description

@rainsxng

Background and motivation

HTTP requests to downstream services often require consistent classification and metadata handling for telemetry, routing, and policy application. The HttpDependencyMetadataResolver provides a highly optimized solution using trie-based lookups to efficiently map HTTP requests to their corresponding metadata.

By exposing this as a public abstract class, we allow applications to:

  1. Leverage the core functionality without re-implementing complex matching logic
  2. Extend or customize the implementation for specific environments
  3. Integrate with existing service discovery or configuration systems

Making this an abstract class rather than an interface or sealed class provides the optimal balance between extensibility and encapsulation. Organizations with performance-critical systems and high-volume API traffic can utilize our efficient request classification without rebuilding the optimized trie-based lookup. Teams leveraging service mesh technologies can integrate with their service registry systems through extension points. The abstract approach also benefits multi-cloud deployments that need adaptive dependency resolution based on environment, while providing simplified mocking capabilities for integration testing without exposing sensitive implementation details. This design preserves performance while enabling the flexibility needed for diverse enterprise scenarios.

API Proposal

namespace Microsoft.Extensions.Http.Diagnostics;

/// <summary>
/// Interface to manage dependency metadata.
/// </summary>
public abstract class HttpDependencyMetadataResolver
{
    /// <summary>
    /// Get metadata for the specified HTTP request message.
    /// </summary>
    /// <param name="requestMessage">The HTTP request.</param>
    /// <returns>The resolved <see cref="RequestMetadata"/> if found; otherwise, <see langword="false" />.</returns>

public virtual RequestMetadata? GetRequestMetadata(HttpRequestMessage requestMessage);

    /// <summary>
    /// Gets request metadata for the specified HTTP web request.
    /// </summary>
    /// <param name="requestMessage">The HTTP web request.</param>
    /// <returns>The resolved <see cref="RequestMetadata"/> if found; otherwise, null.</returns>
public virtual RequestMetadata? GetRequestMetadata(HttpWebRequest requestMessage);
}

API Usage

The example below demonstrates a comprehensive HTTP request processing flow that showcases how the proposed abstract class enables flexible dependency resolution. The flow illustrates how the HttpDependencyMetadataResolver abstract class would serve as a core building block in an HTTP processing pipeline that needs to:

  1. Resolve service dependency information from both static configuration and dynamic service discovery
  2. Apply appropriate telemetry enrichment based on identified dependencies
  3. Configure request-specific policies based on identified operation characteristics
    This represents the primary use case for the abstract class API - allowing users to extend our optimized trie-based lookup algorithm while providing flexibility to integrate with their service discovery mechanisms and apply custom handling based on resolved metadata.
// 1. Create a custom implementation of the abstract class
public class CloudProviderDependencyResolver : HttpDependencyMetadataResolver
{
    private readonly ICloudServiceDiscovery _serviceDiscovery;
    
    public CloudProviderDependencyResolver (
        ICloudServiceDiscovery serviceDiscovery,
        IEnumerable<IDownstreamDependencyMetadata> staticMetadata)
        : base(staticMetadata)
    {
        _serviceDiscovery = serviceDiscovery;
    }
    
    protected override RequestMetadata? GetRequestMetadataCore(string method, string host, string path)
    {
        // First try the base implementation using static metadata
        var metadata = base.GetRequestMetadataCore(method, host, path);
        if (metadata != null)
        {
            return metadata;
        }
        
        // Fall back to dynamic service discovery
        return _serviceDiscovery.GetMetadataForRequest(method, host, path);
    }
}

// 2. Register with dependency injection
public void ConfigureServices(IServiceCollection services)
{
    // Register static metadata
    services.AddSingleton<IDownstreamDependencyMetadata, PaymentServiceMetadata>();
    services.AddSingleton<IDownstreamDependencyMetadata, InventoryServiceMetadata>();
    
    // Register cloud service discovery
    services.AddSingleton<ICloudServiceDiscovery, AzureServiceDiscovery>();
    
    // Register custom dependency resolver
    services.AddSingleton<HttpDependencyMetadataResolver, CloudProviderDependencyResolver>();
    
    // Add HTTP client with appropriate middleware
    services.AddHttpClient("api-client")
            .AddHttpMessageHandler<DependencyMetadataHandler>();
}

// 3. Use in HTTP message handler
public class DependencyMetadataHandler : DelegatingHandler
{
    private readonly HttpDependencyMetadataResolver _metadataResolver;
    private readonly ITelemetry _telemetry;
    
    public DependencyMetadataHandler(
        HttpDependencyMetadataResolver metadataResolver,
        ITelemetry telemetry)
    {
        _metadataResolver= metadataResolver;
        _telemetry = telemetry;
    }
    
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var metadata = _metadataManager.GetRequestMetadata(request);
        
        if (metadata != null)
        {
            // Add telemetry information
            _telemetry.AddProperty("DependencyName", metadata.DependencyName);
            _telemetry.AddProperty("RequestName", metadata.RequestName);
            
            // Apply policy based on metadata
            switch (metadata.RequestName)
            {
                case "CriticalPaymentOperation":
                    request.SetTimeout(TimeSpan.FromSeconds(10));
                    break;
                    
                case "BulkOperation":
                    request.SetTimeout(TimeSpan.FromMinutes(2));
                    break;
            }
        }
        
        return await base.SendAsync(request, cancellationToken);
    }
}

Alternative Designs

No response

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions