--- tags: Spring Boot --- # Spring 實務物件命名慣例 ```mermaid flowchart LR n1["External"] n5["Database"] subgraph s1["Server"] n2["Controller"] n3["Service"] n4["Repository"] end n5@{ shape: db} n1["External"] -- XXXRequest --> n2 n2 -- XXXResponse --> n1 n2 <-- XXXDTO --> n3 n3 <-- XXXEntity --> n4 n4 <-- XXXEntity --> n5["Database"] ``` ## 1. **外部 (External) -> Controller** - **實務命名**: - 通常是 `XxxRequest` 或 `XxxReq`,例如 `CreateUserReq`, `UpdateOrderRequest`。 - 小型專案可能直接使用簡單名稱,如 `UserRequest`,但大型專案會更具體,例如 `RegisterUserRequest`, `UpdateUserProfileRequest`。 - 如果使用 OpenAPI/Swagger 生成 API,命名可能與 API 文件中的 schema 一致,例如 `UserCreationSchema`。 - 欄位名稱通常與前端 JSON 結構一致,遵循 駝峰命名(camelCase)(如 `userName`, `orderId`),但某些團隊可能使用 蛇行命名(snake_case)(如 `user_name`)以符合前端規範。 - **實務細節**: - 物件通常包含驗證註解(如 `@NotBlank`, `@Email`),例如: ```java public class CreateUserRequest { @NotBlank private String userName; @Email private String email; } ``` - **挑戰**: - 如果前端和後端命名規範不一致,可能需要額外的映射邏輯(例如使用 MapStruct)。 - 過多的 Request DTO 可能導致類別爆炸,某些團隊會重用 DTO(例如 `UserRequest` 用於創建和更新)。 - **實務建議**: - 保持命名清晰,反映 API 的意圖(例如 `RegisterUserRequest` 而非泛用的 `UserRequest`)。 - 如果專案規模大,使用工具(如 MapStruct 或 Lombok)減少 樣板程式碼(boilerplate code)。 --- ## 2. **Controller -> Service** - **實務命名**: - 常見名稱包括 `XxxDTO`(如 `UserDTO`, `OrderDTO`)或 `XxxCommand`(如 `CreateUserCommand`)。 - 某些團隊直接傳遞 Request 物件(例如 `CreateUserRequest`),特別是在簡單場景中。 - 如果業務邏輯複雜,可能使用更具體的名稱,例如 `UserRegistrationDTO`, `OrderFulfillmentCommand`。 - **實務細節**: - Controller 通常會將 Request DTO 轉換為 Service 層的 DTO,這一步可能涉及欄位映射或資料轉換。 - 範例: ```java UserDTO userDTO = new UserDTO(request.getUserName(), request.getEmail()); userService.createUser(userDTO); ``` - 在 CQRS(Command Query Responsibility Segregation)架構中,常用 `XxxCommand` 來表示寫入操作,例如 `CreateUserCommand`。 - 小型專案可能直接傳遞原始資料(例如 `String userName, String email`),但這在維護性上較差。 - **挑戰**: - 如果 Request DTO 和 Service DTO 差異不大,團隊可能傾向於重用物件,但這可能導致層次耦合。 - 過多的 DTO 類別可能增加維護成本,特別是在快速迭代的專案中。 - **實務建議**: - 使用工具(如 MapStruct)自動化 DTO 之間的映射。 - 在簡單場景中,可以考慮重用 Request DTO,但需確保層次職責分離。 - 如果業務邏輯複雜,建議使用專用的 Command 物件來表達意圖。 --- ## 3. **Service -> Repository** - **實務命名**: - 大多數情況下直接使用 **Entity**,例如 `User`, `Order`, `Product`。 - 如果需要傳遞非 Entity 的資料,可能使用 `XxxDTO`(如 `UserDTO`)或 `XxxVO`(Value Object,如 `UserVO`)。 - 某些團隊可能使用簡化的物件,例如 `UserCriteria`(用於查詢條件)或 `UserSummary`(用於聚合結果)。 - **實務細節**: - Entity 是最常見的傳遞物件,直接對應資料庫表結構,例如: ```java User user = new User(dto.getUserName(), dto.getEmail()); userRepository.save(user); ``` - 如果查詢結果需要特殊結構(例如聯表查詢),可能返回 DTO,例如: ```java public class UserSummaryDTO { private Long id; private String userName; } ``` - 在高併發或複雜場景中,可能使用專門的查詢物件(如 `UserQuery`)來傳遞查詢參數。 - **挑戰**: - Entity 過於複雜(例如包含大量欄位)可能導致性能問題,特別是在序列化或查詢時。 - 如果 Service 層需要聚合多個 Entity 的資料,DTO 的設計和映射會變得繁瑣。 - **實務建議**: - 優先使用 Entity 進行 CRUD 操作,但在複雜查詢或聚合時使用 DTO。 - 使用 JPA 的 `@Query` 或 QueryDSL 來簡化查詢邏輯,並返回專用的 DTO。 - 避免在 Service 層直接操作底層資料庫結構(例如 SQL 結果集),保持 Repository 層的封裝。 --- ## 4. **Repository -> Database** - **實務命名**: - 幾乎總是 **Entity**,名稱與資料庫表名對應,例如 `User`, `Order`, `Product`。 - 如果表名有前綴(如 `tbl_users`),實體名稱通常去掉前綴(`User`)。 - 欄位名稱與資料庫欄位對應,通常使用 camelCase(例如 `userId` 對應 `user_id`)。 - **實務細節**: - Entity 使用 JPA 註解(如 `@Entity`, `@Table`, `@Column`)映射到資料庫,例如: ```java @Entity @Table(name = "users") public class User { @Id @GeneratedValue private Long id; @Column(name = "user_name") private String userName; } ``` - 在某些場景中,可能使用 `@Embeddable` 物件來表示複合欄位(例如地址資訊)。 - 如果資料庫結構複雜,可能使用 `@SecondaryTable` 或 `@JoinTable` 來處理多表映射。 - **挑戰**: - 如果資料庫結構與業務邏輯不一致(例如遺留系統),Entity 的設計可能變得複雜。 - 過多的 Entity 關聯(例如 `@OneToMany`, `@ManyToMany`)可能導致性能問題(如 N+1 查詢)。 - **實務建議**: - 保持 Entity 簡單,僅包含必要的欄位和關聯。 - 使用 `@BatchSize` 或 `@Fetch` 優化關聯查詢性能。 - 如果資料庫結構複雜,考慮使用 View 或 DTO 來簡化資料訪問。 --- ## 實務中的常見模式與注意事項 1. **DTO 重用與簡化**: - 小型專案可能會重用 DTO(例如 `UserDTO` 用於 Controller 和 Service),以減少類別數量。 - 但在大型專案中,層次分離更重要,每層通常有專用的 DTO。 2. **工具與框架**: - **MapStruct**:用於自動映射 Request DTO、Service DTO 和 Entity,減少手動轉換的程式碼。 - **Lombok**:使用 `@Data`, `@Builder` 等註解減少 boilerplate 程式碼。 - **Swagger/OpenAPI**:用於生成 Request DTO 和 API 文件,確保命名一致。 3. **命名規範的統一**: - 團隊通常會制定命名規範(例如 `XxxRequest`, `XxxDTO`, `XxxEntity`),並記錄在專案 Wiki 或 Coding Guideline 中。 - 常見規範: - Request:`XxxRequest` 或 `XxxReq`。 - DTO:`XxxDTO` 或 `XxxVo`。 - Entity:`Xxx`(無後綴,直接表達資源)。 - Command:`XxxCommand`(用於 CQRS 或事件驅動架構)。 4. **微服務中的特殊情況**: - 在微服務架構中,物件可能需要跨服務傳遞,通常使用 `XxxApiModel` 或 `XxxContract` 來表示 API 契約。 - 跨服務的 DTO 可能需要版本控制(例如 `UserV1DTO`, `UserV2DTO`)。 5. **性能與維護性**: - 過多的 DTO 可能增加維護成本,需權衡層次分離與程式碼簡潔。 - 在高性能場景中,可能使用原生查詢或投影(Projection)來直接返回 DTO,繞過 Entity。 --- ## 實務範例 假設一個用戶註冊場景的物件流: 1. **外部請求**: ```java public class RegisterUserRequest { @NotBlank private String userName; @Email private String email; @Size(min = 8) private String password; } ``` 2. **Controller -> Service**: ```java public class UserRegistrationDTO { private String userName; private String email; private String passwordHash; } ``` - Controller 使用 MapStruct 將 `RegisterUserRequest` 轉為 `UserRegistrationDTO`,並進行密碼加密。 3. **Service -> Repository**: ```java @Entity @Table(name = "users") public class User { @Id @GeneratedValue private Long id; @Column(name = "user_name") private String userName; private String email; private String passwordHash; } ``` 4. **Repository -> Database**: - `User` 實體直接映射到 `users` 表。 --- ## 總結 實務中,物件命名通常遵循理論慣例(`XxxRequest`, `XxxDTO`, `XxxEntity`),但會根據專案需求進行調整。關鍵在於: - **清晰性**:命名應反映物件的職責和上下文。 - **一致性**:遵循團隊規範,確保程式碼可讀性。 - **平衡性**:在層次分離和程式碼簡潔之間找到平衡。 - **工具支援**:使用 MapStruct、Lombok 等工具減少重複工作。