From b3cf5bd3db06bd9a69846de96fac0b192a62031a Mon Sep 17 00:00:00 2001 From: Protocol Buffer Team Date: Mon, 5 Aug 2024 15:43:50 +0000 Subject: [PATCH] This documentation change includes the following: * Adds 1-1-1.md to describe the 1-1-1 rule/best practice * Groups some content under a new best-pratices.md topic * Updates the section in Dos and Don'ts for defining message types in separate files * Adds link to the style guide * Adds clarification information to the Java Generated Code guide PiperOrigin-RevId: 659566969 Change-Id: Ibc191468b743180ddc49ff149664fe8a5e938cdc --- content/programming-guides/1-1-1.md | 159 ++++++++++++++++++ content/programming-guides/best-practices.md | 14 ++ content/programming-guides/dos-donts.md | 18 +- content/programming-guides/style.md | 4 +- content/reference/java/java-generated.md | 6 +- .../reference/protobuf/edition-2023-spec.md | 8 +- 6 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 content/programming-guides/1-1-1.md create mode 100644 content/programming-guides/best-practices.md diff --git a/content/programming-guides/1-1-1.md b/content/programming-guides/1-1-1.md new file mode 100644 index 000000000..a13da707c --- /dev/null +++ b/content/programming-guides/1-1-1.md @@ -0,0 +1,159 @@ ++++ +title = "1-1-1 Rule" +weight = 105 +linkTitle = "1-1-1 Rule" +description = "All proto definitions should have one top-level element and build target per file." +type = "docs" ++++ + +The 1-1-1 rule has the following elements: + +* One `proto_library` rule +* One source `.proto` file +* One top-level entity (message, enum, or extension) + +When defining a proto schema, you should have a single message, enum, extension, +service, or group of cyclic dependencies per file. This makes refactoring +easier. Moving files when they're separated is much easier than extracting +messages from a file with other messages. Following this practice also helps to +keep the proto schema files smaller, which enhances maintainability. + +One place that modularity of proto schema files is important is when creating +gRPC +definitions. The following set of proto files shows modular structure. + +**student_id.proto** + +```proto +edition = "2023"; + +package my.package; + +message StudentID { + string value = 1; +} +``` + +**full_name.proto** + +```proto +edition = "2023"; + +package my.package; + +message FullName { + string family_name = 1; + string given_name = 2; +} +``` + +**student.proto** + +```proto +edition = "2023"; + +package my.package; + +import "student_id.proto"; +import "full_name.proto"; + +message Student { + StudentId id = 1; + FullName name = 2; +} +``` + +**create_student_request.proto** + +```proto +edition = "2023"; + +package my.package; + +import "full_name.proto"; + +message CreateStudentRequest { + FullName name = 1; +} +``` + +**create_student_response.proto** + +```proto +edition = "2023"; + +package my.package; + +import "student.proto"; + +message CreateStudentResponse { + Student student = 1; +} +``` + +**get_student_request.proto** + +```proto +edition = "2023"; + +package my.package; + +import "student_id.proto"; + +message GetStudentRequest { + StudentID id = 1; +} +``` + +**get_student_response.proto** + +```proto +edition = "2023"; + +package my.package; + +import "student.proto"; + +message GetStudentResponse { + Student student = 1; +} +``` + +**student_service.proto** + +```proto +edition = "2023"; + +package my.package; + +import "create_student_request.proto"; +import "create_student_response.proto"; +import "get_student_request.proto"; +import "get_student_response.proto"; + +service StudentService { + rpc CreateStudent(CreateStudentRequest) returns (CreateStudentResponse); + rpc GetStudent(GetStudentRequest) returns (GetStudentResponse); +} +``` + +The service definition and each of the message definitions are each in their own +file, and you use includes to give access to the messages from other schema +files. + +In this example, `Student`, `StudentID`, and `FullName` are domain types that +are reusable across requests and responses. The top-level request and response +protos are unique to each service+method. + +If you later need to add a `middle_name` field to the `FullName` message, you +won't need to update every individual top-level message with that new field. +Likewise, if you need to update `Student` with more information, all the +requests and responses get the update. Further, `StudentID` might update to be a +multi-part ID. + +Lastly, having even simple types like `StudentID` wrapped as a message means +that you have created a type that has semantics and consolidated documentation. +For something like `FullName` you'll need to be careful with where this PII gets +logged; this is another advantage of not repeating these fields in multiple +top-level messages. You can tag those fields in one place as sensitive +and exclude them from logging. diff --git a/content/programming-guides/best-practices.md b/content/programming-guides/best-practices.md new file mode 100644 index 000000000..8f7831c9a --- /dev/null +++ b/content/programming-guides/best-practices.md @@ -0,0 +1,14 @@ ++++ +title = "Proto Best Practices" +weight = 90 +description = "An overview of best practices topics." +type = "docs" +no_list = "true" ++++ + +Best practices content for defining and using protos exists in the following +topics: + +* [Proto Best Practices](/programming-guides/dos-donts) +* [API Best Practices](/programming-guides/api) +* [1-1-1 Rule](/programming-guides/1-1-1) diff --git a/content/programming-guides/dos-donts.md b/content/programming-guides/dos-donts.md index 65d3abd40..c09efb945 100644 --- a/content/programming-guides/dos-donts.md +++ b/content/programming-guides/dos-donts.md @@ -146,12 +146,20 @@ when a perfectly suitable common type already exists! -## **Do** Define Widely-used Message Types in Separate Files {#separate-files} +## **Do** Define Message Types in Separate Files {#separate-files} -If you're defining message types or enums that you hope/fear/expect to be widely -used outside your immediate team, consider putting them in their own file with -no dependencies. Then it's easy for anyone to use those types without -introducing the transitive dependencies in your other proto files. +When defining a proto schema, you should have a single message, enum, extension, +service, or group of cyclic dependencies per file. This makes refactoring +easier. Moving files when they're separated is much easier than extracting +messages from a file with other messages. Following this practice also helps to +keep the proto schema files smaller, which enhances maintainability. + +If they will be widely used outside of your project, consider putting them in +their own file with no dependencies. Then it's easy for anyone to use those +types without introducing the transitive dependencies in your other proto files. + +For more on this topic, see +[1-1-1 Rule](/programming-guides/1-1-1.md). diff --git a/content/programming-guides/style.md b/content/programming-guides/style.md index 7a4b40a5d..413ace225 100644 --- a/content/programming-guides/style.md +++ b/content/programming-guides/style.md @@ -126,7 +126,9 @@ For more service-related guidance, see [Create Unique Protos per Method](/programming-guides/api#unique-protos) and [Don't Include Primitive Types in a Top-level Request or Response Proto](/programming-guides/api#dont-include-primitive-types) -in the API Best Practices topic. +in the API Best Practices topic, and +[Define Messages in Separate Files](/programming-guides/dos-donts.md#separate-files) +in Proto Best Practices. ## Things to Avoid {#avoid} diff --git a/content/reference/java/java-generated.md b/content/reference/java/java-generated.md index b30d50cbb..63b3f8942 100644 --- a/content/reference/java/java-generated.md +++ b/content/reference/java/java-generated.md @@ -25,7 +25,7 @@ The protocol buffer compiler produces Java output when invoked with the `--java_out=` command-line flag. The parameter to the `--java_out=` option is the directory where you want the compiler to write your Java output. For each `.proto` file input, the compiler creates a wrapper `.java` file containing a -Java class which represents the `.proto` file itself. +Java class that represents the `.proto` file itself. If the `.proto` file contains a line like the following: @@ -37,12 +37,12 @@ Then the compiler will also create separate `.java` files for each of the classes/enums which it will generate for each top-level message, enumeration, and service declared in the `.proto` file. -Otherwise (i.e. when the `java_multiple_files` option is disabled; which is the +Otherwise (when the `java_multiple_files` option is disabled, which is the default), the aforementioned wrapper class will also be used as an outer class, and the generated classes/enums for each top-level message, enumeration, and service declared in the `.proto` file will all be nested within the outer wrapper class. Thus the compiler will only generate a single `.java` file for -the entire `.proto` file. +the entire `.proto` file, and it will have an extra layer in the package The wrapper class's name is chosen as follows: If the `.proto` file contains a line like the following: diff --git a/content/reference/protobuf/edition-2023-spec.md b/content/reference/protobuf/edition-2023-spec.md index b78a25355..a2c2c9cbf 100644 --- a/content/reference/protobuf/edition-2023-spec.md +++ b/content/reference/protobuf/edition-2023-spec.md @@ -164,7 +164,7 @@ fields, group fields, oneof fields, or map fields. A field has a label, type and field number. ``` -label = "required" | "optional" | "repeated" +label = [ "repeated" ] type = "double" | "float" | "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" | "fixed32" | "fixed64" | "sfixed32" | "sfixed64" | "bool" | "string" | "bytes" | messageType | enumType @@ -173,10 +173,10 @@ fieldNumber = intLit; ### Normal field {#normal_field} -Each field has label, type, name and field number. It may have field options. +Each field has a label, type, name, and field number. It may have field options. ``` -field = label type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";" +field = [label] type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";" fieldOptions = fieldOption { "," fieldOption } fieldOption = optionName "=" constant ``` @@ -184,7 +184,7 @@ fieldOption = optionName "=" constant Examples: ```proto -optional foo.bar nested_message = 2; +foo.bar nested_message = 2; repeated int32 samples = 4 [packed=true]; ```