@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
1,502 lines (1,258 loc) • 61.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.grpcServiceTemplate = void 0;
exports.grpcServiceTemplate = {
id: 'grpc-service',
name: 'grpc-service',
displayName: 'gRPC Service',
description: 'High-performance gRPC service with Protocol Buffers',
language: 'csharp',
framework: 'grpc',
version: '1.0.0',
tags: ['grpc', 'protobuf', 'microservice', 'api'],
port: 5000,
dependencies: {},
features: ['logging', 'monitoring', 'testing', 'security'],
files: {
// Project file
'{{serviceName}}.csproj': `<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Protobuf Include="Protos\\{{serviceName}}.proto" GrpcServices="Server" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.59.0" />
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" Version="2.59.0" />
<PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="FluentValidation" Version="11.8.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
</ItemGroup>
</Project>`,
// Protocol Buffer definitions
'Protos/{{serviceName}}.proto': `syntax = "proto3";
option csharp_namespace = "{{serviceName}}.Protos";
package {{serviceName}};
// User service definition
service UserService {
// Gets a user by ID
rpc GetUser (GetUserRequest) returns (UserReply);
// Creates a new user
rpc CreateUser (CreateUserRequest) returns (UserReply);
// Updates an existing user
rpc UpdateUser (UpdateUserRequest) returns (UserReply);
// Deletes a user
rpc DeleteUser (DeleteUserRequest) returns (DeleteUserReply);
// Lists users with pagination
rpc ListUsers (ListUsersRequest) returns (ListUsersReply);
// Server streaming: Watch user changes
rpc WatchUsers (WatchUsersRequest) returns (stream UserChangeNotification);
}
// Product service definition
service ProductService {
// Gets a product by ID
rpc GetProduct (GetProductRequest) returns (ProductReply);
// Creates a new product
rpc CreateProduct (CreateProductRequest) returns (ProductReply);
// Updates an existing product
rpc UpdateProduct (UpdateProductRequest) returns (ProductReply);
// Deletes a product
rpc DeleteProduct (DeleteProductRequest) returns (DeleteProductReply);
// Lists products with pagination and filtering
rpc ListProducts (ListProductsRequest) returns (ListProductsReply);
// Client streaming: Batch create products
rpc BatchCreateProducts (stream CreateProductRequest) returns (BatchCreateProductsReply);
// Bidirectional streaming: Real-time product updates
rpc StreamProductUpdates (stream ProductUpdateRequest) returns (stream ProductUpdateReply);
}
// User messages
message GetUserRequest {
int32 id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
string phone = 3;
repeated string roles = 4;
}
message UpdateUserRequest {
int32 id = 1;
string name = 2;
string email = 3;
string phone = 4;
repeated string roles = 5;
}
message DeleteUserRequest {
int32 id = 1;
}
message DeleteUserReply {
bool success = 1;
string message = 2;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
string filter = 3;
string order_by = 4;
}
message ListUsersReply {
repeated UserReply users = 1;
string next_page_token = 2;
int32 total_count = 3;
}
message WatchUsersRequest {
repeated string user_ids = 1;
}
message UserChangeNotification {
ChangeType change_type = 1;
UserReply user = 2;
enum ChangeType {
UNKNOWN = 0;
CREATED = 1;
UPDATED = 2;
DELETED = 3;
}
}
message UserReply {
int32 id = 1;
string name = 2;
string email = 3;
string phone = 4;
repeated string roles = 5;
google.protobuf.Timestamp created_at = 6;
google.protobuf.Timestamp updated_at = 7;
}
// Product messages
message GetProductRequest {
int32 id = 1;
}
message CreateProductRequest {
string name = 1;
string description = 2;
double price = 3;
string category = 4;
int32 stock_quantity = 5;
repeated string tags = 6;
}
message UpdateProductRequest {
int32 id = 1;
string name = 2;
string description = 3;
double price = 4;
string category = 5;
int32 stock_quantity = 6;
repeated string tags = 7;
}
message DeleteProductRequest {
int32 id = 1;
}
message DeleteProductReply {
bool success = 1;
string message = 2;
}
message ListProductsRequest {
int32 page_size = 1;
string page_token = 2;
string category_filter = 3;
double min_price = 4;
double max_price = 5;
string search_query = 6;
string order_by = 7;
}
message ListProductsReply {
repeated ProductReply products = 1;
string next_page_token = 2;
int32 total_count = 3;
}
message BatchCreateProductsReply {
repeated ProductReply products = 1;
int32 success_count = 2;
int32 error_count = 3;
repeated string errors = 4;
}
message ProductUpdateRequest {
int32 product_id = 1;
string field = 2;
string value = 3;
}
message ProductUpdateReply {
bool success = 1;
string message = 2;
ProductReply product = 3;
}
message ProductReply {
int32 id = 1;
string name = 2;
string description = 3;
double price = 4;
string category = 5;
int32 stock_quantity = 6;
repeated string tags = 7;
google.protobuf.Timestamp created_at = 8;
google.protobuf.Timestamp updated_at = 9;
}
// Import well-known types
import "google/protobuf/timestamp.proto";`,
// Program.cs
'Program.cs': `using {{serviceName}}.Services;
using {{serviceName}}.Data;
using {{serviceName}}.Interceptors;
using Microsoft.EntityFrameworkCore;
using Serilog;
using AutoMapper;
using FluentValidation;
var builder = WebApplication.CreateBuilder(args);
// Configure Serilog
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
builder.Host.UseSerilog();
// Add services to the container
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Add gRPC services
builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<LoggingInterceptor>();
options.Interceptors.Add<ExceptionInterceptor>();
options.EnableDetailedErrors = builder.Environment.IsDevelopment();
});
// Add gRPC reflection (for development)
if (builder.Environment.IsDevelopment())
{
builder.Services.AddGrpcReflection();
}
// Add AutoMapper
builder.Services.AddAutoMapper(typeof(Program));
// Add FluentValidation
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
// Add application services
builder.Services.AddScoped<IUserDataService, UserDataService>();
builder.Services.AddScoped<IProductDataService, ProductDataService>();
// Add health checks
builder.Services.AddHealthChecks()
.AddDbContextCheck<ApplicationDbContext>();
var app = builder.Build();
// Configure the HTTP request pipeline
app.MapGrpcService<UserGrpcService>();
app.MapGrpcService<ProductGrpcService>();
// Add gRPC reflection (for development)
if (app.Environment.IsDevelopment())
{
app.MapGrpcReflectionService();
}
// Add health check endpoint
app.MapHealthChecks("/health");
// Default HTTP endpoint
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
// Ensure database is created
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.Database.EnsureCreated();
}
app.Run();`,
// Models
'Models/User.cs': `using System.ComponentModel.DataAnnotations;
namespace {{serviceName}}.Models;
public class User
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; } = string.Empty;
[Required]
[EmailAddress]
[MaxLength(200)]
public string Email { get; set; } = string.Empty;
[Phone]
[MaxLength(20)]
public string? Phone { get; set; }
public List<string> Roles { get; set; } = new();
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}`,
'Models/Product.cs': `using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace {{serviceName}}.Models;
public class Product
{
public int Id { get; set; }
[Required]
[MaxLength(200)]
public string Name { get; set; } = string.Empty;
[MaxLength(1000)]
public string? Description { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
[MaxLength(100)]
public string Category { get; set; } = string.Empty;
public int StockQuantity { get; set; }
public List<string> Tags { get; set; } = new();
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}`,
// Data
'Data/ApplicationDbContext.cs': `using Microsoft.EntityFrameworkCore;
using {{serviceName}}.Models;
using System.Text.Json;
namespace {{serviceName}}.Data;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Configure User entity
modelBuilder.Entity<User>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(100);
entity.Property(e => e.Email).IsRequired().HasMaxLength(200);
entity.Property(e => e.Phone).HasMaxLength(20);
entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
entity.Property(e => e.UpdatedAt).HasDefaultValueSql("GETUTCDATE()");
// Store roles as JSON
entity.Property(e => e.Roles)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions?)null) ?? new List<string>())
.HasColumnType("nvarchar(max)");
entity.HasIndex(e => e.Email).IsUnique();
});
// Configure Product entity
modelBuilder.Entity<Product>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
entity.Property(e => e.Description).HasMaxLength(1000);
entity.Property(e => e.Price).HasColumnType("decimal(18,2)");
entity.Property(e => e.Category).IsRequired().HasMaxLength(100);
entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
entity.Property(e => e.UpdatedAt).HasDefaultValueSql("GETUTCDATE()");
// Store tags as JSON
entity.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions?)null) ?? new List<string>())
.HasColumnType("nvarchar(max)");
entity.HasIndex(e => e.Category);
entity.HasIndex(e => e.Price);
});
}
}`,
// Services
'Services/IUserDataService.cs': `using {{serviceName}}.Models;
namespace {{serviceName}}.Services;
public interface IUserDataService
{
Task<User?> GetUserByIdAsync(int id);
Task<User> CreateUserAsync(User user);
Task<User> UpdateUserAsync(User user);
Task<bool> DeleteUserAsync(int id);
Task<(List<User> Users, int TotalCount)> GetUsersAsync(int pageSize, int skip, string? filter = null, string? orderBy = null);
}`,
'Services/UserDataService.cs': `using Microsoft.EntityFrameworkCore;
using {{serviceName}}.Data;
using {{serviceName}}.Models;
namespace {{serviceName}}.Services;
public class UserDataService : IUserDataService
{
private readonly ApplicationDbContext _context;
private readonly ILogger<UserDataService> _logger;
public UserDataService(ApplicationDbContext context, ILogger<UserDataService> logger)
{
_context = context;
_logger = logger;
}
public async Task<User?> GetUserByIdAsync(int id)
{
try
{
return await _context.Users.FindAsync(id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving user with ID: {UserId}", id);
throw;
}
}
public async Task<User> CreateUserAsync(User user)
{
try
{
user.CreatedAt = DateTime.UtcNow;
user.UpdatedAt = DateTime.UtcNow;
_context.Users.Add(user);
await _context.SaveChangesAsync();
_logger.LogInformation("Created user with ID: {UserId}", user.Id);
return user;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating user: {UserEmail}", user.Email);
throw;
}
}
public async Task<User> UpdateUserAsync(User user)
{
try
{
user.UpdatedAt = DateTime.UtcNow;
_context.Users.Update(user);
await _context.SaveChangesAsync();
_logger.LogInformation("Updated user with ID: {UserId}", user.Id);
return user;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating user with ID: {UserId}", user.Id);
throw;
}
}
public async Task<bool> DeleteUserAsync(int id)
{
try
{
var user = await _context.Users.FindAsync(id);
if (user == null)
{
return false;
}
_context.Users.Remove(user);
await _context.SaveChangesAsync();
_logger.LogInformation("Deleted user with ID: {UserId}", id);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting user with ID: {UserId}", id);
throw;
}
}
public async Task<(List<User> Users, int TotalCount)> GetUsersAsync(int pageSize, int skip, string? filter = null, string? orderBy = null)
{
try
{
var query = _context.Users.AsQueryable();
// Apply filter
if (!string.IsNullOrEmpty(filter))
{
query = query.Where(u => u.Name.Contains(filter) || u.Email.Contains(filter));
}
var totalCount = await query.CountAsync();
// Apply ordering
query = orderBy?.ToLower() switch
{
"name" => query.OrderBy(u => u.Name),
"email" => query.OrderBy(u => u.Email),
"created" => query.OrderBy(u => u.CreatedAt),
_ => query.OrderByDescending(u => u.CreatedAt)
};
var users = await query
.Skip(skip)
.Take(pageSize)
.ToListAsync();
return (users, totalCount);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving users");
throw;
}
}
}`,
'Services/IProductDataService.cs': `using {{serviceName}}.Models;
namespace {{serviceName}}.Services;
public interface IProductDataService
{
Task<Product?> GetProductByIdAsync(int id);
Task<Product> CreateProductAsync(Product product);
Task<Product> UpdateProductAsync(Product product);
Task<bool> DeleteProductAsync(int id);
Task<(List<Product> Products, int TotalCount)> GetProductsAsync(int pageSize, int skip, string? categoryFilter = null, decimal? minPrice = null, decimal? maxPrice = null, string? searchQuery = null, string? orderBy = null);
Task<List<Product>> BatchCreateProductsAsync(List<Product> products);
}`,
'Services/ProductDataService.cs': `using Microsoft.EntityFrameworkCore;
using {{serviceName}}.Data;
using {{serviceName}}.Models;
namespace {{serviceName}}.Services;
public class ProductDataService : IProductDataService
{
private readonly ApplicationDbContext _context;
private readonly ILogger<ProductDataService> _logger;
public ProductDataService(ApplicationDbContext context, ILogger<ProductDataService> logger)
{
_context = context;
_logger = logger;
}
public async Task<Product?> GetProductByIdAsync(int id)
{
try
{
return await _context.Products.FindAsync(id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving product with ID: {ProductId}", id);
throw;
}
}
public async Task<Product> CreateProductAsync(Product product)
{
try
{
product.CreatedAt = DateTime.UtcNow;
product.UpdatedAt = DateTime.UtcNow;
_context.Products.Add(product);
await _context.SaveChangesAsync();
_logger.LogInformation("Created product with ID: {ProductId}", product.Id);
return product;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating product: {ProductName}", product.Name);
throw;
}
}
public async Task<Product> UpdateProductAsync(Product product)
{
try
{
product.UpdatedAt = DateTime.UtcNow;
_context.Products.Update(product);
await _context.SaveChangesAsync();
_logger.LogInformation("Updated product with ID: {ProductId}", product.Id);
return product;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating product with ID: {ProductId}", product.Id);
throw;
}
}
public async Task<bool> DeleteProductAsync(int id)
{
try
{
var product = await _context.Products.FindAsync(id);
if (product == null)
{
return false;
}
_context.Products.Remove(product);
await _context.SaveChangesAsync();
_logger.LogInformation("Deleted product with ID: {ProductId}", id);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting product with ID: {ProductId}", id);
throw;
}
}
public async Task<(List<Product> Products, int TotalCount)> GetProductsAsync(int pageSize, int skip, string? categoryFilter = null, decimal? minPrice = null, decimal? maxPrice = null, string? searchQuery = null, string? orderBy = null)
{
try
{
var query = _context.Products.AsQueryable();
// Apply filters
if (!string.IsNullOrEmpty(categoryFilter))
{
query = query.Where(p => p.Category == categoryFilter);
}
if (minPrice.HasValue)
{
query = query.Where(p => p.Price >= minPrice.Value);
}
if (maxPrice.HasValue)
{
query = query.Where(p => p.Price <= maxPrice.Value);
}
if (!string.IsNullOrEmpty(searchQuery))
{
query = query.Where(p => p.Name.Contains(searchQuery) ||
p.Description!.Contains(searchQuery));
}
var totalCount = await query.CountAsync();
// Apply ordering
query = orderBy?.ToLower() switch
{
"name" => query.OrderBy(p => p.Name),
"price" => query.OrderBy(p => p.Price),
"category" => query.OrderBy(p => p.Category),
"created" => query.OrderBy(p => p.CreatedAt),
_ => query.OrderByDescending(p => p.CreatedAt)
};
var products = await query
.Skip(skip)
.Take(pageSize)
.ToListAsync();
return (products, totalCount);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving products");
throw;
}
}
public async Task<List<Product>> BatchCreateProductsAsync(List<Product> products)
{
try
{
var now = DateTime.UtcNow;
foreach (var product in products)
{
product.CreatedAt = now;
product.UpdatedAt = now;
}
_context.Products.AddRange(products);
await _context.SaveChangesAsync();
_logger.LogInformation("Batch created {Count} products", products.Count);
return products;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error batch creating products");
throw;
}
}
}`,
// gRPC Services
'Services/UserGrpcService.cs': `using Grpc.Core;
using AutoMapper;
using {{serviceName}}.Protos;
using {{serviceName}}.Models;
using Google.Protobuf.WellKnownTypes;
namespace {{serviceName}}.Services;
public class UserGrpcService : UserService.UserServiceBase
{
private readonly IUserDataService _userDataService;
private readonly IMapper _mapper;
private readonly ILogger<UserGrpcService> _logger;
public UserGrpcService(IUserDataService userDataService, IMapper mapper, ILogger<UserGrpcService> logger)
{
_userDataService = userDataService;
_mapper = mapper;
_logger = logger;
}
public override async Task<UserReply> GetUser(GetUserRequest request, ServerCallContext context)
{
try
{
_logger.LogInformation("Getting user with ID: {UserId}", request.Id);
var user = await _userDataService.GetUserByIdAsync(request.Id);
if (user == null)
{
throw new RpcException(new Status(StatusCode.NotFound, $"User with ID {request.Id} not found"));
}
return _mapper.Map<UserReply>(user);
}
catch (RpcException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting user with ID: {UserId}", request.Id);
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task<UserReply> CreateUser(CreateUserRequest request, ServerCallContext context)
{
try
{
_logger.LogInformation("Creating user with email: {Email}", request.Email);
var user = _mapper.Map<User>(request);
var createdUser = await _userDataService.CreateUserAsync(user);
return _mapper.Map<UserReply>(createdUser);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating user with email: {Email}", request.Email);
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task<UserReply> UpdateUser(UpdateUserRequest request, ServerCallContext context)
{
try
{
_logger.LogInformation("Updating user with ID: {UserId}", request.Id);
var existingUser = await _userDataService.GetUserByIdAsync(request.Id);
if (existingUser == null)
{
throw new RpcException(new Status(StatusCode.NotFound, $"User with ID {request.Id} not found"));
}
_mapper.Map(request, existingUser);
var updatedUser = await _userDataService.UpdateUserAsync(existingUser);
return _mapper.Map<UserReply>(updatedUser);
}
catch (RpcException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating user with ID: {UserId}", request.Id);
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task<DeleteUserReply> DeleteUser(DeleteUserRequest request, ServerCallContext context)
{
try
{
_logger.LogInformation("Deleting user with ID: {UserId}", request.Id);
var deleted = await _userDataService.DeleteUserAsync(request.Id);
return new DeleteUserReply
{
Success = deleted,
Message = deleted ? "User deleted successfully" : "User not found"
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting user with ID: {UserId}", request.Id);
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task<ListUsersReply> ListUsers(ListUsersRequest request, ServerCallContext context)
{
try
{
_logger.LogInformation("Listing users with page size: {PageSize}", request.PageSize);
var pageSize = Math.Max(1, Math.Min(request.PageSize, 100)); // Limit page size
var skip = string.IsNullOrEmpty(request.PageToken) ? 0 : int.Parse(request.PageToken);
var (users, totalCount) = await _userDataService.GetUsersAsync(
pageSize, skip, request.Filter, request.OrderBy);
var reply = new ListUsersReply
{
TotalCount = totalCount,
NextPageToken = (skip + pageSize < totalCount) ? (skip + pageSize).ToString() : ""
};
reply.Users.AddRange(users.Select(u => _mapper.Map<UserReply>(u)));
return reply;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error listing users");
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task WatchUsers(WatchUsersRequest request, IServerStreamWriter<UserChangeNotification> responseStream, ServerCallContext context)
{
try
{
_logger.LogInformation("Starting user watch for {Count} users", request.UserIds.Count);
// This is a simplified example. In a real implementation, you would:
// 1. Set up database change tracking
// 2. Use SignalR or other real-time mechanism
// 3. Monitor for changes and stream them back
while (!context.CancellationToken.IsCancellationRequested)
{
// Simulate checking for changes every 5 seconds
await Task.Delay(5000, context.CancellationToken);
// In a real implementation, you would check for actual changes
// For demo purposes, we'll just send a heartbeat notification
var notification = new UserChangeNotification
{
ChangeType = UserChangeNotification.Types.ChangeType.Unknown,
User = new UserReply
{
Id = 0,
Name = "Heartbeat",
Email = "heartbeat@example.com"
}
};
await responseStream.WriteAsync(notification);
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("User watch cancelled");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in user watch");
throw;
}
}
}`,
'Services/ProductGrpcService.cs': `using Grpc.Core;
using AutoMapper;
using {{serviceName}}.Protos;
using {{serviceName}}.Models;
namespace {{serviceName}}.Services;
public class ProductGrpcService : ProductService.ProductServiceBase
{
private readonly IProductDataService _productDataService;
private readonly IMapper _mapper;
private readonly ILogger<ProductGrpcService> _logger;
public ProductGrpcService(IProductDataService productDataService, IMapper mapper, ILogger<ProductGrpcService> logger)
{
_productDataService = productDataService;
_mapper = mapper;
_logger = logger;
}
public override async Task<ProductReply> GetProduct(GetProductRequest request, ServerCallContext context)
{
try
{
_logger.LogInformation("Getting product with ID: {ProductId}", request.Id);
var product = await _productDataService.GetProductByIdAsync(request.Id);
if (product == null)
{
throw new RpcException(new Status(StatusCode.NotFound, $"Product with ID {request.Id} not found"));
}
return _mapper.Map<ProductReply>(product);
}
catch (RpcException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting product with ID: {ProductId}", request.Id);
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task<ProductReply> CreateProduct(CreateProductRequest request, ServerCallContext context)
{
try
{
_logger.LogInformation("Creating product: {ProductName}", request.Name);
var product = _mapper.Map<Product>(request);
var createdProduct = await _productDataService.CreateProductAsync(product);
return _mapper.Map<ProductReply>(createdProduct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating product: {ProductName}", request.Name);
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task<ProductReply> UpdateProduct(UpdateProductRequest request, ServerCallContext context)
{
try
{
_logger.LogInformation("Updating product with ID: {ProductId}", request.Id);
var existingProduct = await _productDataService.GetProductByIdAsync(request.Id);
if (existingProduct == null)
{
throw new RpcException(new Status(StatusCode.NotFound, $"Product with ID {request.Id} not found"));
}
_mapper.Map(request, existingProduct);
var updatedProduct = await _productDataService.UpdateProductAsync(existingProduct);
return _mapper.Map<ProductReply>(updatedProduct);
}
catch (RpcException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating product with ID: {ProductId}", request.Id);
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task<DeleteProductReply> DeleteProduct(DeleteProductRequest request, ServerCallContext context)
{
try
{
_logger.LogInformation("Deleting product with ID: {ProductId}", request.Id);
var deleted = await _productDataService.DeleteProductAsync(request.Id);
return new DeleteProductReply
{
Success = deleted,
Message = deleted ? "Product deleted successfully" : "Product not found"
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting product with ID: {ProductId}", request.Id);
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task<ListProductsReply> ListProducts(ListProductsRequest request, ServerCallContext context)
{
try
{
_logger.LogInformation("Listing products with page size: {PageSize}", request.PageSize);
var pageSize = Math.Max(1, Math.Min(request.PageSize, 100));
var skip = string.IsNullOrEmpty(request.PageToken) ? 0 : int.Parse(request.PageToken);
var (products, totalCount) = await _productDataService.GetProductsAsync(
pageSize, skip, request.CategoryFilter,
request.MinPrice > 0 ? (decimal)request.MinPrice : null,
request.MaxPrice > 0 ? (decimal)request.MaxPrice : null,
request.SearchQuery, request.OrderBy);
var reply = new ListProductsReply
{
TotalCount = totalCount,
NextPageToken = (skip + pageSize < totalCount) ? (skip + pageSize).ToString() : ""
};
reply.Products.AddRange(products.Select(p => _mapper.Map<ProductReply>(p)));
return reply;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error listing products");
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task<BatchCreateProductsReply> BatchCreateProducts(IAsyncStreamReader<CreateProductRequest> requestStream, ServerCallContext context)
{
try
{
_logger.LogInformation("Starting batch product creation");
var products = new List<Product>();
var errors = new List<string>();
await foreach (var request in requestStream.ReadAllAsync())
{
try
{
var product = _mapper.Map<Product>(request);
products.Add(product);
}
catch (Exception ex)
{
errors.Add($"Error mapping product '{request.Name}': {ex.Message}");
}
}
var createdProducts = new List<Product>();
if (products.Any())
{
createdProducts = await _productDataService.BatchCreateProductsAsync(products);
}
var reply = new BatchCreateProductsReply
{
SuccessCount = createdProducts.Count,
ErrorCount = errors.Count
};
reply.Products.AddRange(createdProducts.Select(p => _mapper.Map<ProductReply>(p)));
reply.Errors.AddRange(errors);
_logger.LogInformation("Batch creation completed: {SuccessCount} success, {ErrorCount} errors",
reply.SuccessCount, reply.ErrorCount);
return reply;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in batch product creation");
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task StreamProductUpdates(IAsyncStreamReader<ProductUpdateRequest> requestStream, IServerStreamWriter<ProductUpdateReply> responseStream, ServerCallContext context)
{
try
{
_logger.LogInformation("Starting product update stream");
await foreach (var request in requestStream.ReadAllAsync())
{
try
{
var product = await _productDataService.GetProductByIdAsync(request.ProductId);
if (product == null)
{
var errorReply = new ProductUpdateReply
{
Success = false,
Message = $"Product with ID {request.ProductId} not found"
};
await responseStream.WriteAsync(errorReply);
continue;
}
// Apply the update based on the field
switch (request.Field.ToLower())
{
case "name":
product.Name = request.Value;
break;
case "description":
product.Description = request.Value;
break;
case "price":
if (decimal.TryParse(request.Value, out var price))
product.Price = price;
break;
case "category":
product.Category = request.Value;
break;
case "stock":
if (int.TryParse(request.Value, out var stock))
product.StockQuantity = stock;
break;
default:
var invalidReply = new ProductUpdateReply
{
Success = false,
Message = $"Invalid field: {request.Field}"
};
await responseStream.WriteAsync(invalidReply);
continue;
}
var updatedProduct = await _productDataService.UpdateProductAsync(product);
var successReply = new ProductUpdateReply
{
Success = true,
Message = $"Updated {request.Field} successfully",
Product = _mapper.Map<ProductReply>(updatedProduct)
};
await responseStream.WriteAsync(successReply);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating product {ProductId}", request.ProductId);
var errorReply = new ProductUpdateReply
{
Success = false,
Message = $"Error updating product: {ex.Message}"
};
await responseStream.WriteAsync(errorReply);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in product update stream");
throw;
}
}
}`,
// Mapping Profiles
'Mappings/MappingProfile.cs': `using AutoMapper;
using {{serviceName}}.Models;
using {{serviceName}}.Protos;
using Google.Protobuf.WellKnownTypes;
namespace {{serviceName}}.Mappings;
public class MappingProfile : Profile
{
public MappingProfile()
{
// User mappings
CreateMap<CreateUserRequest, User>()
.ForMember(dest => dest.Roles, opt => opt.MapFrom(src => src.Roles.ToList()));
CreateMap<UpdateUserRequest, User>()
.ForMember(dest => dest.Roles, opt => opt.MapFrom(src => src.Roles.ToList()));
CreateMap<User, UserReply>()
.ForMember(dest => dest.Roles, opt => opt.MapFrom(src => src.Roles))
.ForMember(dest => dest.CreatedAt, opt => opt.MapFrom(src => Timestamp.FromDateTime(src.CreatedAt)))
.ForMember(dest => dest.UpdatedAt, opt => opt.MapFrom(src => Timestamp.FromDateTime(src.UpdatedAt)));
// Product mappings
CreateMap<CreateProductRequest, Product>()
.ForMember(dest => dest.Price, opt => opt.MapFrom(src => (decimal)src.Price))
.ForMember(dest => dest.Tags, opt => opt.MapFrom(src => src.Tags.ToList()));
CreateMap<UpdateProductRequest, Product>()
.ForMember(dest => dest.Price, opt => opt.MapFrom(src => (decimal)src.Price))
.ForMember(dest => dest.Tags, opt => opt.MapFrom(src => src.Tags.ToList()));
CreateMap<Product, ProductReply>()
.ForMember(dest => dest.Price, opt => opt.MapFrom(src => (double)src.Price))
.ForMember(dest => dest.Tags, opt => opt.MapFrom(src => src.Tags))
.ForMember(dest => dest.CreatedAt, opt => opt.MapFrom(src => Timestamp.FromDateTime(src.CreatedAt)))
.ForMember(dest => dest.UpdatedAt, opt => opt.MapFrom(src => Timestamp.FromDateTime(src.UpdatedAt)));
}
}`,
// Interceptors
'Interceptors/LoggingInterceptor.cs': `using Grpc.Core;
using Grpc.Core.Interceptors;
namespace {{serviceName}}.Interceptors;
public class LoggingInterceptor : Interceptor
{
private readonly ILogger<LoggingInterceptor> _logger;
public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
{
_logger = logger;
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
var methodName = context.Method;
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
_logger.LogInformation("Starting gRPC call: {Method}", methodName);
try
{
var response = await continuation(request, context);
stopwatch.Stop();
_logger.LogInformation("Completed gRPC call: {Method} in {ElapsedMilliseconds}ms",
methodName, stopwatch.ElapsedMilliseconds);
return response;
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, "Error in gRPC call: {Method} after {ElapsedMilliseconds}ms",
methodName, stopwatch.ElapsedMilliseconds);
throw;
}
}
public override async Task<TResponse> ClientStreamingServerHandler<TRequest, TResponse>(
IAsyncStreamReader<TRequest> requestStream,
ServerCallContext context,
ClientStreamingServerMethod<TRequest, TResponse> continuation)
{
var methodName = context.Method;
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
_logger.LogInformation("Starting client streaming gRPC call: {Method}", methodName);
try
{
var response = await continuation(requestStream, context);
stopwatch.Stop();
_logger.LogInformation("Completed client streaming gRPC call: {Method} in {ElapsedMilliseconds}ms",
methodName, stopwatch.ElapsedMilliseconds);
return response;
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, "Error in client streaming gRPC call: {Method} after {ElapsedMilliseconds}ms",
methodName, stopwatch.ElapsedMilliseconds);
throw;
}
}
public override async Task ServerStreamingServerHandler<TRequest, TResponse>(
TRequest request,
IServerStreamWriter<TResponse> responseStream,
ServerCallContext context,
ServerStreamingServerMethod<TRequest, TResponse> continuation)
{
var methodName = context.Method;
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
_logger.LogInformation("Starting server streaming gRPC call: {Method}", methodName);
try
{
await continuation(request, responseStream, context);
stopwatch.Stop();
_logger.LogInformation("Completed server streaming gRPC call: {Method} in {ElapsedMilliseconds}ms",
methodName, stopwatch.ElapsedMilliseconds);
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, "Error in server streaming gRPC call: {Method} after {ElapsedMilliseconds}ms",
methodName, stopwatch.ElapsedMilliseconds);
throw;
}
}
public override async Task DuplexStreamingServerHandler<TRequest, TResponse>(
IAsyncStreamReader<TRequest> requestStream,
IServerStreamWriter<TResponse> responseStream,
ServerCallContext context,
DuplexStreamingServerMethod<TRequest, TResponse> continuation)
{
var methodName = context.Method;
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
_logger.LogInformation("Starting bidirectional streaming gRPC call: {Method}", methodName);
try
{
await continuation(requestStream, responseStream, context);
stopwatch.Stop();
_logger.LogInformation("Completed bidirectional streaming gRPC call: {Method} in {ElapsedMilliseconds}ms",
methodName, stopwatch.ElapsedMilliseconds);
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, "Error in bidirectional streaming gRPC call: {Method} after {ElapsedMilliseconds}ms",
methodName, stopwatch.ElapsedMilliseconds);
throw;
}
}
}`,
'Interceptors/ExceptionInterceptor.cs': `using Grpc.Core;
using Grpc.Core.Interceptors;
namespace {{serviceName}}.Interceptors;
public class ExceptionInterceptor : Interceptor
{
private readonly ILogger<ExceptionInterceptor> _logger;
public ExceptionInterceptor(ILogger<ExceptionInterceptor> logger)
{
_logger = logger;
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
try
{
return await continuation(request, context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in gRPC method: {Method}", context.Method);
throw HandleException(ex);
}
}
public override async Task<TResponse> ClientStreamingServerHandler<TRequest, TResponse>(
IAsyncStreamReader<TRequest> requestStream,
ServerCallContext context,
ClientStreamingServerMethod<TRequest, TResponse> continuation)
{
try
{
return await continuation(requestStream, context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in client streaming gRPC method: {Method}", context.Method);
throw HandleException(ex);
}
}
public override async Task ServerStreamingServerHandler<TRequest, TResponse>(
TRequest request,
IServerStreamWriter<TResponse> responseStream,
ServerCallContext context,
ServerStreamingServerMethod<TRequest, TResponse> continuation)
{
try
{
await continuation(request, responseStream, context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in server streaming gRPC method: {Method}", context.Method);
throw HandleException(ex);
}
}
public override async Task DuplexStreamingServerHandler<TRequest, TResponse>(
IAsyncStreamReader<TRequest> requestStream,
IServerStreamWriter<TResponse> responseStream,
ServerCallContext context,
DuplexStreamingServerMethod<TRequest, TResponse> continuation)
{
try
{
await continuation(requestStream, responseStream, context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in bidirectional streaming gRPC method: {Method}", context.Method);
throw HandleException(ex);
}
}
private static RpcException HandleException(Exception ex)
{
return ex switch
{
RpcException rpcEx => rpcEx,
ArgumentException argEx => new RpcException(new Status(StatusCode.InvalidArgument, argEx.Message)),
UnauthorizedAccessException => new RpcException(new Status(StatusCode.Unauthenticated, "Unauthorized")),
InvalidOperationException invEx => new RpcException(new Status(StatusCode.FailedPrecondition, invEx.Message)),