Skip to content

ayushwritescode/RaftLabs

Repository files navigation

RaftLabs User API Client

A .NET 9.0 solution demonstrating Clean Architecture principles for interacting with external APIs. This project implements a robust, generic API client for the reqres.in service with comprehensive error handling, retry logic, caching, and extensive testing.

  • [This README is generated with the help of an AI tool but the project is handmade by a human developer :) ]

Architecture Overview

This solution follows Clean Architecture principles with a generic, reusable design that can support multiple entity types:

├── src/
│   ├── RaftLabs.Domain/          # Core business entities and interfaces
│   ├── RaftLabs.Application/     # Use cases and application services  
│   ├── RaftLabs.Infrastructure/  # Generic HTTP client and repository patterns
│   └── RaftLabs.Presentation/    # Web API controllers and endpoints
├── tests/
│   └── RaftLabs.UnitTests/       # Comprehensive unit tests (11 tests)
├── samples/
│   └── RaftLabs.Console/         # Interactive demo console application
└── README.md

Architecture Layers

  1. Domain Layer (innermost): Contains core business entities, domain exceptions, and repository interfaces
  2. Application Layer: Implements use cases, business logic, and coordinates between layers
  3. Infrastructure Layer: Generic HTTP client, repository base classes, caching, and retry policies
  4. Presentation Layer: REST API endpoints with global exception handling and Swagger documentation

Generic Architecture Benefits

  • Extensible Design: Easily add new entity types (Products, Orders, etc.) without code duplication
  • Type-Safe Generic Patterns: IRepository<T, TId> and ExternalRepositoryBase<T, TId, TExternal, TExternalPage>
  • Reusable HTTP Client: Generic IApiClient supporting any endpoint and response type
  • Consistent Patterns: All repositories follow the same structure and behavior

Features

Core Functionality

  • Generic HTTP Client: IApiClient using IHttpClientFactory with named client registration
  • Generic Repository Pattern: IRepository<T, TId> and ExternalRepositoryBase<T, TId, TExternal, TExternalPage>
  • Options Pattern: Strongly-typed configuration with IOptions<ApiSettings>
  • Async/Await Patterns: Full asynchronous implementation throughout the solution
  • Pagination Support: Handles paginated data retrieval and aggregation
  • Type-Safe Mapping: Clean mapping between external API models and domain entities

Resilience & Performance

  • Retry Logic: Implements exponential backoff using Polly library
  • Timeout Handling: Configurable request timeouts per named HttpClient
  • In-Memory Caching: Reduces API calls with configurable cache expiration
  • Error Handling: Comprehensive exception handling with domain-specific exceptions

Configuration & Monitoring

  • Configurable Settings: External API settings via appsettings.json with Options pattern
  • Structured Logging: Comprehensive logging with named HttpClient identification
  • Health Monitoring: Built-in error tracking and retry monitoring
  • Global Exception Handling: Centralized exception middleware

API Documentation & Testing

  • Swagger/OpenAPI: Interactive API documentation with API key authentication
  • Unit Testing: 11 comprehensive unit tests with 100% pass rate
  • Mocking Strategy: Clean mocking of IApiClient and other dependencies
  • XML Documentation: Comprehensive code documentation

API Endpoints

The solution exposes the following REST endpoints:

Users Controller

Method Endpoint Description
GET /api/users/{id} Get a specific user by ID
GET /api/users Get all users (aggregated from all pages)
GET /api/users/page/{page} Get users for a specific page

Example Responses

Get User by ID:

{
  "id": 2,
  "email": "[email protected]",
  "firstName": "Janet",
  "lastName": "Weaver",
  "avatar": "https://reqres.in/img/faces/2-image.jpg",
  "fullName": "Janet Weaver"
}

Get Users Page:

{
  "page": 1,
  "per_page": 6,
  "total": 12,
  "total_pages": 2,
  "data": [...],
  "has_next_page": true,
  "has_previous_page": false
}

Quick Start

