.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:
| Property | Description | Example |
|---|---|---|
org-name | Organization name for namespacing | afi, cpd, a1p |
solution-name | Solution name for grouping | apps, xyz |
prefix | Domain prefix for service names | invoice, 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
-
Generate the gateway:
archetect render git@github.com:p6m-archetypes/dotnet-rest-domain-gateway.archetype.git -
Configure services:
# Update appsettings.json with your gRPC service endpoints
# Configure authentication settings -
Run locally:
docker-compose up -d
dotnet run --project src/DomainGateway.Api -
Access Swagger UI:
http://localhost:8080/swagger -
Test authentication:
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}' -
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