|
| 1 | ++++ |
| 2 | +title = "1-1-1 Rule" |
| 3 | +weight = 105 |
| 4 | +linkTitle = "1-1-1 Rule" |
| 5 | +description = "All proto definitions should have one top-level element and build target per file." |
| 6 | +type = "docs" |
| 7 | ++++ |
| 8 | + |
| 9 | +The 1-1-1 rule has the following elements: |
| 10 | + |
| 11 | +* One `proto_library` rule |
| 12 | +* One source `.proto` file |
| 13 | +* One top-level entity (message, enum, or extension) |
| 14 | + |
| 15 | +When defining a proto schema, you should have a single message, enum, extension, |
| 16 | +service, or group of cyclic dependencies per file. This makes refactoring |
| 17 | +easier. Moving files when they're separated is much easier than extracting |
| 18 | +messages from a file with other messages. Following this practice also helps to |
| 19 | +keep the proto schema files smaller, which enhances maintainability. |
| 20 | + |
| 21 | +One place that modularity of proto schema files is important is when creating |
| 22 | +gRPC |
| 23 | +definitions. The following set of proto files shows modular structure. |
| 24 | + |
| 25 | +**student_id.proto** |
| 26 | + |
| 27 | +```proto |
| 28 | +edition = "2023"; |
| 29 | +
|
| 30 | +package my.package; |
| 31 | +
|
| 32 | +message StudentID { |
| 33 | + string value = 1; |
| 34 | +} |
| 35 | +``` |
| 36 | + |
| 37 | +**full_name.proto** |
| 38 | + |
| 39 | +```proto |
| 40 | +edition = "2023"; |
| 41 | +
|
| 42 | +package my.package; |
| 43 | +
|
| 44 | +message FullName { |
| 45 | + string family_name = 1; |
| 46 | + string given_name = 2; |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +**student.proto** |
| 51 | + |
| 52 | +```proto |
| 53 | +edition = "2023"; |
| 54 | +
|
| 55 | +package my.package; |
| 56 | +
|
| 57 | +import "student_id.proto"; |
| 58 | +import "full_name.proto"; |
| 59 | +
|
| 60 | +message Student { |
| 61 | + StudentId id = 1; |
| 62 | + FullName name = 2; |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +**create_student_request.proto** |
| 67 | + |
| 68 | +```proto |
| 69 | +edition = "2023"; |
| 70 | +
|
| 71 | +package my.package; |
| 72 | +
|
| 73 | +import "full_name.proto"; |
| 74 | +
|
| 75 | +message CreateStudentRequest { |
| 76 | + FullName name = 1; |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +**create_student_response.proto** |
| 81 | + |
| 82 | +```proto |
| 83 | +edition = "2023"; |
| 84 | +
|
| 85 | +package my.package; |
| 86 | +
|
| 87 | +import "student.proto"; |
| 88 | +
|
| 89 | +message CreateStudentResponse { |
| 90 | + Student student = 1; |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +**get_student_request.proto** |
| 95 | + |
| 96 | +```proto |
| 97 | +edition = "2023"; |
| 98 | +
|
| 99 | +package my.package; |
| 100 | +
|
| 101 | +import "student_id.proto"; |
| 102 | +
|
| 103 | +message GetStudentRequest { |
| 104 | + StudentID id = 1; |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +**get_student_response.proto** |
| 109 | + |
| 110 | +```proto |
| 111 | +edition = "2023"; |
| 112 | +
|
| 113 | +package my.package; |
| 114 | +
|
| 115 | +import "student.proto"; |
| 116 | +
|
| 117 | +message GetStudentResponse { |
| 118 | + Student student = 1; |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +**student_service.proto** |
| 123 | + |
| 124 | +```proto |
| 125 | +edition = "2023"; |
| 126 | +
|
| 127 | +package my.package; |
| 128 | +
|
| 129 | +import "create_student_request.proto"; |
| 130 | +import "create_student_response.proto"; |
| 131 | +import "get_student_request.proto"; |
| 132 | +import "get_student_response.proto"; |
| 133 | +
|
| 134 | +service StudentService { |
| 135 | + rpc CreateStudent(CreateStudentRequest) returns (CreateStudentResponse); |
| 136 | + rpc GetStudent(GetStudentRequest) returns (GetStudentResponse); |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +The service definition and each of the message definitions are each in their own |
| 141 | +file, and you use includes to give access to the messages from other schema |
| 142 | +files. |
| 143 | + |
| 144 | +In this example, `Student`, `StudentID`, and `FullName` are domain types that |
| 145 | +are reusable across requests and responses. The top-level request and response |
| 146 | +protos are unique to each service+method. |
| 147 | + |
| 148 | +If you later need to add a `middle_name` field to the `FullName` message, you |
| 149 | +won't need to update every individual top-level message with that new field. |
| 150 | +Likewise, if you need to update `Student` with more information, all the |
| 151 | +requests and responses get the update. Further, `StudentID` might update to be a |
| 152 | +multi-part ID. |
| 153 | + |
| 154 | +Lastly, having even simple types like `StudentID` wrapped as a message means |
| 155 | +that you have created a type that has semantics and consolidated documentation. |
| 156 | +For something like `FullName` you'll need to be careful with where this PII gets |
| 157 | +logged; this is another advantage of not repeating these fields in multiple |
| 158 | +top-level messages. You can tag those fields in one place as sensitive |
| 159 | +and exclude them from logging. |
0 commit comments