Prerequisites

  • .NET 9.0 SDK or later (Download here)
  • IDE: Visual Studio 2022, VS Code, or JetBrains Rider
  • Internet Connection: Required for external API calls to reqres.in

Getting Started

1. Clone and Setup

# Clone the repository
git clone <repository-url>
cd RaftLabs

# Restore all packages
dotnet restore

# Build the entire solution
dotnet build

2. Run the Interactive Console Demo

# Navigate to console project
cd samples/RaftLabs.Console

# Run the demo (shows all features)
dotnet run

Expected Output:

=== RaftLabs User API Client Demo ===

Demo 1: Getting user with ID 2...
Found user: Janet Weaver ([email protected])

Demo 2: Getting users from page 1...
Page 1 of 2 (Total: 12 users)

Demo 3: Getting all users...
Retrieved 12 total users

Demo 4: Testing error handling...
Correctly handled non-existent user

Demo 5: Testing caching...
First call: 250ms, Second call: 0ms
   Cache working: Yes

3. Run the Web API

# From solution root
dotnet run --project src/RaftLabs.Presentation

# Or with specific URL
dotnet run --project src/RaftLabs.Presentation --urls "http://localhost:5000"

API will be available at:

  • Swagger UI: http://localhost:5000 (interactive documentation)
  • API Base: http://localhost:5000/api/users

4. Run All Unit Tests

# From solution root
dotnet test

# Expected output:
# Test run for RaftLabs.UnitTests.dll (.NET 9.0)
# Total tests: 11
# Passed: 11
# Failed: 0
# Skipped: 0

5. Test API Endpoints

# Get user by ID
curl http://localhost:5000/api/users/2

# Get all users (aggregated from all pages)  
curl http://localhost:5000/api/users

# Get specific page
curl http://localhost:5000/api/users/page/1

# Test error handling
curl http://localhost:5000/api/users/999

Build and Development

Build Commands

# Clean and rebuild entire solution
dotnet clean && dotnet build

# Build specific project
dotnet build src/RaftLabs.Infrastructure

# Build in Release mode
dotnet build --configuration Release

# Restore packages only
dotnet restore

Running Projects

# Run Web API (default: https://localhost:7042, http://localhost:5000)
dotnet run --project src/RaftLabs.Presentation

# Run Console Demo
dotnet run --project samples/RaftLabs.Console

# Run with specific profile
dotnet run --project src/RaftLabs.Presentation --launch-profile "https"

Testing Commands

# Run all tests
dotnet test

# Run tests with detailed output
dotnet test --verbosity normal

# Run tests with coverage (requires coverage tools)
dotnet test --collect:"XPlat Code Coverage"

# Run specific test class
dotnet test --filter "ExternalUserRepositoryTests"

# Run specific test method
dotnet test --filter "GetUserByIdAsync_WithValidUser_ReturnsUser"

Development Workflow

# 1. Make changes to code
# 2. Build to check for compilation errors
dotnet build

# 3. Run tests to ensure functionality
dotnet test

# 4. Test the console app
dotnet run --project samples/RaftLabs.Console

# 5. Test the API
dotnet run --project src/RaftLabs.Presentation

Configuration

API Settings (appsettings.json)

{
  "ApiSettings": {
    "BaseUrl": "https://reqres.in/api/",
    "ApiKey": "reqres-free-v1", 
    "TimeoutSeconds": 30,
    "RetryAttempts": 3,
    "RetryDelayMs": 1000,
    "CacheExpirationMinutes": 5
  }
}

Configuration Options

Setting Description Default Location
BaseUrl External API base URL https://reqres.in/api/ Both Console & API
ApiKey API key for authentication reqres-free-v1 HTTP Header x-api-key
TimeoutSeconds HTTP request timeout 30 HttpClient configuration
RetryAttempts Number of retry attempts 3 Polly retry policy
RetryDelayMs Delay between retries (ms) 1000 Exponential backoff base
CacheExpirationMinutes Cache expiration time 5 IMemoryCache settings

HttpClient Configuration

The solution uses named HttpClient registration with the Options pattern:

