Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions content/programming-guides/1-1-1.md
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 14 additions & 0 deletions content/programming-guides/best-practices.md
Original file line number Diff line number Diff line change
@@ -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)
18 changes: 13 additions & 5 deletions content/programming-guides/dos-donts.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,20 @@ when a perfectly suitable common type already exists!

<a id="do-define-widely-used-message-types-in-separate-files"></a>

## **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).

<a id="dont-change-the-default-value-of-a-field"></a>

Expand Down
4 changes: 3 additions & 1 deletion content/programming-guides/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down
6 changes: 3 additions & 3 deletions content/reference/java/java-generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions content/reference/protobuf/edition-2023-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -173,18 +173,18 @@ 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
```

Examples:

```proto
optional foo.bar nested_message = 2;
foo.bar nested_message = 2;
repeated int32 samples = 4 [packed=true];
```

Expand Down