@@ -8,163 +8,230 @@ aliases = "/programming-guides/1-1-1"
88+++
99
1010The "1-1-1" best practice advocates structuring definitions with one top-level
11- entity (message, enum, or extension) per ` .proto ` file, corresponding to a
12- single ` proto_library ` build rule. This approach promotes small, modular proto
13- definitions. Key benefits include simplified refactoring, potentially improved
14- build times, and smaller binary sizes due to minimized transitive dependencies.
11+ entry point (exported message, exported enum, service, or extension) per
12+ ` .proto ` file, corresponding to a single ` proto_library ` build rule. This
13+ approach promotes small, modular proto definitions. Key benefits include
14+ simplified refactoring, potentially improved build times, and smaller binary
15+ sizes due to minimized transitive dependencies.
1516
16- ## Rationale
17+ ## Rationale {#rationale}
1718
18- The 1-1-1 best practice is to keep every proto_library and .proto file as small
19- as is reasonable, with the ideal being:
19+ The 1-1-1 best practice is to keep every ` proto_library ` and ` .proto ` file as
20+ small as is reasonable, with the ideal ratio being:
2021
2122* One ` proto_library ` build rule
2223* One source ` .proto ` file
23- * One top-level entity (message, enum, or extension)
24+ * One top-level entry point (` export message ` , ` export enum ` , ` service ` , or
25+ ` extend ` )
2426
25- Having the fewest number of message, enum, extension, and services as you
26- reasonably can makes refactoring easier. Moving files when they're separated is
27- much easier than extracting messages from a file with other messages.
27+ Having the fewest number of exported messages, enums, extensions, and services
28+ as you reasonably can makes refactoring easier. Moving files when they're
29+ separated is much easier than extracting messages from a file with other
30+ messages.
2831
2932Following this practice can help build times and binary size by reducing the
3033size of your transitive dependencies in practice: when some code only needs to
31- use one enum, under a 1-1-1 design it can depend just on the .proto file that
34+ use one enum, under a 1-1-1 design it can depend just on the ` .proto ` file that
3235defines that enum and avoid incidentally pulling in a large set of transitive
3336dependencies that may only be used by another message defined in the same file.
3437
3538There are cases where the 1-1-1 ideal is not possible (circular dependencies),
3639not ideal (extremely conceptually coupled messages which have readability
3740benefits by being co-located), or where some of the downsides don't apply (when
38- a .proto file has no imports, then there are no technical concerns about the
41+ a ` .proto ` file has no imports, then there are no technical concerns about the
3942size of transitive dependencies). As with any best practice, use good judgment
4043for when to diverge from the guideline.
4144
42- One place that modularity of proto schema files is important is when creating
43- gRPC
44- definitions. The following set of proto files shows modular structure.
45+ ## Symbol Visibility in Edition 2024 and Later {#2024 -later}
4546
46- ** student_id.proto**
47+ Starting in ` edition = "2024" ` , Protocol Buffers introduces
48+ [ Symbol Visibility] ( /programming-guides/symbol_visibility )
49+ with ` export ` and ` local ` keywords
50+ and the ` features.default_symbol_visibility ` option.
4751
48- ``` proto
49- edition = "2023";
52+ > ** Scope Note:** Symbol visibility controls ** only** the Protobuf compiler
53+ > (` protoc ` ) import behavior when resolving references between ` .proto ` files.
54+ > It has ** no impact** on language-specific generated code or consumers of proto
55+ > descriptors.
5056
51- package my.package;
57+ ### Recommended Setting: ` STRICT ` {#strict}
5258
53- message StudentId {
54- string value = 1;
55- }
59+ For all new ` .proto ` files in Edition 2024 and later file-option
60+ ` features.default_symbol_visibility ` should be set to STRICT. This is the
61+ default in edition 2026:
62+
63+ ``` proto
64+ option features.default_symbol_visibility = STRICT;
5665```
5766
58- ** full_name.proto **
67+ In ` STRICT ` mode:
5968
60- ``` proto
61- edition = "2023";
69+ * All symbols default to ` local ` .
70+ * Only top-level ` message ` and ` enum ` declarations intended as entry points
71+ may be marked with ` export ` .
72+ * Nested symbols cannot use ` export ` and are enforced ` local ` (preventing
73+ accidental imports of nested types across file boundaries).
6274
63- package my.package;
75+ Top-level messages are designed to be ** atomic** with regards to reuse. Nested
76+ messages and enums are inherently private to their parent container. Attempting
77+ to use another message's nested types as reusable vocabulary subverts the "use
78+ all or none" design of ` .proto `
79+ files. Types intended for independent reuse should be
80+ defined as top-level symbols in dedicated ` .proto ` files.
6481
65- message FullName {
66- string family_name = 1;
67- string given_name = 2;
68- }
69- ```
82+ ### Relaxation for ` local ` Helper Symbols {#relaxation}
7083
71- ** student.proto**
84+ Symbol visibility relaxes strict 1-1-1 for internal helper symbols: a file may
85+ contain multiple ` local message ` or ` local enum ` definitions, provided they
86+ support a single top-level entry point (forming a single cluster of ` local `
87+ symbols supporting one entry point). Because ` local ` symbols cannot be imported
88+ into other ` .proto ` files, they do not introduce transitive dependency bloat to
89+ importers.
90+
91+ ### Service Request and Response Messages {#req-res}
92+
93+ Co-locating a ` service ` and its request/response ` message ` s in a single file is
94+ acceptable in Edition 2024 if those request and response messages are marked
95+ ` local ` (or default to ` local ` under ` STRICT ` mode). This prevents them from
96+ being imported into other ` .proto ` files and avoids pulling the ` service `
97+ definition and its gRPC dependencies into unrelated build
98+ targets.
99+
100+ However, if a request or response message ever needs to be shared or imported
101+ outside of the service file (for example, for logging, data retention, or reuse
102+ in another RPC), it should be defined as a top-level ` export message ` in its own
103+ dedicated ` .proto ` file.
104+
105+ ## Modular Schema Example {#example}
106+
107+ One place that modularity of proto schema files is important is when
108+ creating gRPC definitions. The following set of proto
109+ files shows modular structure in Edition 2024.
110+
111+ ** student_id.proto**
72112
73113``` proto
74- edition = "2023 ";
114+ edition = "2024 ";
75115
76116package my.package;
77117
78- import "student_id.proto";
79- import "full_name.proto";
118+ option features.default_symbol_visibility = STRICT;
80119
81- message Student {
82- StudentId id = 1;
83- FullName name = 2;
120+ export message StudentId {
121+ string value = 1;
84122}
85123```
86124
87- ** create_student_request .proto**
125+ ** full_name .proto**
88126
89127``` proto
90- edition = "2023 ";
128+ edition = "2024 ";
91129
92130package my.package;
93131
94- import "full_name.proto" ;
132+ option features.default_symbol_visibility = STRICT ;
95133
96- message CreateStudentRequest {
97- FullName name = 1;
134+ export message FullName {
135+ string family_name = 1;
136+ string given_name = 2;
98137}
99138```
100139
101- ** create_student_response .proto**
140+ ** student .proto**
102141
103142``` proto
104- edition = "2023 ";
143+ edition = "2024 ";
105144
106145package my.package;
107146
108- import "student.proto" ;
147+ option features.default_symbol_visibility = STRICT ;
109148
110- message CreateStudentResponse {
111- Student student = 1;
149+ import "student_id.proto";
150+ import "full_name.proto";
151+
152+ export message Student {
153+ StudentId id = 1;
154+ FullName name = 2;
112155}
113156```
114157
115158** get_student_request.proto**
116159
117160``` proto
118- edition = "2023 ";
161+ edition = "2024 ";
119162
120163package my.package;
121164
165+ option features.default_symbol_visibility = STRICT;
166+
122167import "student_id.proto";
168+ import "external/expensive/dependency.proto";
123169
124- message GetStudentRequest {
170+ // Export and stand-alone because it is used in both
171+ // student_request_log.proto AND student_service.proto.
172+ export message GetStudentRequest {
125173 StudentId id = 1;
174+ expensive.external.OtherMessage message = 2;
126175}
127176```
128177
129- ** get_student_response .proto**
178+ ** student_request_log .proto**
130179
131180``` proto
132- edition = "2023 ";
181+ edition = "2024 ";
133182
134183package my.package;
135184
136- import "student.proto" ;
185+ option features.default_symbol_visibility = STRICT ;
137186
138- message GetStudentResponse {
139- Student student = 1;
187+ import "get_student_request.proto";
188+
189+ // log any requests made.
190+ message StudentRequestLog {
191+ string requesting_user = 1;
192+ GetStudentRequest request = 2;
140193}
141194```
142195
143196** student_service.proto**
144197
145198``` proto
146- edition = "2023 ";
199+ edition = "2024 ";
147200
148201package my.package;
149202
150- import "create_student_request.proto";
151- import "create_student_response.proto";
203+ option features.default_symbol_visibility = STRICT;
204+
205+ // imported because we log this message in student_request_log.proto meaning the
206+ // message needs to be in its own file and `export`.
152207import "get_student_request.proto";
153- import "get_student_response.proto";
208+
209+ local message GetStudentResponse {
210+ Student student = 1;
211+ }
212+
213+ local message CreateStudentRequest {
214+ FullName name = 1;
215+ }
216+
217+ local message CreateStudentResponse {
218+ Student student = 1;
219+ }
154220
155221service StudentService {
156222 rpc CreateStudent(CreateStudentRequest) returns (CreateStudentResponse);
157223 rpc GetStudent(GetStudentRequest) returns (GetStudentResponse);
158224}
159225```
160226
161- The service definition and each of the message definitions are each in their own
162- file, and you use includes to give access to the messages from other schema
163- files.
227+ The service definition and 3 of its 4 message definitions are defined in
228+ ` student_service.proto ` . Those messages are all marked ` local ` making it safe to
229+ co-locate with the ` service ` definition without introducing unnecessary
230+ dependencies. ` GetStudentRequest ` is shared by the ` StudentService ` and
231+ ` StudentRequestLog ` , and so it should be in its own file.
164232
165- In this example, ` Student ` , ` StudentId ` , and ` FullName ` are domain types that
166- are reusable across requests and responses. The top-level request and response
167- protos are unique to each service+method.
233+ In this example, ` Student ` , ` StudentId ` , and ` FullName ` are public domain types
234+ that are reusable in logs, requests, responses and external protos.
168235
169236If you later need to add a ` middle_name ` field to the ` FullName ` message, you
170237won't need to update every individual top-level message with that new field.
0 commit comments