English
Protobuf File Organization Guidelines: A Practical Guide to Splitting, Imports, and Common Type Management

Introduction: What does it look like when 50 files are crammed together?
In a code review, I encountered a scenario like this: a directory had more than 50 Proto files piled up, with no naming pattern at all, some were named mKfs.proto of (mixed case), some are called getqossa.proto of (all lowercase), and also bucketlink.proto and bucket_link.proto coexist.
What is even more troubling is that,response.proto is imported by almost all files, but you have no idea which types are public and which are private to a particular Service. Modify a common.proto It could affect 20 files, but no one knows the exact scope of impact.
This is not a joke; it's my personal experience.
This article summarizes the Proto file organization guidelines based on observations from this Code Review. We hope it helps you establish a clear file structure and make team collaboration smoother.
Statement: File organization is not a trivial matter; it is a reflection of engineering capability.
You can tell whether a project is good just by glancing at its directory structure. Disorganized Proto files indicate that the team lacks an engineering mindset. This is not a matter of appearances; it is a real problem that affects development efficiency and production stability.
I. Why do we need a unified file organization specification?
1.1 The Cost of Poor File Organization
| 问题 | consequences |
|---|---|
| Non-standard file naming | Cannot find the desired type definition. |
| Unclear responsibilities | The same type is repeatedly defined in multiple places. |
| Circular dependency | Compilation failed; unable to generate code. |
| Chaos in public type management | Changing one place affects the whole, and the scope of the change is uncontrollable. |
1.2 A True Lesson
At one point, the common response structure needed to be modified, but the developer didn't realize it.response.proto It was referenced by more than 20 files. I changed the field type but forgot to check all callers. After deployment, multiple services failed to serialize, and it took a full hour to roll back.
This problem would never have occurred if there were clear file organization guidelines.
II. File Naming Conventions
Specification requirements
Use all lowercase + underscore-separated (snake_case)
One Service corresponds to one file
Public type files are named as
common.proto
Correct example
proto/
├── common.proto // public type
├── user.proto // User Service
├── product.proto // Product Services
├── order.proto // Order Service
└── task.proto // Task ServiceError example
proto/
├── mKfs.proto // Mixed case
├── getUser.proto // camel hump
├── productdetail.proto // The two words are not separated by an underscore.
├── acl.proto // The abbreviation is unclear.
└── commonTypes.proto // camel humpWhy do it this way?
all lowercase: Avoid cross-platform case sensitivity issues
Underscore-separated Clear and easy to read
One Service, one file: Clear responsibilities, easy to maintain
III. Division of Document Responsibilities
3.1 Split by Service
Each Service has its own separate file:
proto/
├── common.proto
├── user_service.proto
├── product_service.proto
└── order_service.proto3.2 Organization of File Content
Each Service file is organized in the following order:
protobuf
// user_service.proto
// 1. syntax declaration
syntax = "proto3";
// 2. import dependencies
import "common.proto";
// 3. package declaration
package api;
// 4. Option configuration
option go_package = ".;api";
// 5. Enum Definitions (Enums used internally by the Service)
enum UserStatus {
USER_STATUS_UNSPECIFIED = 0;
USER_STATUS_ACTIVE = 1;
USER_STATUS_INACTIVE = 2;
}
// 6. Public message (type used internally by Service)
message UserInfo {
string user_id = 1;
string user_name = 2;
UserStatus status = 3;
}
// 7. Request/Response message
message GetUserRequest {
string user_id = 1;
}
message GetUserResponse {
CommonResponse result = 1;
UserInfo user = 2;
}
// 8. Service Definition
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}IV. Public Type Management
4.1 Unified response structure
Types shared by multiple Services should be uniformly placed in common.proto Chinese:
protobuf
// common.proto
syntax = "proto3";
package api;
option go_package = ".;api";
// Generic response structure
message CommonResponse {
int32 code = 1;
string message = 2;
}
// Generic pagination parameters
message PageRequest {
uint32 page_nubmer = 1;
uint32 page_size = 2;
}
// Generic pagination response
message PageResponse {
uint32 total = 1;
uint32 page_nubmer = 2;
uint32 page_size = 3;
}4.2 Avoid over-extraction
Only extract types that are truly shared by multiple Services., do not over-engineer for the sake of "reuse":
| Type | Should it be placed in common.proto? | Explanation |
|---|---|---|
| Generic response structure | ✅ Yes | All Services use |
| Pagination parameters | ✅ Yes | Multiple Services use |
| User information | ❌ No | Only UserService uses it. |
| Order Details | ❌ No | Only OrderService uses it. |
V. Import Management
5.1 Specification Requirements
Only import What is truly needed dependency on
Avoid circular dependencies
Use relative path Import
Correct example
protobuf
// user_service.proto
import "common.proto";
import "order_service.proto"; // Import only when neededIncorrect example
protobuf
// user_service.proto
import "common.proto";
import "product_service.proto"; // Unused imports
import "order_service.proto"; // Unused imports5.2 Circular Dependency Problem
Incorrect example:
user_service.proto → import "order_service.proto"
order_service.proto → import "user_service.proto" // ❌ Circular dependency!Solution:
Extract common types into
common.protoCheck whether the division of responsibilities is reasonable.
protobuf
// Correct approach
user_service.proto → import "common.proto"
order_service.proto → import "common.proto"VI. Version Management
6.1 Fields are only added, not modified or deleted.
protobuf
// Error: Delete the field directly
message UserInfo {
string user_id = 1;
// string old_field = 2; // ❌ Removing it will cause compatibility issues
}
// Correct: use reserved to mark as deprecated
message UserInfo {
string user_id = 1;
reserved 2; // Mark deprecated field numbers
reserved "old_field"; // Mark deprecated field name
string new_field = 3;
}6.2 Change Process for Public Types
Modify common.proto The public types therein must follow the following process:
Assess the scope of impact: Confirm which Services use this type
Backward compatibility: Only add new fields; do not modify existing field types.
Notify all teams Notify all dependent parties before the change.
Version Release Ensure all services are upgraded in sync
VII. Directory Structure Example
7.1 Recommended directory structure
proto/
├── common.proto # public type
├── user_service.proto # User Service
├── product_service.proto # Product Services
├── order_service.proto # Order Service
└── internal/ # Internal type (not exposed externally)
└── config.proto7.2 Directory Structure of Large Projects
proto/
├── common/
│ ├── common.proto # public type
│ └── types.proto # Common enums and constants
├── user/
│ ├── user_service.proto # User Service Definition
│ └── user_types.proto # User service internal type
├── product/
│ ├── product_service.proto
│ └── product_types.proto
└── third_party/ # Third-party dependencies
└── google/
└── api/
└── annotations.protoVIII. How to Implement File Organization Specifications
8.1 Introducing Lint Tools
buf Support Lint rules related to file organization:
yaml
# buf.yaml
version: v1
lint:
use:
- DEFAULT
except:
- PACKAGE_VERSION_SUFFIX8.2 Directory Structure Review Mandatory Inspection Items
In Code Review, changes to the directory structure of Proto files must be checked:
Whether the filename is all lowercase + underscores
Are file responsibilities clear?
Should common types be placed in common.proto?
Does import have unused dependencies?
Is there a circular dependency?
Do deprecated fields use the reserved marker?
Has it affected other dependent parties?
8.3 Establish team standards documentation
Organize the above specifications into a team document as a unified standard for the organization and management of Proto files.
9. Checklist Quick Reference Table
| Check item | Standard |
|---|---|
| File name | snake_case.proto |
| Service decomposition | One Service, one file |
| public type | Put them all together common.proto |
| Import path | Use relative paths |
| circular dependency | Prohibited |
| Deprecated field | Use reserved mark |
| Unused import | Prohibited |
Summary
The way Proto files are organized directly reflects a team's engineering capability.
A clear file structure lets newcomers get up to speed in half a day; a messy file structure leaves veterans spending a week looking for type definitions.
Three core principles:
Clear responsibilities: One Service per file, common types go in common
Dependency management: Avoid circular dependencies and promptly clean up unused imports
Version compatibility: Assess the scope of impact before changing public types
Finally, let me leave you with this: file organization is not a vanity project; it is a reflection of engineering capability.
Further Reading
This article is the third (final) article in the Protobuf series. Please refer to the first two articles: