# Proto File Setup .proto file creation and conventions for CQRS services. ## Overview Protocol Buffer (.proto) files define the contract between gRPC clients and servers. They specify: - Service definitions (RPCs) - Message structures (commands, queries, DTOs) - Data types and field numbers - Import dependencies ## File Structure ### Basic Template ```protobuf syntax = "proto3"; package myapp; // Imports import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; // Options (optional) option csharp_namespace = "MyApp.Grpc"; // Service definitions service CommandService { // RPCs here } service QueryService { // RPCs here } // Message definitions message CreateUserCommand { // Fields here } ``` ### File Organization **Recommended structure:** ``` Protos/ ├── cqrs_services.proto # Main CQRS services ├── common.proto # Shared messages └── google/ # Google common types (auto-imported) └── protobuf/ ├── empty.proto ├── timestamp.proto └── wrappers.proto ``` ##Syntax ### Syntax Declaration **Always use proto3:** ```protobuf syntax = "proto3"; ``` ### Package Declaration Groups related messages and services: ```protobuf package myapp; ``` **In C#, this becomes:** ```csharp namespace MyApp.Grpc { // Generated classes } ``` ### Namespace Override ```protobuf option csharp_namespace = "MyCompany.MyApp.Grpc"; ``` ## Service Definitions ### Command Service ```protobuf service CommandService { // Command with result rpc CreateUser (CreateUserCommand) returns (CreateUserResponse); // Command without result rpc DeleteUser (DeleteUserCommand) returns (google.protobuf.Empty); // Command with complex result rpc UpdateOrder (UpdateOrderCommand) returns (OrderDto); } ``` ### Query Service ```protobuf service QueryService { // Single entity query rpc GetUser (GetUserQuery) returns (UserDto); // List query rpc ListUsers (ListUsersQuery) returns (UserListResponse); // Search query rpc SearchProducts (SearchProductsQuery) returns (ProductSearchResponse); } ``` ## Message Definitions ### Commands ```protobuf message CreateUserCommand { string name = 1; string email = 2; int32 age = 3; bool is_active = 4; } message CreateUserResponse { int32 user_id = 1; } message DeleteUserCommand { int32 user_id = 1; } ``` ### Queries ```protobuf message GetUserQuery { int32 user_id = 1; } message ListUsersQuery { int32 page = 1; int32 page_size = 2; string sort_by = 3; bool descending = 4; } ``` ### DTOs ```protobuf message UserDto { int32 id = 1; string name = 2; string email = 3; int32 age = 4; bool is_active = 5; google.protobuf.Timestamp created_at = 6; } message UserListResponse { repeated UserDto users = 1; int32 total_count = 2; int32 page = 3; int32 page_size = 4; } ``` ## Data Types ### Scalar Types | .proto Type | C# Type | Notes | |-------------|---------|-------| | `double` | `double` | | | `float` | `float` | | | `int32` | `int` | Variable-length encoding | | `int64` | `long` | Variable-length encoding | | `uint32` | `uint` | Variable-length encoding | | `uint64` | `ulong` | Variable-length encoding | | `sint32` | `int` | Signed, better for negative | | `sint64` | `long` | Signed, better for negative | | `fixed32` | `uint` | Fixed 4 bytes | | `fixed64` | `ulong` | Fixed 8 bytes | | `sfixed32` | `int` | Fixed 4 bytes, signed | | `sfixed64` | `long` | Fixed 8 bytes, signed | | `bool` | `bool` | | | `string` | `string` | UTF-8 or ASCII | | `bytes` | `ByteString` | Binary data | ### Choosing Numeric Types **Use int32/int64 for:** - Most integer fields - IDs, counts, quantities **Use sint32/sint64 for:** - Frequently negative values - Temperature, coordinates, deltas **Use fixed32/fixed64 for:** - Values > 2^28 (usually positive) - Better performance when value is consistently large ### Complex Types ```protobuf // Nested message message Address { string street = 1; string city = 2; string postal_code = 3; string country = 4; } message User { int32 id = 1; string name = 2; Address address = 3; // Nested message } // Repeated field (list) message UserListResponse { repeated UserDto users = 1; } // Map message UserPreferences { map settings = 1; } // Enum enum UserRole { USER_ROLE_UNSPECIFIED = 0; // Required default USER_ROLE_ADMIN = 1; USER_ROLE_MODERATOR = 2; USER_ROLE_USER = 3; } message User { int32 id = 1; string name = 2; UserRole role = 3; } ``` ## Field Numbers ### Rules 1. **Uniqueness**: Each field must have a unique number within a message 2. **Range**: 1 to 536,870,911 (excluding 19000-19999) 3. **Reserved**: 1-15 use 1 byte encoding (use for frequently set fields) 4. **Reserved**: 16-2047 use 2 bytes encoding ### Best Practices ```protobuf message UserDto { // Use 1-15 for frequently set fields int32 id = 1; string name = 2; string email = 3; // Use 16+ for less common fields google.protobuf.Timestamp created_at = 16; google.protobuf.Timestamp updated_at = 17; google.protobuf.Timestamp deleted_at = 18; } ``` ### Reserved Fields Prevent reusing deleted field numbers: ```protobuf message UserDto { reserved 4, 5; // Reserved field numbers reserved "old_field", "deprecated_field"; // Reserved names int32 id = 1; string name = 2; string email = 3; // Fields 4 and 5 cannot be used int32 age = 6; } ``` ## Default Values In proto3, all fields have default values: | Type | Default | |------|---------| | Numeric | 0 | | Bool | false | | String | "" (empty string) | | Bytes | Empty bytes | | Enum | First value (must be 0) | | Message | null | | Repeated | Empty list | **Important:** You cannot distinguish between "not set" and "default value" in proto3. ## Optional Fields ### Explicit Optional ```protobuf message UpdateUserCommand { int32 user_id = 1; optional string name = 2; // Can be null optional string email = 3; // Can be null optional int32 age = 4; // Can be null } ``` **In C#:** ```csharp command.Name = "John"; // Set command.Email = null; // Not set command.Age = 0; // Set to 0 or not set? Ambiguous! ``` ### Wrapper Types For nullable primitives, use wrapper types: ```protobuf import "google/protobuf/wrappers.proto"; message UpdateUserCommand { int32 user_id = 1; google.protobuf.StringValue name = 2; // Nullable string google.protobuf.Int32Value age = 3; // Nullable int google.protobuf.BoolValue is_active = 4; // Nullable bool } ``` **Available wrappers:** - `DoubleValue` - `FloatValue` - `Int64Value` - `UInt64Value` - `Int32Value` - `UInt32Value` - `BoolValue` - `StringValue` - `BytesValue` ## Common Imports ### Google Protobuf Types ```protobuf import "google/protobuf/empty.proto"; // Empty (void) import "google/protobuf/timestamp.proto"; // DateTime import "google/protobuf/duration.proto"; // TimeSpan import "google/protobuf/wrappers.proto"; // Nullable primitives ``` ### Empty Response ```protobuf import "google/protobuf/empty.proto"; service CommandService { rpc DeleteUser (DeleteUserCommand) returns (google.protobuf.Empty); } ``` ### Timestamps ```protobuf import "google/protobuf/timestamp.proto"; message UserDto { int32 id = 1; string name = 2; google.protobuf.Timestamp created_at = 3; google.protobuf.Timestamp updated_at = 4; } ``` ## Naming Conventions ### Services ```protobuf // ✅ Good - Singular, describes domain service UserService { } service OrderService { } service ProductService { } // ❌ Bad service UsersService { } // Plural service userService { } // Lowercase service User { } // Ambiguous ``` ### RPCs ```protobuf // ✅ Good - Verb + Noun rpc CreateUser (...) returns (...); rpc UpdateOrder (...) returns (...); rpc DeleteProduct (...) returns (...); rpc GetUserById (...) returns (...); rpc ListOrders (...) returns (...); // ❌ Bad rpc User (...) returns (...); // No verb rpc create_user (...) returns (...); // Snake case rpc CREATEUSER (...) returns (...); // All caps ``` ### Messages ```protobuf // ✅ Good - PascalCase message CreateUserCommand { } message UserDto { } message OrderListResponse { } // ❌ Bad message create_user_command { } // Snake case message userDto { } // Camel case message User { } // Ambiguous ``` ### Fields ```protobuf // ✅ Good - snake_case message UserDto { int32 user_id = 1; string first_name = 2; string last_name = 3; google.protobuf.Timestamp created_at = 4; } // ❌ Bad message UserDto { int32 UserId = 1; // PascalCase string firstName = 2; // CamelCase string LastName = 3; // PascalCase } ``` ## Complete Example ```protobuf syntax = "proto3"; package ecommerce; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; option csharp_namespace = "ECommerce.Grpc"; // ================== // Services // ================== service CommandService { rpc CreateProduct (CreateProductCommand) returns (CreateProductResponse); rpc UpdateProduct (UpdateProductCommand) returns (google.protobuf.Empty); rpc DeleteProduct (DeleteProductCommand) returns (google.protobuf.Empty); rpc PlaceOrder (PlaceOrderCommand) returns (PlaceOrderResponse); } service QueryService { rpc GetProduct (GetProductQuery) returns (ProductDto); rpc ListProducts (ListProductsQuery) returns (ProductListResponse); rpc SearchProducts (SearchProductsQuery) returns (ProductSearchResponse); } // ================== // Commands // ================== message CreateProductCommand { string name = 1; string description = 2; double price = 3; int32 stock = 4; string category = 5; } message CreateProductResponse { int32 product_id = 1; } message UpdateProductCommand { int32 product_id = 1; google.protobuf.StringValue name = 2; google.protobuf.StringValue description = 3; google.protobuf.DoubleValue price = 4; google.protobuf.Int32Value stock = 5; } message DeleteProductCommand { int32 product_id = 1; } message PlaceOrderCommand { int32 customer_id = 1; repeated OrderItem items = 2; } message OrderItem { int32 product_id = 1; int32 quantity = 2; } message PlaceOrderResponse { int32 order_id = 1; double total_amount = 2; } // ================== // Queries // ================== message GetProductQuery { int32 product_id = 1; } message ListProductsQuery { int32 page = 1; int32 page_size = 2; } message SearchProductsQuery { string keyword = 1; string category = 2; google.protobuf.DoubleValue min_price = 3; google.protobuf.DoubleValue max_price = 4; } // ================== // DTOs // ================== message ProductDto { int32 id = 1; string name = 2; string description = 3; double price = 4; int32 stock = 5; string category = 6; google.protobuf.Timestamp created_at = 7; google.protobuf.Timestamp updated_at = 8; } message ProductListResponse { repeated ProductDto products = 1; int32 total_count = 2; int32 page = 3; int32 page_size = 4; } message ProductSearchResponse { repeated ProductDto products = 1; int32 total_count = 2; } ``` ## Best Practices ### ✅ DO - Use proto3 syntax - Use snake_case for field names - Use PascalCase for message/service names - Reserve deleted field numbers - Use 1-15 for frequently set fields - Import google common types - Document complex messages - Version your .proto files ### ❌ DON'T - Don't change field numbers - Don't reuse reserved numbers - Don't use negative field numbers - Don't mix naming conventions - Don't skip field numbers unnecessarily - Don't forget to import dependencies ## See Also - [gRPC Integration Overview](README.md) - [Getting Started](getting-started-grpc.md) - [Source Generators](source-generators.md) - [Service Implementation](service-implementation.md) - [Protocol Buffers Language Guide](https://protobuf.dev/programming-guides/proto3/)