// Named client registration
builder.Services.AddHttpClient("ReqresApiClient");

// ApiClient uses IHttpClientFactory internally
public ApiClient(IHttpClientFactory httpClientFactory, ILogger<ApiClient> logger, IOptions<ApiSettings> apiSettings)
{
    _httpClient = httpClientFactory.CreateClient("ReqresApiClient");
    _httpClient.BaseAddress = new Uri(_apiSettings.BaseUrl);
    _httpClient.Timeout = TimeSpan.FromSeconds(_apiSettings.TimeoutSeconds);
    _httpClient.DefaultRequestHeaders.Add("x-api-key", _apiSettings.ApiKey);
}

Testing Strategy

Unit Test Coverage

The solution includes 11 comprehensive unit tests covering:

ExternalUserRepository Tests (6 tests)

  • GetUserByIdAsync_WithValidUser_ReturnsUser: Successful user retrieval
  • GetUserByIdAsync_WithNonExistentUser_ReturnsNull: 404 handling
  • GetUserByIdAsync_WithInvalidUserId_ThrowsValidationException: Input validation
  • GetUsersPageAsync_WithValidPage_ReturnsPagedResult: Pagination support
  • GetUsersPageAsync_WithInvalidPage_ThrowsValidationException: Page validation
  • Additional scenarios: Caching, error handling, mapping

UserService Tests (5 tests)

  • Service layer business logic: Coordination between repositories
  • Error propagation: Domain exception handling
  • Input validation: Parameter validation at service level
  • Logging verification: Structured logging validation
  • Mock verification: Dependency interaction verification

Test Architecture

// Clean mocking strategy using the generic IApiClient
public class ExternalUserRepositoryTests
{
    private readonly Mock<IApiClient> _mockApiClient;
    private readonly IMemoryCache _memoryCache;
    private readonly Mock<ILogger<ExternalUserRepository>> _mockLogger;
    private readonly ExternalUserRepository _repository;

    public ExternalUserRepositoryTests()
    {
        _mockApiClient = new Mock<IApiClient>();
        // ... setup with Options.Create(apiSettings)
        _repository = new ExternalUserRepository(_mockApiClient.Object, _memoryCache, _mockLogger.Object, options);
    }
}

Running Tests

Basic Test Execution

# Run all tests (11 tests)
dotnet test
# Expected: Total tests: 11, Passed: 11, Failed: 0

# Run with detailed output
dotnet test --verbosity normal

# Run with minimal output
dotnet test --verbosity quiet

Advanced Test Commands

# Run specific test class
dotnet test --filter "ExternalUserRepositoryTests"

# Run specific test method
dotnet test --filter "GetUserByIdAsync_WithValidUser_ReturnsUser"

# Run tests with logger output
dotnet test --logger "console;verbosity=detailed"

# Run tests and generate coverage report (if coverage tools installed)
dotnet test --collect:"XPlat Code Coverage"

Test Results Interpretation

Expected successful test run:
Test Run Successful.
Total tests: 11
     Passed: 11
     Failed: 0
    Skipped: 0
 Total time: ~2-3 seconds

If tests fail, check:
- Network connectivity (tests use mocked dependencies, but build needs packages)
- .NET 9.0 SDK installation
- NuGet package restoration (dotnet restore)

Test Categories

1. Unit Tests (Current - 11 tests)

  • Mock all external dependencies
  • Test business logic in isolation
  • Fast execution (< 3 seconds total)
  • No external API calls

2. Integration Tests (Future Enhancement)

  • Test with real HTTP calls to reqres.in
  • Validate end-to-end scenarios
  • Network dependency required

3. Performance Tests (Future Enhancement)

  • Cache performance validation
  • HTTP client connection pooling
  • Retry policy effectiveness

Development Decisions

Technology Choices

  1. Clean Architecture: Ensures maintainability, testability, and separation of concerns
  2. Generic Repository Pattern: ExternalRepositoryBase<T, TId, TExternal, TExternalPage> for reusability
  3. HttpClientFactory with Named Clients: Prevents socket exhaustion, enables proper logging
  4. Options Pattern: Strongly-typed configuration with IOptions<ApiSettings>
  5. Polly for Resilience: Industry-standard library for retry policies and circuit breakers
  6. Memory Caching: Reduces external API calls and improves performance
  7. Structured Logging: Enables comprehensive monitoring and debugging

Error Handling Strategy

  1. Domain Exceptions: Custom exceptions (NotFoundException, ValidationException, etc.)
  2. HTTP Error Mapping: Proper HTTP status codes for different scenarios
  3. Retry Logic: Exponential backoff for transient failures
  4. Global Exception Middleware: Centralized error handling in the API layer

Generic Architecture Benefits

// Easy to extend for new entity types
public class ProductRepository : ExternalRepositoryBase<Product, int, ExternalProductResponse, ExternalProductsResponse>
{
    protected override string EndpointName => "products";
    protected override string CacheKeyPrefix => "product";
    
    // Only need to implement mapping - everything else is inherited!
    protected override Product MapFromExternalResponse(ExternalProductResponse response) => response.Data.ToProduct();
    protected override PagedResult<Product> MapFromExternalPageResponse(ExternalProductsResponse response) => response.ToPagedResult();
}
  1. Retry Logic: Exponential backoff for transient failures
  2. Graceful Degradation: Fallback mechanisms for service unavailability

Performance Optimizations

  1. Async Throughout: Full asynchronous implementation
  2. HTTP Client Reuse: Efficient connection management
  3. Response Caching: Configurable in-memory caching
  4. Lazy Loading: Data fetched only when needed

Dependencies

Core Dependencies

  • Microsoft.Extensions.Http - HttpClientFactory and named client management
  • Microsoft.Extensions.Caching.Memory - In-memory caching with IMemoryCache
  • Microsoft.Extensions.Options - Options pattern for strongly-typed configuration
  • Polly.Extensions.Http - Retry policies and resilience patterns
  • Swashbuckle.AspNetCore - Swagger/OpenAPI documentation

Infrastructure Dependencies

  • System.Text.Json - JSON serialization/deserialization
  • Microsoft.Extensions.Logging - Structured logging throughout the application
  • Microsoft.Extensions.DependencyInjection - Dependency injection container

Test Dependencies

  • xUnit - Modern testing framework for .NET
  • Moq - Mocking framework for unit testing
  • Microsoft.Extensions.Logging.Abstractions - Logging abstractions for testing

Usage Examples

Console Application Demo

The included console application demonstrates all key features:

dotnet run --project samples/RaftLabs.Console

Features demonstrated:

  • Individual user retrieval with caching
  • Paginated data retrieval
  • Error handling for non-existent resources
  • Performance comparison (first call vs cached call)
  • Comprehensive logging output

Web API Usage

# Start the API
dotnet run --project src/RaftLabs.Presentation

# Test endpoints
curl http://localhost:5000/api/users              # Get all users
curl http://localhost:5000/api/users/2            # Get specific user  
curl http://localhost:5000/api/users/page/1       # Get users page
curl http://localhost:5000/api/users/999          # Test error handling (404)

Programmatic Usage

// Dependency injection setup (Program.cs)
builder.Services.Configure<ApiSettings>(builder.Configuration.GetSection("ApiSettings"));
builder.Services.AddHttpClient("ReqresApiClient");  
builder.Services.AddScoped<IApiClient, ApiClient>();
builder.Services.AddScoped<IUserRepository, ExternalUserRepository>();
builder.Services.AddScoped<IUserService, UserService>();

// Usage in your application
public class MyService
{
    private readonly IUserService _userService;

    public MyService(IUserService userService)
    {
        _userService = userService;
    }

    public async Task<UserDto> GetUserAsync(int id)
    {
        return await _userService.GetUserByIdAsync(id);
    }

    public async Task<PagedResult<UserDto>> GetUsersPageAsync(int page) 
    {
        return await _userService.GetUsersPageAsync(page);
    }
}

Extending for New Entity Types

// 1. Define domain entity
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// 2. Define external models  
public class ExternalProductResponse
{
    public ExternalProductModel Data { get; set; }
}

public class ExternalProductsResponse
{
    public List<ExternalProductModel> Data { get; set; }
    // ... pagination properties
}

// 3. Create repository (inherits all functionality!)
public class ProductRepository : ExternalRepositoryBase<Product, int, ExternalProductResponse, ExternalProductsResponse>, IProductRepository
{
    protected override string EndpointName => "products";
    protected override string CacheKeyPrefix => "product";
    
    public ProductRepository(IApiClient apiClient, IMemoryCache cache, ILogger<ProductRepository> logger, IOptions<ApiSettings> apiSettings)
        : base(apiClient, cache, logger, apiSettings) { }
    
    protected override Product MapFromExternalResponse(ExternalProductResponse response) => response.Data.ToProduct();
    protected override PagedResult<Product> MapFromExternalPageResponse(ExternalProductsResponse response) => response.ToPagedResult();
}

// 4. Register in DI container
builder.Services.AddScoped<IProductRepository, ProductRepository>();

Monitoring & Observability

Logging

The application provides structured logging at multiple levels:

  • Information: Successful operations, HTTP requests, cache hits/misses
  • Warning: Retry attempts, validation warnings, cache evictions
  • Error: Exceptions, service failures, network timeouts

Named HttpClient Logging:

info: System.Net.Http.HttpClient.ReqresApiClient.LogicalHandler[100]
      Start processing HTTP request GET https://reqres.in/api/users/2
info: System.Net.Http.HttpClient.ReqresApiClient.ClientHandler[101]  
      Received HTTP response headers after 245ms - 200

Performance Metrics

The console demo shows real performance data:

  • API Response Times: Actual HTTP call duration
  • Cache Effectiveness: "First call: 250ms, Second call: 0ms"
  • Retry Monitoring: Failed attempts and retry logic execution
  • Error Rates: Success/failure ratios by operation type

Monitoring Capabilities

  • HttpClient Connection Pooling: Automatic via HttpClientFactory
  • Request/Response Logging: Detailed HTTP operation tracking
  • Cache Hit Ratios: Memory cache performance metrics
  • Exception Tracking: Comprehensive error logging with context

Troubleshooting

Common Issues

Build Errors

# Clean and rebuild if you encounter build issues
dotnet clean
dotnet restore  
dotnet build

Test Failures

# Ensure all packages are restored
dotnet restore
dotnet test --verbosity normal

Network Issues (Console Demo)

# Verify network connectivity
curl https://reqres.in/api/users/1

# Check configuration in appsettings.json
cat samples/RaftLabs.Console/appsettings.json

Port Conflicts (Web API)

# Use different port if 5000 is occupied
dotnet run --project src/RaftLabs.Presentation --urls "http://localhost:8080"

Debug Information

  • Solution: Uses .NET 9.0 target framework
  • Dependencies: All packages compatible with .NET 9.0
  • Architecture: Clean Architecture with generic patterns
  • Test Framework: xUnit with Moq for mocking
  • HTTP Client: Named client with HttpClientFactory

Performance Characteristics

Response Times (Console Demo Results)

  • Cold Start: ~250ms (first API call)
  • Cached Response: ~0ms (subsequent identical calls)
  • Page Retrieval: ~20-50ms (typical API response)
  • Error Handling: ~500ms (network timeout scenarios)

Memory Usage

  • Startup: Minimal footprint with DI container
  • Caching: Configurable memory cache (5-minute default expiration)
  • HTTP Connections: Pooled and reused via HttpClientFactory

Scalability Features

  • Connection Pooling: Automatic via HttpClientFactory
  • Async Operations: Full async/await throughout
  • Generic Design: Easy horizontal scaling for new entity types
  • Stateless Design: API can be deployed across multiple instances

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add/update tests
  5. Ensure all tests pass
  6. Submit a pull request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contact

For questions or support, please contact:


Built with ❤️ using .NET 9.0 and Clean Architecture principles

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages