Skip to main content

.NET REST Domain Gateway

A production-ready .NET REST domain gateway archetype that provides centralized API management, authentication, service orchestration, and domain-specific business logic aggregation.

Overview

This archetype generates a sophisticated domain gateway that acts as an intermediary between client applications and backend microservices. It provides authentication, authorization, request routing, data aggregation, and domain-specific business logic in a single, cohesive API surface.

Technology Stack

  • .NET 8: Latest long-term support version
  • ASP.NET Core: Web API framework
  • OpenAPI/Swagger: API documentation and testing
  • Swashbuckle: OpenAPI implementation for .NET
  • gRPC: Backend service communication
  • Authentication: JWT and OAuth 2.0 support
  • OpenTelemetry: Distributed tracing and monitoring
  • Serilog: Structured logging
  • Docker: Containerization
  • Kubernetes: Container orchestration

Key Features

Core Functionality

  • Domain-Specific APIs: Aggregated APIs tailored to domain needs
  • Service Orchestration: Coordinate multiple backend services
  • Data Transformation: Convert between external and internal formats
  • Business Logic: Domain-specific business rules and validation
  • Error Handling: Centralized error management and reporting

Security Features

  • Authentication: JWT token validation and OAuth 2.0 integration
  • Authorization: Role-based and policy-based access control
  • API Rate Limiting: Request throttling and quota management
  • Security Headers: CORS, HSTS, and security-focused middleware
  • Input Validation: Comprehensive request validation

Integration Features

  • gRPC Client Integration: Efficient backend service communication
  • Circuit Breaker: Fault tolerance for downstream services
  • Load Balancing: Client-side load balancing for gRPC services
  • Retry Policies: Automatic retry with exponential backoff
  • Health Checks: Service dependency health monitoring

Observability

  • OpenTelemetry: Distributed tracing across service boundaries
  • Structured Logging: Comprehensive request and response logging
  • Metrics Collection: Performance and business metrics
  • Health Endpoints: Service and dependency health reporting

Project Structure

dotnet-rest-domain-gateway/
├── src/
│ ├── DomainGateway.Api/ # Main gateway application
│ │ ├── Controllers/ # REST API controllers
│ │ │ ├── AuthController.cs # Authentication endpoints
│ │ │ ├── UsersController.cs # User management
│ │ │ └── OrdersController.cs # Order management
│ │ ├── Models/ # API models and DTOs
│ │ │ ├── Requests/ # Request models
│ │ │ ├── Responses/ # Response models
│ │ │ └── Shared/ # Shared models
│ │ ├── Services/ # Domain services
│ │ │ ├── IUserService.cs # User service interface
│ │ │ ├── UserService.cs # User service implementation
│ │ │ └── OrderService.cs # Order service implementation
│ │ ├── Clients/ # gRPC client wrappers
│ │ │ ├── UserServiceClient.cs # User service client
│ │ │ └── OrderServiceClient.cs# Order service client
│ │ ├── Configuration/ # Configuration classes
│ │ │ ├── AuthOptions.cs # Authentication configuration
│ │ │ ├── GrpcOptions.cs # gRPC client configuration
│ │ │ └── ServiceOptions.cs # Service configuration
│ │ ├── Middleware/ # Custom middleware
│ │ │ ├── AuthenticationMiddleware.cs
│ │ │ ├── ErrorHandlingMiddleware.cs
│ │ │ └── RequestLoggingMiddleware.cs
│ │ ├── Extensions/ # Extension methods
│ │ │ ├── ServiceCollectionExtensions.cs
│ │ │ └── ApplicationBuilderExtensions.cs
│ │ ├── Health/ # Health check implementations
│ │ │ ├── GrpcHealthCheck.cs # gRPC service health checks
│ │ │ └── DatabaseHealthCheck.cs
│ │ └── Program.cs # Application entry point
│ ├── DomainGateway.Core/ # Core business logic
│ │ ├── Models/ # Domain models
│ │ ├── Services/ # Business services
│ │ ├── Interfaces/ # Service contracts
│ │ ├── Validators/ # Business rule validation
│ │ └── Exceptions/ # Custom exceptions
│ └── DomainGateway.Infrastructure/ # Infrastructure concerns
│ ├── gRPC/ # gRPC client implementations
│ ├── Http/ # HTTP client implementations
│ ├── Configuration/ # Configuration providers
│ └── Monitoring/ # Telemetry and monitoring
├── tests/
│ ├── DomainGateway.Api.Tests/ # API integration tests
│ ├── DomainGateway.Core.Tests/ # Unit tests
│ └── DomainGateway.IntegrationTests/ # End-to-end tests
├── k8s/ # Kubernetes manifests
│ ├── deployment.yaml # Service deployment
│ ├── service.yaml # Service definition
│ ├── configmap.yaml # Configuration
│ └── ingress.yaml # Ingress rules
├── scripts/ # Build and deployment scripts
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Local development stack
└── README.md # Project documentation

Configuration & Prompts

During archetype generation, you'll be prompted for:

PropertyDescriptionExample
org-nameOrganization name for namespacingafi, cpd, a1p
solution-nameSolution name for groupingapps, xyz
prefixDomain prefix for service namesinvoice, order, booking

Domain Gateway Implementation

Authentication Controller

[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
private readonly ILogger<AuthController> _logger;

public AuthController(IAuthService authService, ILogger<AuthController> logger)
{
_authService = authService;
_logger = logger;
}

/// <summary>
/// Authenticate user and return JWT token
/// </summary>
/// <param name="request">Login credentials</param>
/// <returns>Authentication result with JWT token</returns>
[HttpPost("login")]
[ProducesResponseType(typeof(AuthenticationResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<AuthenticationResponse>> Login([FromBody] LoginRequest request)
{
try
{
_logger.LogInformation("Authentication attempt for user: {Username}", request.Username);

var result = await _authService.AuthenticateAsync(request.Username, request.Password);

if (!result.IsSuccess)
{
_logger.LogWarning("Authentication failed for user: {Username}", request.Username);
return Unauthorized(new ErrorResponse("Invalid credentials"));
}

_logger.LogInformation("Authentication successful for user: {Username}", request.Username);

return Ok(new AuthenticationResponse
{
Token = result.Token,
ExpiresAt = result.ExpiresAt,
User = new UserSummary
{
Id = result.User.Id,
Username = result.User.Username,
Email = result.User.Email,
Roles = result.User.Roles
}
});
}
catch (ValidationException ex)
{
_logger.LogWarning("Validation error during authentication: {Message}", ex.Message);
return BadRequest(new ErrorResponse(ex.Message));
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error during authentication for user: {Username}", request.Username);
return StatusCode(500, new ErrorResponse("An unexpected error occurred"));
}
}

/// <summary>
/// Refresh authentication token
/// </summary>
/// <param name="request">Refresh token request</param>
/// <returns>New authentication token</returns>
[HttpPost("refresh")]
[ProducesResponseType(typeof(AuthenticationResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<AuthenticationResponse>> RefreshToken([FromBody] RefreshTokenRequest request)
{
try
{
var result = await _authService.RefreshTokenAsync(request.RefreshToken);

if (!result.IsSuccess)
{
return Unauthorized(new ErrorResponse("Invalid refresh token"));
}

return Ok(new AuthenticationResponse
{
Token = result.Token,
ExpiresAt = result.ExpiresAt,
User = new UserSummary
{
Id = result.User.Id,
Username = result.User.Username,
Email = result.User.Email,
Roles = result.User.Roles
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error refreshing token");
return StatusCode(500, new ErrorResponse("An unexpected error occurred"));
}
}

/// <summary>
/// Logout and invalidate token
/// </summary>
/// <returns>Logout confirmation</returns>
[HttpPost("logout")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> Logout()
{
try
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (!string.IsNullOrEmpty(userId))
{
await _authService.LogoutAsync(userId);
}

return Ok(new { message = "Logged out successfully" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during logout");
return StatusCode(500, new ErrorResponse("An unexpected error occurred"));
}
}
}

Domain Service Implementation

public interface IUserService
{
Task<PagedResult<UserDto>> GetUsersAsync(UserQueryParameters parameters);
Task<UserDto> GetUserByIdAsync(int id);
Task<UserDto> CreateUserAsync(CreateUserRequest request);
Task<UserDto> UpdateUserAsync(int id, UpdateUserRequest request);
Task<bool> DeleteUserAsync(int id);
Task<UserProfileDto> GetUserProfileAsync(int id);
Task<UserProfileDto> UpdateUserProfileAsync(int id, UpdateUserProfileRequest request);
}

public class UserService : IUserService
{
private readonly UserServiceClient _userServiceClient;
private readonly ILogger<UserService> _logger;
private readonly IMapper _mapper;

public UserService(
UserServiceClient userServiceClient,
ILogger<UserService> logger,
IMapper mapper)
{
_userServiceClient = userServiceClient;
_logger = logger;
_mapper = mapper;
}

public async Task<PagedResult<UserDto>> GetUsersAsync(UserQueryParameters parameters)
{
try
{
using var activity = DomainGatewayActivitySource.StartActivity("UserService.GetUsers");
activity?.SetTag("page", parameters.Page);
activity?.SetTag("pageSize", parameters.PageSize);

_logger.LogInformation("Fetching users with parameters: {@Parameters}", parameters);

var grpcRequest = new GetUsersRequest
{
Page = parameters.Page,
PageSize = parameters.PageSize,
Filter = parameters.Filter ?? string.Empty,
SortBy = parameters.SortBy ?? "Id",
SortDirection = parameters.SortDirection ?? "asc"
};

var grpcResponse = await _userServiceClient.GetUsersAsync(grpcRequest);

var users = grpcResponse.Users.Select(_mapper.Map<UserDto>).ToList();

var result = new PagedResult<UserDto>
{
Items = users,
TotalCount = grpcResponse.TotalCount,
Page = parameters.Page,
PageSize = parameters.PageSize,
TotalPages = (int)Math.Ceiling((double)grpcResponse.TotalCount / parameters.PageSize)
};

_logger.LogInformation("Successfully fetched {Count} users", users.Count);
return result;
}
catch (RpcException ex)
{
_logger.LogError(ex, "gRPC error fetching users: {Status} - {Detail}", ex.Status.StatusCode, ex.Status.Detail);
throw new ServiceException($"Error communicating with user service: {ex.Status.Detail}", ex);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error fetching users");
throw;
}
}

public async Task<UserDto> GetUserByIdAsync(int id)
{
try
{
using var activity = DomainGatewayActivitySource.StartActivity("UserService.GetUserById");
activity?.SetTag("userId", id);

_logger.LogInformation("Fetching user with ID: {UserId}", id);

var grpcRequest = new GetUserByIdRequest { Id = id };
var grpcResponse = await _userServiceClient.GetUserByIdAsync(grpcRequest);

if (grpcResponse.User == null)
{
_logger.LogWarning("User not found with ID: {UserId}", id);
throw new NotFoundException($"User with ID {id} not found");
}

var user = _mapper.Map<UserDto>(grpcResponse.User);

_logger.LogInformation("Successfully fetched user: {UserId}", id);
return user;
}
catch (RpcException ex) when (ex.Status.StatusCode == StatusCode.NotFound)
{
_logger.LogWarning("User not found with ID: {UserId}", id);
throw new NotFoundException($"User with ID {id} not found");
}
catch (RpcException ex)
{
_logger.LogError(ex, "gRPC error fetching user {UserId}: {Status} - {Detail}", id, ex.Status.StatusCode, ex.Status.Detail);
throw new ServiceException($"Error communicating with user service: {ex.Status.Detail}", ex);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error fetching user {UserId}", id);
throw;
}
}

public async Task<UserDto> CreateUserAsync(CreateUserRequest request)
{
try
{
using var activity = DomainGatewayActivitySource.StartActivity("UserService.CreateUser");
activity?.SetTag("username", request.Username);

_logger.LogInformation("Creating user: {Username}", request.Username);

// Validate business rules
await ValidateCreateUserRequest(request);

var grpcRequest = new CreateUserGrpcRequest
{
Username = request.Username,
Email = request.Email,
FirstName = request.FirstName,
LastName = request.LastName,
Password = request.Password
};

var grpcResponse = await _userServiceClient.CreateUserAsync(grpcRequest);
var user = _mapper.Map<UserDto>(grpcResponse.User);

_logger.LogInformation("Successfully created user: {UserId} - {Username}", user.Id, user.Username);
return user;
}
catch (RpcException ex) when (ex.Status.StatusCode == StatusCode.AlreadyExists)
{
_logger.LogWarning("User already exists: {Username}", request.Username);
throw new ConflictException($"User with username '{request.Username}' already exists");
}
catch (RpcException ex)
{
_logger.LogError(ex, "gRPC error creating user: {Status} - {Detail}", ex.Status.StatusCode, ex.Status.Detail);
throw new ServiceException($"Error communicating with user service: {ex.Status.Detail}", ex);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error creating user");
throw;
}
}

private async Task ValidateCreateUserRequest(CreateUserRequest request)
{
// Domain-specific business rule validation
if (request.Username.Length < 3)
{
throw new ValidationException("Username must be at least 3 characters long");
}

if (!IsValidEmail(request.Email))
{
throw new ValidationException("Invalid email format");
}

// Check if username is already taken (additional business logic)
try
{
await GetUserByUsernameAsync(request.Username);
throw new ConflictException($"Username '{request.Username}' is already taken");
}
catch (NotFoundException)
{
// Username is available - this is expected
}
}

private async Task<UserDto> GetUserByUsernameAsync(string username)
{
var grpcRequest = new GetUserByUsernameRequest { Username = username };
var grpcResponse = await _userServiceClient.GetUserByUsernameAsync(grpcRequest);

if (grpcResponse.User == null)
{
throw new NotFoundException($"User with username '{username}' not found");
}

return _mapper.Map<UserDto>(grpcResponse.User);
}

private static bool IsValidEmail(string email)
{
try
{
var mailAddress = new System.Net.Mail.MailAddress(email);
return mailAddress.Address == email;
}
catch
{
return false;
}
}
}

gRPC Client Configuration

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddGrpcClients(this IServiceCollection services, IConfiguration configuration)
{
var grpcOptions = configuration.GetSection("GrpcServices").Get<GrpcServicesOptions>();

// User Service Client
services.AddGrpcClient<UserService.UserServiceClient>(options =>
{
options.Address = new Uri(grpcOptions.UserService.Address);
})
.ConfigureChannel(options =>
{
options.HttpHandler = CreateGrpcHttpHandler();
options.ServiceConfig = new ServiceConfig
{
MethodConfigs =
{
new MethodConfig
{
Names = { MethodName.Default },
RetryPolicy = new RetryPolicy
{
MaxAttempts = 3,
InitialBackoff = TimeSpan.FromSeconds(1),
MaxBackoff = TimeSpan.FromSeconds(5),
BackoffMultiplier = 1.5,
RetryableStatusCodes = { StatusCode.Unavailable, StatusCode.DeadlineExceeded }
}
}
}
};
})
.AddInterceptor<LoggingInterceptor>()
.AddInterceptor<AuthenticationInterceptor>();

// Order Service Client
services.AddGrpcClient<OrderService.OrderServiceClient>(options =>
{
options.Address = new Uri(grpcOptions.OrderService.Address);
})
.ConfigureChannel(options =>
{
options.HttpHandler = CreateGrpcHttpHandler();
})
.AddInterceptor<LoggingInterceptor>()
.AddInterceptor<AuthenticationInterceptor>();

return services;
}

private static HttpMessageHandler CreateGrpcHttpHandler()
{
return new SocketsHttpHandler
{
PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
KeepAlivePingDelay = TimeSpan.FromSeconds(60),
KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
EnableMultipleHttp2Connections = true
};
}
}

public class GrpcServicesOptions
{
public GrpcServiceOptions UserService { get; set; } = new();
public GrpcServiceOptions OrderService { get; set; } = new();
}

public class GrpcServiceOptions
{
public string Address { get; set; } = string.Empty;
public int TimeoutSeconds { get; set; } = 30;
public bool EnableRetry { get; set; } = true;
}

Error Handling Middleware

public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ErrorHandlingMiddleware> _logger;

public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}

public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception occurred");
await HandleExceptionAsync(context, ex);
}
}

private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";

var response = exception switch
{
NotFoundException ex => new ErrorResponse
{
Message = ex.Message,
StatusCode = 404,
TraceId = Activity.Current?.Id
},
ValidationException ex => new ErrorResponse
{
Message = ex.Message,
StatusCode = 400,
TraceId = Activity.Current?.Id,
Details = ex.Errors?.ToList()
},
ConflictException ex => new ErrorResponse
{
Message = ex.Message,
StatusCode = 409,
TraceId = Activity.Current?.Id
},
UnauthorizedException ex => new ErrorResponse
{
Message = ex.Message,
StatusCode = 401,
TraceId = Activity.Current?.Id
},
ForbiddenException ex => new ErrorResponse
{
Message = ex.Message,
StatusCode = 403,
TraceId = Activity.Current?.Id
},
ServiceException ex => new ErrorResponse
{
Message = "A service error occurred",
StatusCode = 503,
TraceId = Activity.Current?.Id,
Details = new List<string> { ex.Message }
},
_ => new ErrorResponse
{
Message = "An unexpected error occurred",
StatusCode = 500,
TraceId = Activity.Current?.Id
}
};

context.Response.StatusCode = response.StatusCode;

var jsonResponse = JsonSerializer.Serialize(response, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});

await context.Response.WriteAsync(jsonResponse);
}
}

public class ErrorResponse
{
public string Message { get; set; } = string.Empty;
public int StatusCode { get; set; }
public string? TraceId { get; set; }
public List<string>? Details { get; set; }
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}

Authentication & Authorization

public static class AuthenticationExtensions
{
public static IServiceCollection AddDomainAuthentication(this IServiceCollection services, IConfiguration configuration)
{
var authOptions = configuration.GetSection("Authentication").Get<AuthenticationOptions>();

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = authOptions.Issuer,
ValidateAudience = true,
ValidAudience = authOptions.Audience,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authOptions.SecretKey)),
ClockSkew = TimeSpan.FromMinutes(5)
};

options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// Support token from query string (for SignalR)
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;

if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs"))
{
context.Token = accessToken;
}

return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});

services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Administrator"));

options.AddPolicy("UserManagement", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim("permission", "user.read") ||
context.User.HasClaim("permission", "user.write")));
});

return services;
}
}

[Authorize(Policy = "UserManagement")]
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
// Implementation with authorization
}

Testing Strategy

Integration Tests

public class UsersControllerIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly HttpClient _client;

public UsersControllerIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
// Replace gRPC clients with test doubles
services.AddSingleton<UserService.UserServiceClient>(provider =>
{
var mockClient = new Mock<UserService.UserServiceClient>();
ConfigureMockUserServiceClient(mockClient);
return mockClient.Object;
});
});
});

_client = _factory.CreateClient();
}

[Fact]
public async Task GetUsers_ReturnsUsers_WhenAuthenticated()
{
// Arrange
var token = await GetAuthTokenAsync();
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

// Act
var response = await _client.GetAsync("/api/users");

// Assert
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<PagedResult<UserDto>>(content);

Assert.NotNull(result);
Assert.True(result.Items.Any());
}

[Fact]
public async Task CreateUser_ReturnsCreatedUser_WhenValidRequest()
{
// Arrange
var token = await GetAuthTokenAsync();
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

var request = new CreateUserRequest
{
Username = "testuser",
Email = "test@example.com",
FirstName = "Test",
LastName = "User",
Password = "SecurePassword123!"
};

// Act
var response = await _client.PostAsJsonAsync("/api/users", request);

// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.Created, response.StatusCode);

var content = await response.Content.ReadAsStringAsync();
var user = JsonSerializer.Deserialize<UserDto>(content);

Assert.NotNull(user);
Assert.Equal(request.Username, user.Username);
Assert.Equal(request.Email, user.Email);
}

private async Task<string> GetAuthTokenAsync()
{
var loginRequest = new LoginRequest
{
Username = "testuser",
Password = "testpassword"
};

var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
response.EnsureSuccessStatusCode();

var content = await response.Content.ReadAsStringAsync();
var authResponse = JsonSerializer.Deserialize<AuthenticationResponse>(content);

return authResponse.Token;
}

private static void ConfigureMockUserServiceClient(Mock<UserService.UserServiceClient> mockClient)
{
mockClient.Setup(c => c.GetUsersAsync(It.IsAny<GetUsersRequest>(), null, null, default))
.ReturnsAsync(new GetUsersResponse
{
Users = { CreateTestGrpcUser() },
TotalCount = 1
});

mockClient.Setup(c => c.CreateUserAsync(It.IsAny<CreateUserGrpcRequest>(), null, null, default))
.ReturnsAsync(new CreateUserResponse
{
User = CreateTestGrpcUser()
});
}

private static GrpcUser CreateTestGrpcUser()
{
return new GrpcUser
{
Id = 1,
Username = "testuser",
Email = "test@example.com",
FirstName = "Test",
LastName = "User",
CreatedAt = Timestamp.FromDateTime(DateTime.UtcNow)
};
}
}

Deployment

Docker Configuration

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080
EXPOSE 8081

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["src/DomainGateway.Api/DomainGateway.Api.csproj", "src/DomainGateway.Api/"]
COPY ["src/DomainGateway.Core/DomainGateway.Core.csproj", "src/DomainGateway.Core/"]
COPY ["src/DomainGateway.Infrastructure/DomainGateway.Infrastructure.csproj", "src/DomainGateway.Infrastructure/"]
RUN dotnet restore "src/DomainGateway.Api/DomainGateway.Api.csproj"
COPY . .
WORKDIR "/src/src/DomainGateway.Api"
RUN dotnet build "DomainGateway.Api.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "DomainGateway.Api.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DomainGateway.Api.dll"]

Kubernetes Configuration

apiVersion: apps/v1
kind: Deployment
metadata:
name: domain-gateway
labels:
app: domain-gateway
spec:
replicas: 3
selector:
matchLabels:
app: domain-gateway
template:
metadata:
labels:
app: domain-gateway
spec:
containers:
- name: domain-gateway
image: your-registry/domain-gateway:latest
ports:
- containerPort: 8080
- containerPort: 8081
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: Authentication__SecretKey
valueFrom:
secretKeyRef:
name: auth-secret
key: jwt-secret
- name: GrpcServices__UserService__Address
value: "http://user-service:5000"
- name: GrpcServices__OrderService__Address
value: "http://order-service:5000"
livenessProbe:
httpGet:
path: /health
port: 8081
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 8081
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: domain-gateway
spec:
selector:
app: domain-gateway
ports:
- name: http
port: 80
targetPort: 8080
- name: management
port: 8081
targetPort: 8081
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: domain-gateway-ingress
spec:
rules:
- host: api.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: domain-gateway
port:
number: 80

Quick Start

  1. Generate the gateway:

    archetect render git@github.com:p6m-archetypes/dotnet-rest-domain-gateway.archetype.git
  2. Configure services:

    # Update appsettings.json with your gRPC service endpoints
    # Configure authentication settings
  3. Run locally:

    docker-compose up -d
    dotnet run --project src/DomainGateway.Api
  4. Access Swagger UI:

    http://localhost:8080/swagger
  5. Test authentication:

    curl -X POST http://localhost:8080/api/auth/login \
    -H "Content-Type: application/json" \
    -d '{"username":"admin","password":"password"}'
  6. Run tests:

    dotnet test

Best Practices

API Design

  • Use consistent naming conventions
  • Implement proper versioning
  • Provide comprehensive documentation
  • Use appropriate HTTP status codes

Security

  • Always validate inputs
  • Implement proper authentication
  • Use HTTPS in production
  • Follow OWASP guidelines

Performance

  • Implement caching strategies
  • Use connection pooling
  • Monitor response times
  • Optimize data transfer

Monitoring

  • Track all API calls
  • Monitor downstream services
  • Set up alerting
  • Use distributed tracing