# Getting Started with gRPC Create your first gRPC service with automatic code generation. ## Prerequisites - .NET 10 SDK - Basic understanding of CQRS - Protocol Buffers familiarity (helpful but not required) ## Installation ### Install Packages ```bash dotnet add package Svrnty.CQRS.Grpc dotnet add package Svrnty.CQRS.Grpc.Generators dotnet add package Grpc.AspNetCore dotnet add package Grpc.AspNetCore.Server.Reflection ``` ### Package References ```xml ``` ## Step 1: Create .proto File Create `Protos/cqrs_services.proto`: ```protobuf syntax = "proto3"; package myapp; import "google/protobuf/empty.proto"; // Command Service service CommandService { rpc CreateUser (CreateUserCommand) returns (CreateUserResponse); rpc DeleteUser (DeleteUserCommand) returns (google.protobuf.Empty); } // Query Service service QueryService { rpc GetUser (GetUserQuery) returns (UserDto); } // Commands message CreateUserCommand { string name = 1; string email = 2; } message CreateUserResponse { int32 user_id = 1; } message DeleteUserCommand { int32 user_id = 1; } // Queries message GetUserQuery { int32 user_id = 1; } // DTOs message UserDto { int32 id = 1; string name = 2; string email = 3; } ``` ## Step 2: Configure .csproj Add .proto file reference to your project file: ```xml ``` ## Step 3: Implement Handlers ### Command Handler (With Result) ```csharp using Svrnty.CQRS.Abstractions; public record CreateUserCommand { public string Name { get; init; } = string.Empty; public string Email { get; init; } = string.Empty; } public class CreateUserCommandHandler : ICommandHandler { private readonly IUserRepository _userRepository; public CreateUserCommandHandler(IUserRepository userRepository) { _userRepository = userRepository; } public async Task HandleAsync( CreateUserCommand command, CancellationToken cancellationToken) { var user = new User { Name = command.Name, Email = command.Email }; await _userRepository.AddAsync(user, cancellationToken); return user.Id; } } ``` ### Command Handler (No Result) ```csharp public record DeleteUserCommand { public int UserId { get; init; } } public class DeleteUserCommandHandler : ICommandHandler { private readonly IUserRepository _userRepository; public DeleteUserCommandHandler(IUserRepository userRepository) { _userRepository = userRepository; } public async Task HandleAsync( DeleteUserCommand command, CancellationToken cancellationToken) { await _userRepository.DeleteAsync(command.UserId, cancellationToken); } } ``` ### Query Handler ```csharp public record GetUserQuery { public int UserId { get; init; } } public record UserDto { public int Id { get; init; } public string Name { get; init; } = string.Empty; public string Email { get; init; } = string.Empty; } public class GetUserQueryHandler : IQueryHandler { private readonly IUserRepository _userRepository; public GetUserQueryHandler(IUserRepository userRepository) { _userRepository = userRepository; } public async Task HandleAsync( GetUserQuery query, CancellationToken cancellationToken) { var user = await _userRepository.GetByIdAsync(query.UserId, cancellationToken); if (user == null) throw new KeyNotFoundException($"User {query.UserId} not found"); return new UserDto { Id = user.Id, Name = user.Name, Email = user.Email }; } } ``` ## Step 4: Configure Services ```csharp using Svrnty.CQRS; using Svrnty.CQRS.Grpc; var builder = WebApplication.CreateBuilder(args); // Register CQRS services builder.Services.AddSvrntyCQRS(); builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddDefaultQueryDiscovery(); // Register commands builder.Services.AddCommand(); builder.Services.AddCommand(); // Register queries builder.Services.AddQuery(); // Register repository builder.Services.AddScoped(); // Add gRPC builder.Services.AddGrpc(); var app = builder.Build(); // Map auto-generated service implementations app.MapGrpcService(); app.MapGrpcService(); // Enable reflection for development app.MapGrpcReflectionService(); app.Run(); ``` **That's it!** The source generator automatically creates `CommandServiceImpl` and `QueryServiceImpl`. ## Step 5: Build and Run ```bash dotnet build dotnet run ``` **Server starts on:** ``` Now listening on: https://localhost:5001 ``` ## Step 6: Test with grpcurl ### Install grpcurl **macOS:** ```bash brew install grpcurl ``` **Windows:** ```powershell choco install grpcurl ``` **Linux:** ```bash # Download from GitHub releases ``` ### List Services ```bash grpcurl -plaintext localhost:5001 list ``` **Output:** ``` grpc.reflection.v1alpha.ServerReflection myapp.CommandService myapp.QueryService ``` ### Describe Service ```bash grpcurl -plaintext localhost:5001 describe myapp.CommandService ``` **Output:** ``` myapp.CommandService is a service: service CommandService { rpc CreateUser ( .myapp.CreateUserCommand ) returns ( .myapp.CreateUserResponse ); rpc DeleteUser ( .myapp.DeleteUserCommand ) returns ( .google.protobuf.Empty ); } ``` ### Call CreateUser ```bash grpcurl -plaintext \ -d '{"name": "John Doe", "email": "john@example.com"}' \ localhost:5001 \ myapp.CommandService/CreateUser ``` **Response:** ```json { "userId": 42 } ``` ### Call GetUser ```bash grpcurl -plaintext \ -d '{"userId": 42}' \ localhost:5001 \ myapp.QueryService/GetUser ``` **Response:** ```json { "id": 42, "name": "John Doe", "email": "john@example.com" } ``` ### Call DeleteUser ```bash grpcurl -plaintext \ -d '{"userId": 42}' \ localhost:5001 \ myapp.CommandService/DeleteUser ``` **Response:** ```json {} ``` ## Step 7: Add Validation ### Install FluentValidation ```bash dotnet add package FluentValidation ``` ### Create Validator ```csharp using FluentValidation; public class CreateUserCommandValidator : AbstractValidator { public CreateUserCommandValidator() { RuleFor(x => x.Name) .NotEmpty() .WithMessage("Name is required") .MaximumLength(100) .WithMessage("Name must not exceed 100 characters"); RuleFor(x => x.Email) .NotEmpty() .EmailAddress() .WithMessage("Valid email address is required"); } } ``` ### Register Validator ```csharp builder.Services.AddTransient, CreateUserCommandValidator>(); ``` ### Test Validation ```bash grpcurl -plaintext \ -d '{"name": "", "email": "invalid"}' \ localhost:5001 \ myapp.CommandService/CreateUser ``` **Error Response:** ```json ERROR: Code: InvalidArgument Message: Validation failed Details: google.rpc.BadRequest { field_violations: [ { field: "name", description: "Name is required" }, { field: "email", description: "Valid email address is required" } ] } ``` ## Complete Example **Project Structure:** ``` MyGrpcApp/ ├── MyGrpcApp.csproj ├── Program.cs ├── Protos/ │ └── cqrs_services.proto ├── Commands/ │ ├── CreateUserCommand.cs │ ├── CreateUserCommandHandler.cs │ ├── CreateUserCommandValidator.cs │ ├── DeleteUserCommand.cs │ └── DeleteUserCommandHandler.cs ├── Queries/ │ ├── GetUserQuery.cs │ ├── GetUserQueryHandler.cs │ └── UserDto.cs └── Repositories/ ├── IUserRepository.cs └── UserRepository.cs ``` **Program.cs:** ```csharp using FluentValidation; using MyGrpcApp.Commands; using MyGrpcApp.Queries; using MyGrpcApp.Repositories; using Svrnty.CQRS; using Svrnty.CQRS.Grpc; var builder = WebApplication.CreateBuilder(args); // CQRS builder.Services.AddSvrntyCQRS(); builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddDefaultQueryDiscovery(); // Commands builder.Services.AddCommand(); builder.Services.AddTransient, CreateUserCommandValidator>(); builder.Services.AddCommand(); // Queries builder.Services.AddQuery(); // Repositories builder.Services.AddSingleton(); // gRPC builder.Services.AddGrpc(); var app = builder.Build(); // Map services app.MapGrpcService(); app.MapGrpcService(); app.MapGrpcReflectionService(); app.Run(); ``` ## Testing with Postman Postman supports gRPC natively: 1. Create new gRPC request 2. Enter server URL: `localhost:5001` 3. Import .proto files or use reflection 4. Select service method 5. Fill in message fields 6. Click "Invoke" ## Next Steps Now that you have a basic gRPC service: 1. **[Proto File Setup](proto-file-setup.md)** - Learn .proto file conventions 2. **[Source Generators](source-generators.md)** - Understand how code generation works 3. **[Service Implementation](service-implementation.md)** - Explore generated code 4. **[gRPC Clients](grpc-clients.md)** - Build clients to consume your services ## Troubleshooting ### Build Errors **Issue:** "Could not find file Protos\cqrs_services.proto" **Solution:** Ensure .proto file is in `Protos/` directory and referenced in .csproj. ### grpcurl Connection Failed **Issue:** "Failed to dial target host" **Solution:** 1. Ensure server is running 2. Check port number 3. Use `-plaintext` for development (no TLS) 4. Use `-insecure` for self-signed certificates in production ### Reflection Not Working **Issue:** grpcurl can't list services **Solution:** Add gRPC reflection: ```csharp app.MapGrpcReflectionService(); ``` ### Validation Not Working **Issue:** Validation doesn't run **Solution:** Ensure validator is registered: ```csharp builder.Services.AddTransient, CreateUserCommandValidator>(); ``` ## See Also - [gRPC Integration Overview](README.md) - [Proto File Setup](proto-file-setup.md) - [Source Generators](source-generators.md) - [gRPC Clients](grpc-clients.md) - [gRPC Troubleshooting](grpc-troubleshooting.md)