
我見過太多SpringBoot項目啟動類后面跟著五個包controller、service、dao、entity、config。三個月后service包下膨脹出幾百個類命名從UserService到UserServiceImpl再到UserBizService沒人說得清某個功能到底在哪個類里。改一個需求要翻三四個包加一個字段要動五個文件。這種結構的核心問題不是“分不夠細”而是包結構完全是技術分層的復讀從未表達過業務邊界。清晰的SpringBoot項目結構第一原則是包結構映射業務域而不是映射技術角色。技術分層是運行時依賴的約定目錄是用來讓人快速理解“這個系統管哪些事”的。當你打開項目的頂級包看到的應當是“訂單”“商品”“用戶”這樣的業務域而不是“controller”“service”“dao”這樣的技術層。業務域之間天然隔離改動彼此獨立而技術層之間則存在強耦合任何需求都會穿透三層導致改動無法局部化。有人會問那controller、service這些放哪里答案是放在業務域內部作為二級包。比如com.company.order下面可以有controller、service、repository、domain但“tech.layer”作為頂級目錄只會讓代碼按照“我是什么”分類而不是按照“我屬于哪塊業務”分類。按業務域組織后你要改訂單相關功能直接進入order域無需在全局幾十個技術包里做“語境切換”。這才是“清晰”的第一含義——減少導航成本。第二個必須想清楚的問題是項目結構到底要為誰服務為一個剛啟動的Demo服務單模塊加技術分層沒問題但一旦業務復雜到十幾個人協作結構就必須能隔離團隊、隔離發布、隔離失敗。很多人上來就拆多模塊結果拆出五個模塊互相同引最后干脆用一個公共common包放所有實體——這比單模塊更糟。拆分的核心依據是依賴方向而不是“看著整齊”。一個模塊如果被所有其他模塊依賴那它就是變相的全局垃圾場。SpringBoot項目里最典型的反面教材就是common或util模塊里面塞滿DateUtils、StringUtils、BaseEntity、Result最后沒人敢刪因為所有模塊都引了它。第三個犀利觀點entity、DTO、VO的分離不是“多寫幾個類”的問題而是結構腐化的分水嶺。很多項目為了“簡化”直接用entity充當響應體甚至把數據庫字段暴露給前端。等加了權限校驗、脫敏、版本兼容需求時只能往entity里塞一堆JsonIgnore和臨時字段。正確的做法是讓每一層有自己的模型語言JPA實體只表達持久化結構DTO負責API輸入輸出Domain對象承載業務規則。哪怕初期看起來重復但當業務復雜到一定程度這種“重復”恰恰是保護層讓數據庫表結構變化不至于炸到接口契約。那么包結構究竟怎么落地我推薦“業務域內部結構”的清晰范式。頂級包按業務域拆分每個域內部包含api對外接口與DTO、application應用服務/用例、domain實體、值對象、領域服務、infrastructure持久化、外部客戶端。這個結構借鑒了整潔架構但不必拘泥于五層。關鍵是依賴方向必須從外向內api依賴applicationapplication依賴domaininfrastructure依賴domain但被application反向依賴。SpringBoot的依賴注入讓這些方向天然正確但目錄卻常常寫反導致infrastructure里的工具類被controller直接調用繞過了業務規則。我見過最隱蔽的壞味道是“萬能service”。一個OrderService里同時處理支付回調、庫存扣減、積分發放、物流同步。表面看它“包治百病”實際上它違反了單一職責所有用例被揉進一個上帝類。當你想復用“確認訂單”邏輯時發現它必須在某個service方法里而且你沒法只調用它而避開其他副作用。破局之道是把“用例”作為一等公民——一個用例一個類命名如PlaceOrderUseCase、CancelOrderUseCase。這些類放在application包下它們編排domain對象和repository接口而不再有所謂的“業務service”層。這會讓項目結構看起來類變多了但每一個類的名字都直指業務意圖可測試性也大幅提升。另外包與包之間的循環依賴是結構清晰度的最大殺手。在SpringBoot中循環依賴有時能被容器“勉強”解決但包結構的循環依賴會讓人徹底崩潰。比如order包的service反向依賴了user包的repository而后者的某處又依賴了order的DTO。這種糾纏一旦出現你無法獨立改動任何一個域任何局部重構都像在雷區跳舞。強制規則很簡單業務域名之間禁止相互依賴跨域協作通過領域事件或解耦接口進行。具體做法是當訂單需要用戶信息時不要在order包里直接注入UserRepository而是定義UserQueryPort接口讓infrastructure層實現這個端口。這樣order包只依賴一個抽象契約真正的用戶狀態由user域負責。還有一個高頻痛點Controller里堆業務邏輯。有人把參數校驗、數據組裝、權限判斷全寫在Controller方法里一個方法上百行。這和包結構無關卻直接反映結構設計缺失。Controller的唯一職責是解析HTTP、綁定參數、調用應用服務、返回響應。任何超出這些的動作都必須下沉到application或domain。否則你會看到Controller依賴了repository、甚至事務注解——這意味著技術層之間的邊界已經蕩然無存。判斷結構是否清晰最簡單的方法是看一個改動需要同時觸碰幾個包。讓我用一次真實重構展示效果。原項目頂級包是controller/service/mapper/entity其中service有120個類實體和VO混用。重構后頂級包變為order/product/user/payment/customer-support每個業務域下用api/application/domain/infrastructure組織。原UserService被拆成GetUserUseCase、UpdateProfileUseCase等并把UserEntity拆成UserAccountDO持久化和UserProfileVO響應。結果修改用戶頭像的功能只動了user里的四個文件不需要打開order包新接一個支付渠道時新增類只在payment/infrastructure里。整個系統的可理解性直接從“按打字員分類”升級為“按業務負責人分類”。有人擔心這種結構過度設計對于小項目是負擔。確實一個只有三個實體的CRUD服務按業務域加用例類會顯得笨重。但結構設計從來不是為今天寫的而是為三個月后的預期復雜度寫的。更務實的做法是漸進式重構先保證頂級包按業務域劃分哪怕內部暫時保留controller/service/dao當某個業務域里的service超過十個方法或三個用例時再內部引入application和domain。 記住清晰的結構不是一次畫出來的是隨著對業務理解的加深不斷演進出來的。關鍵是建立一套“依賴方向”和“命名即意圖”的紀律并把這種紀律寫進CheckStyle或ArchUnit測試里讓CI攔住下一次腐化。如果給所有原則排個序我最看重一條包結構應當是“按業務能力垂直切片”而不是“按技術細節水平分層”。垂直切片意味著每個業務域擁有自己從API到持久化的完整實現改動被限制在切片內水平分層則讓每次需求穿過所有層等于每次改動都在“全量編譯”。SpringBoot給了你極大的自由但也給了你極大的腐蝕空間。真正的清晰不是目錄多么優雅而是任何一位新成員都能在十分鐘內找到“某個操作”應該住的包并且改完不擔心影響別處。當你的項目做到這一點SpringBoot結構設計才算真正及格。