Skip to content

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

Protobuf Naming Convention Best Practices: A Diagram Illustrating the Importance of Naming Conventions from Code Reviews of 50+ Proto Files

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 namingCannot find the desired type definition.
Unclear responsibilitiesThe same type is repeatedly defined in multiple places.
Circular dependencyCompilation failed; unable to generate code.
Chaos in public type managementChanging 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 Service

Error 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 hump

Why 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.proto

3.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":

TypeShould it be placed in common.proto?Explanation
Generic response structure✅ YesAll Services use
Pagination parameters✅ YesMultiple Services use
User information❌ NoOnly UserService uses it.
Order Details❌ NoOnly 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 needed

Incorrect example ​

protobuf
// user_service.proto
import "common.proto";
import "product_service.proto";  // Unused imports
import "order_service.proto";    // Unused imports

5.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.proto

  • Check 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 ​

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.proto

7.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.proto

VIII. 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_SUFFIX

8.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 itemStandard
File namesnake_case.proto
Service decompositionOne Service, one file
public typePut them all together common.proto
Import pathUse relative paths
circular dependencyProhibited
Deprecated fieldUse reserved mark
Unused importProhibited

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:

📖Protobuf Naming Conventions Best Practices

📖Detailed Explanation of Protobuf Data Types

Last updated on11/07/2026, 02:16 PM
@fishfinal | @fishfinals | [email protected]