503 lines
11 KiB
Markdown
503 lines
11 KiB
Markdown
# gRPC Integration Overview
|
|
|
|
Expose commands and queries via high-performance gRPC services with automatic code generation.
|
|
|
|
## What is gRPC Integration?
|
|
|
|
The `Svrnty.CQRS.Grpc` package with `Svrnty.CQRS.Grpc.Generators` source generator provides automatic gRPC service implementations for all registered commands and queries.
|
|
|
|
**Key Features:**
|
|
- ✅ **Automatic service generation** - Source generators create implementations
|
|
- ✅ **Google Rich Error Model** - Structured validation errors
|
|
- ✅ **High performance** - Binary Protocol Buffers
|
|
- ✅ **Strong typing** - Compile-time safety
|
|
- ✅ **gRPC reflection** - Tool support (grpcurl, Postman)
|
|
- ✅ **Bidirectional streaming** - Real-time communication
|
|
- ✅ **Cross-platform** - Works with any gRPC client
|
|
|
|
## Quick Start
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
dotnet add package Svrnty.CQRS.Grpc
|
|
dotnet add package Svrnty.CQRS.Grpc.Generators
|
|
dotnet add package Grpc.AspNetCore
|
|
```
|
|
|
|
### Basic Setup
|
|
|
|
**1. Define .proto file:**
|
|
|
|
```protobuf
|
|
syntax = "proto3";
|
|
|
|
package myapp;
|
|
|
|
import "google/protobuf/empty.proto";
|
|
|
|
service CommandService {
|
|
rpc CreateUser (CreateUserCommand) returns (CreateUserResponse);
|
|
rpc DeleteUser (DeleteUserCommand) returns (google.protobuf.Empty);
|
|
}
|
|
|
|
service QueryService {
|
|
rpc GetUser (GetUserQuery) returns (UserDto);
|
|
}
|
|
|
|
message CreateUserCommand {
|
|
string name = 1;
|
|
string email = 2;
|
|
}
|
|
|
|
message CreateUserResponse {
|
|
int32 user_id = 1;
|
|
}
|
|
|
|
message DeleteUserCommand {
|
|
int32 user_id = 1;
|
|
}
|
|
|
|
message GetUserQuery {
|
|
int32 user_id = 1;
|
|
}
|
|
|
|
message UserDto {
|
|
int32 id = 1;
|
|
string name = 2;
|
|
string email = 3;
|
|
}
|
|
```
|
|
|
|
**2. Configure services:**
|
|
|
|
```csharp
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Register CQRS services
|
|
builder.Services.AddSvrntyCQRS();
|
|
builder.Services.AddDefaultCommandDiscovery();
|
|
builder.Services.AddDefaultQueryDiscovery();
|
|
|
|
// Register commands and queries
|
|
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
|
|
builder.Services.AddCommand<DeleteUserCommand, DeleteUserCommandHandler>();
|
|
builder.Services.AddQuery<GetUserQuery, UserDto, GetUserQueryHandler>();
|
|
|
|
// Add gRPC
|
|
builder.Services.AddGrpc();
|
|
|
|
var app = builder.Build();
|
|
|
|
// Map auto-generated service implementations
|
|
app.MapGrpcService<CommandServiceImpl>();
|
|
app.MapGrpcService<QueryServiceImpl>();
|
|
|
|
// Enable reflection for tools
|
|
app.MapGrpcReflectionService();
|
|
|
|
app.Run();
|
|
```
|
|
|
|
**3. Source generator automatically creates:**
|
|
- `CommandServiceImpl` class implementing `CommandService.CommandServiceBase`
|
|
- `QueryServiceImpl` class implementing `QueryService.QueryServiceBase`
|
|
|
|
## How It Works
|
|
|
|
```
|
|
┌─────────────────────────────┐
|
|
│ Build Time │
|
|
├─────────────────────────────┤
|
|
│ 1. Read .proto files │
|
|
│ 2. Discover commands/queries│
|
|
│ 3. Generate service impls │
|
|
│ 4. Compile into assembly │
|
|
└─────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────┐
|
|
│ Runtime │
|
|
├─────────────────────────────┤
|
|
│ gRPC Request │
|
|
│ → Deserialize protobuf │
|
|
│ → Validate │
|
|
│ → Authorize │
|
|
│ → Execute handler │
|
|
│ → Serialize response │
|
|
└─────────────────────────────┘
|
|
```
|
|
|
|
## Commands via gRPC
|
|
|
|
### Command Without Result
|
|
|
|
```csharp
|
|
public record DeleteUserCommand
|
|
{
|
|
public int UserId { get; init; }
|
|
}
|
|
|
|
public class DeleteUserCommandHandler : ICommandHandler<DeleteUserCommand>
|
|
{
|
|
public async Task HandleAsync(DeleteUserCommand command, CancellationToken cancellationToken)
|
|
{
|
|
// Delete user logic
|
|
}
|
|
}
|
|
```
|
|
|
|
**.proto definition:**
|
|
```protobuf
|
|
service CommandService {
|
|
rpc DeleteUser (DeleteUserCommand) returns (google.protobuf.Empty);
|
|
}
|
|
|
|
message DeleteUserCommand {
|
|
int32 user_id = 1;
|
|
}
|
|
```
|
|
|
|
**gRPC Client:**
|
|
```csharp
|
|
var client = new CommandService.CommandServiceClient(channel);
|
|
|
|
var request = new DeleteUserCommand { UserId = 123 };
|
|
await client.DeleteUserAsync(request);
|
|
```
|
|
|
|
### Command With Result
|
|
|
|
```csharp
|
|
public record CreateUserCommand
|
|
{
|
|
public string Name { get; init; } = string.Empty;
|
|
public string Email { get; init; } = string.Empty;
|
|
}
|
|
|
|
public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand, int>
|
|
{
|
|
public async Task<int> HandleAsync(CreateUserCommand command, CancellationToken cancellationToken)
|
|
{
|
|
// Create user and return ID
|
|
return newUserId;
|
|
}
|
|
}
|
|
```
|
|
|
|
**.proto definition:**
|
|
```protobuf
|
|
service CommandService {
|
|
rpc CreateUser (CreateUserCommand) returns (CreateUserResponse);
|
|
}
|
|
|
|
message CreateUserCommand {
|
|
string name = 1;
|
|
string email = 2;
|
|
}
|
|
|
|
message CreateUserResponse {
|
|
int32 user_id = 1;
|
|
}
|
|
```
|
|
|
|
**gRPC Client:**
|
|
```csharp
|
|
var client = new CommandService.CommandServiceClient(channel);
|
|
|
|
var request = new CreateUserCommand
|
|
{
|
|
Name = "John Doe",
|
|
Email = "john@example.com"
|
|
};
|
|
|
|
var response = await client.CreateUserAsync(request);
|
|
var userId = response.UserId;
|
|
```
|
|
|
|
## Queries via gRPC
|
|
|
|
```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<GetUserQuery, UserDto>
|
|
{
|
|
public async Task<UserDto> HandleAsync(GetUserQuery query, CancellationToken cancellationToken)
|
|
{
|
|
// Fetch and return user
|
|
}
|
|
}
|
|
```
|
|
|
|
**.proto definition:**
|
|
```protobuf
|
|
service QueryService {
|
|
rpc GetUser (GetUserQuery) returns (UserDto);
|
|
}
|
|
|
|
message GetUserQuery {
|
|
int32 user_id = 1;
|
|
}
|
|
|
|
message UserDto {
|
|
int32 id = 1;
|
|
string name = 2;
|
|
string email = 3;
|
|
}
|
|
```
|
|
|
|
**gRPC Client:**
|
|
```csharp
|
|
var client = new QueryService.QueryServiceClient(channel);
|
|
|
|
var request = new GetUserQuery { UserId = 123 };
|
|
var user = await client.GetUserAsync(request);
|
|
```
|
|
|
|
## Validation
|
|
|
|
### Automatic Validation with Rich Error Model
|
|
|
|
```csharp
|
|
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
|
|
{
|
|
public CreateUserCommandValidator()
|
|
{
|
|
RuleFor(x => x.Name)
|
|
.NotEmpty()
|
|
.WithMessage("Name is required");
|
|
|
|
RuleFor(x => x.Email)
|
|
.EmailAddress()
|
|
.WithMessage("Valid email address is required");
|
|
}
|
|
}
|
|
```
|
|
|
|
**Validation failure response:**
|
|
```protobuf
|
|
google.rpc.Status {
|
|
code: 3 // INVALID_ARGUMENT
|
|
message: "Validation failed"
|
|
details: [
|
|
google.rpc.BadRequest {
|
|
field_violations: [
|
|
{ field: "name", description: "Name is required" },
|
|
{ field: "email", description: "Valid email address is required" }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Client handling:**
|
|
```csharp
|
|
using Grpc.Core;
|
|
using Google.Rpc;
|
|
|
|
try
|
|
{
|
|
var response = await client.CreateUserAsync(request);
|
|
}
|
|
catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument)
|
|
{
|
|
var status = ex.GetRpcStatus();
|
|
var badRequest = status.GetDetail<BadRequest>();
|
|
|
|
foreach (var violation in badRequest.FieldViolations)
|
|
{
|
|
Console.WriteLine($"{violation.Field}: {violation.Description}");
|
|
}
|
|
}
|
|
```
|
|
|
|
## Performance Benefits
|
|
|
|
### Binary Protocol
|
|
|
|
gRPC uses Protocol Buffers (binary format) instead of JSON:
|
|
|
|
**JSON (HTTP):**
|
|
```json
|
|
{
|
|
"id": 123,
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
}
|
|
```
|
|
**Size:** ~71 bytes
|
|
|
|
**Protobuf (gRPC):**
|
|
```
|
|
Binary representation
|
|
```
|
|
**Size:** ~35 bytes
|
|
|
|
**Result:** ~50% smaller payload
|
|
|
|
### HTTP/2 Multiplexing
|
|
|
|
- Multiple requests over single connection
|
|
- Header compression
|
|
- Server push capability
|
|
- Bidirectional streaming
|
|
|
|
## gRPC vs HTTP Comparison
|
|
|
|
| Feature | gRPC | HTTP (Minimal API) |
|
|
|---------|------|-------------------|
|
|
| Protocol | HTTP/2 | HTTP/1.1 or HTTP/2 |
|
|
| Format | Protobuf (binary) | JSON (text) |
|
|
| Performance | Very fast | Fast |
|
|
| Payload Size | Small | Larger |
|
|
| Browser Support | Limited (grpc-web) | Full |
|
|
| Tooling | grpcurl, Postman | curl, Postman, Swagger |
|
|
| Streaming | Native bidirectional | Server-Sent Events |
|
|
| Code Generation | Automatic | Automatic |
|
|
| Type Safety | Strong | Strong |
|
|
|
|
### When to Use gRPC
|
|
|
|
✅ **Use gRPC for:**
|
|
- Microservices communication
|
|
- High-performance APIs
|
|
- Real-time bidirectional streaming
|
|
- Internal APIs
|
|
- Polyglot environments
|
|
|
|
### When to Use HTTP
|
|
|
|
✅ **Use HTTP for:**
|
|
- Public APIs
|
|
- Browser-based clients
|
|
- Simple REST APIs
|
|
- Legacy system integration
|
|
- Human-readable debugging
|
|
|
|
### Dual Protocol
|
|
|
|
**Best of both worlds:**
|
|
```csharp
|
|
// Same handlers, multiple protocols
|
|
builder.Services.AddCommand<CreateUserCommand, int, CreateUserCommandHandler>();
|
|
|
|
// HTTP endpoints
|
|
app.MapSvrntyCommands();
|
|
|
|
// gRPC endpoints
|
|
app.MapGrpcService<CommandServiceImpl>();
|
|
```
|
|
|
|
Clients choose their preferred protocol!
|
|
|
|
## Documentation
|
|
|
|
### [Getting Started](getting-started-grpc.md)
|
|
|
|
First gRPC service:
|
|
|
|
- Installation
|
|
- .proto file creation
|
|
- Service registration
|
|
- Testing with grpcurl
|
|
|
|
### [Proto File Setup](proto-file-setup.md)
|
|
|
|
.proto file creation:
|
|
|
|
- Syntax and conventions
|
|
- Message definitions
|
|
- Service definitions
|
|
- Importing common types
|
|
|
|
### [Source Generators](source-generators.md)
|
|
|
|
How code generation works:
|
|
|
|
- Build-time generation
|
|
- Generated code structure
|
|
- Customization options
|
|
- Troubleshooting
|
|
|
|
### [Service Implementation](service-implementation.md)
|
|
|
|
Generated service implementations:
|
|
|
|
- CommandServiceImpl
|
|
- QueryServiceImpl
|
|
- Validation integration
|
|
- Authorization integration
|
|
|
|
### [gRPC Reflection](grpc-reflection.md)
|
|
|
|
gRPC reflection for tools:
|
|
|
|
- Enabling reflection
|
|
- Using grpcurl
|
|
- Postman support
|
|
- Service discovery
|
|
|
|
### [gRPC Clients](grpc-clients.md)
|
|
|
|
Consuming gRPC services:
|
|
|
|
- C# client
|
|
- TypeScript client
|
|
- Go client
|
|
- Python client
|
|
|
|
### [gRPC Troubleshooting](grpc-troubleshooting.md)
|
|
|
|
Common issues:
|
|
|
|
- Connection errors
|
|
- Validation errors
|
|
- Code generation issues
|
|
- Performance tuning
|
|
|
|
## Best Practices
|
|
|
|
### ✅ DO
|
|
|
|
- Use gRPC for microservices
|
|
- Define clear .proto contracts
|
|
- Use gRPC reflection in development
|
|
- Handle RpcException properly
|
|
- Version your services
|
|
- Use deadlines/timeouts
|
|
- Enable compression
|
|
|
|
### ❌ DON'T
|
|
|
|
- Don't skip error handling
|
|
- Don't expose gRPC publicly without security
|
|
- Don't ignore validation
|
|
- Don't use gRPC for browser apps without grpc-web
|
|
- Don't forget cancellation tokens
|
|
|
|
## What's Next?
|
|
|
|
- **[Getting Started](getting-started-grpc.md)** - Create your first gRPC service
|
|
- **[Proto File Setup](proto-file-setup.md)** - Learn .proto file conventions
|
|
- **[Source Generators](source-generators.md)** - Understand code generation
|
|
- **[Service Implementation](service-implementation.md)** - Explore generated code
|
|
- **[gRPC Clients](grpc-clients.md)** - Build gRPC clients
|
|
|
|
## See Also
|
|
|
|
- [Commands Overview](../core-features/commands/README.md)
|
|
- [Queries Overview](../core-features/queries/README.md)
|
|
- [Validation Overview](../core-features/validation/README.md)
|
|
- [HTTP Integration](../http-integration/README.md)
|
|
- [Getting Started: Choosing HTTP or gRPC](../getting-started/06-choosing-http-or-grpc.md)
|