摘要:邊界清晰,有利于理解開發(fā)測試和部署。前后端分離考慮到目前開發(fā)流行前后端分離,為了適應(yīng)潮流,引入前后端分離的約束。該請求被接受處理,但是該處理是不完整的。
本文欲回答這樣一個問題:在 「特定環(huán)境 」下,如何規(guī)劃Web開發(fā)框架,使其能滿足 「期望 」?
假設(shè)我們的「特定環(huán)境 」如下:
技術(shù)層面
使用Java語言進行開發(fā)
通過Maven構(gòu)建
基于SpringBoot
使用IntellijIDEA作為IDE
使用Mybatis作為持久層框架
前后端分離
非技術(shù)層面
新項目,變化較頻繁
快速迭代
開發(fā)人員資歷較淺
人員流動性較大
我們的 「期望 」是:
快速上手:鑒于人員流動性較大、開發(fā)人員的資歷較淺和項目的快速迭代需求,期望開發(fā)框架易于開發(fā)人員開發(fā)。易于入門,易于部署。
符合行業(yè)規(guī)約:盡量不定義私有規(guī)范,使用行業(yè)標(biāo)準(zhǔn),進一步降低學(xué)習(xí)難度
快速開發(fā):盡可能復(fù)用代碼,盡可能自動化生成模板代碼
獨立性:應(yīng)用能獨立運行,不過多的依賴其它應(yīng)用或中間件。邊界清晰,有利于理解、開發(fā)、測試和部署。反例:就是沒有規(guī)劃的RPC調(diào)用。
易于測試:能方便的進行單元/集成測試,不影響真實數(shù)據(jù)
易于部署:能方便的進行部署,便于快速的擴容
異常可追蹤:對異常,可快速定位到具體是哪個應(yīng)用,哪個類,哪行代碼的問題
本文從一個空框架開始,逐步加入上面的約束,最終推導(dǎo)出符合期望的Web框架!
本文提供的是一種思路!如有紕漏、或不同意見,歡迎討論指正!
我們從一個「空框架」開始我們的框架推導(dǎo)!所謂「空框架」是一個沒有任何約束的接收HTTP的可運行代碼,比如對任何請求都只返回Hello World的servlet!
這里我們基于Maven和SpringBoot快速搭建一個「空框架」!
代碼結(jié)構(gòu)如下(Maven構(gòu)建約束):?
intellijweb2 src/main java com.ivaneye.intellijweb2 TestController resources application.properties logback-spring.xml
?
代碼如下:
package com.ivaneye.intellijweb2; ? import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ResponseBody; ? @Controller @EnableAutoConfiguration public class TestController { ? ????@RequestMapping("/") ????@ResponseBody ????public String home() { ????????return "Hello World!"; ????} ? ????public static void main(String[] args) throws Exception { ????????SpringApplication.run(Main.class, args); ????} }
?
啟動后,當(dāng)訪問http://localhost:8080時,頁面上將顯示Hello world!字樣!
我們完全可以基于這個「空框架」進行開發(fā),但是這個「空框架」離我們的期望還很遠(yuǎn)。我們來一步步的改造!
分層架構(gòu)分層架構(gòu)可以說是Web項目的默認(rèn)架構(gòu)風(fēng)格,可以說是行業(yè)標(biāo)準(zhǔn)!所以我們首先引入分層架構(gòu)這個約束!
分層架構(gòu)有其優(yōu)勢和劣勢:
優(yōu)勢:通過將組件對系統(tǒng)的知識限制在單一層內(nèi),為整個系統(tǒng)的復(fù)雜性設(shè)置了邊界,并且提高了底層獨立性。使用層來封裝遺留的服務(wù),使新的服務(wù)免受遺留客戶端的影響;通過將不常用的功能轉(zhuǎn)移到一個共享的中間組件中,從而簡化組件的實現(xiàn)。中間組件還能夠通過支持跨多個網(wǎng)絡(luò)和處理器的負(fù)載均衡,來改善系統(tǒng)的可伸縮性。
劣勢:增加了數(shù)據(jù)處理的開銷和延遲,因此降低了用戶可覺察的性能??梢酝ㄟ^在中間層使用共享緩存來彌補這一缺點。
Web里最常用的切分方式就是MVC模式!我們對我們的「空框架」引入MVC模式!
那我們這里是切分包?還是切分模塊呢?考慮到最小影響原則,這里先切分包。如果有后續(xù)約束,再做進一步調(diào)整。
?
?引入MVC模式后的代碼結(jié)構(gòu):
intellijweb2 src/main java com.ivaneye.intellijweb2 controller TestController model respository service Main resources application.properties logback-spring.xml
?
引入MVC模式后的代碼:
package com.ivaneye.intellijweb2; ? import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; ? @EnableAutoConfiguration @ComponentScan({"com.ivaneye.intellijweb2"}) public class Main { ? ????public static void main(String[] args) throws Exception { ????????SpringApplication.run(Main.class, args); ????} } ? ? package com.ivaneye.intellijweb2.controller; ? import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ResponseBody; ? @Controller public class TestController { ? ????@RequestMapping("/") ????@ResponseBody ????public String home() { ????????return "Hello World!"; ????} }
?
這里暫時切分了Controller,Service,Model,Respository四個包,職責(zé)如下:
Controller:接收前臺的請求,驗證數(shù)據(jù),組裝需要的數(shù)據(jù),委托Service執(zhí)行具體業(yè)務(wù)邏輯,并將結(jié)果組裝返回給前臺
Service:處理核心業(yè)務(wù)邏輯,包含事務(wù)
Model:數(shù)據(jù)模型,與數(shù)據(jù)庫表的對應(yīng)類
Respository:數(shù)據(jù)操作類包,操作Model中的類,進行基本的CRUD操作
?
分層后的框架邏輯清晰,且切分方式符合行業(yè)規(guī)約,更易于上手。
考慮到目前Web開發(fā)流行前后端分離,為了適應(yīng)潮流,引入前后端分離的約束。
為了適應(yīng)前后端分離,后端不負(fù)責(zé)頁面的渲染,只接收和返回JSON數(shù)據(jù)。SpringBoot對此有直接的支持,直接將@Controller改為@RestController即可!
?
相關(guān)代碼:
package com.ivaneye.intellijweb2.controller; ? import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; ? @RestController public class TestController { ? ????@RequestMapping("/") ????public String home() { ????????return "Hello World!"; ????} }
?
整個URL符合RESTful,即符合行業(yè)規(guī)約!至于REST相關(guān)內(nèi)容另行討論。
實際上完整的RESTful應(yīng)用不只是URL符合RESTful,需要符合四個核心的約束:
資源的識別(identification of resources)
通過表述操作資源(manipulation of resources through representations)
自描述的消息(self-descriptive messages)
超媒體作為應(yīng)用狀態(tài)引擎(hypermedia as the engine of application state)
絕大部分聲稱符合RESTful的應(yīng)用都不是百分百符合這四個約束,特別是超媒體作為應(yīng)用狀態(tài)引擎(hypermedia as the engine of application state)這個約束。
?
確定了以JSON的方式進行參數(shù)的傳遞后,就需要確定如何來處理參數(shù)和返回結(jié)果?這涉及到幾個問題:
Controller如何接收參數(shù)?
Controller如何返回結(jié)果?
Controller如何將數(shù)據(jù)傳遞給Respository進行持久化處理?
Respository又如何將數(shù)據(jù)從數(shù)據(jù)庫中查出來返回給Controller?
這里選擇了Mybatis作為持久化框架,我們先從Mybatis的角度來回答上面的幾個問題!
首先Mybatis作為框架,會生成幾個文件:Model.java,Mapper.java和Mapper.xml?。ㄟ@里不做過多解釋!對Mybatis不熟悉的朋友請自行g(shù)oogle?。┻@幾個文件可以自動生成,也可以手寫!
不論是自動生成還是手寫都有其優(yōu)缺點:
先說自動生成的優(yōu)缺點:
優(yōu)點就是在修改表結(jié)構(gòu)以后,直接一條命令就可以自動生成新文件。
缺點就是這三個文件不能修改,如果修改了就不能再次自動生成了,否則會被覆蓋。
手動編寫的優(yōu)缺點:
優(yōu)點是完全自主控制,可復(fù)用Model,在里面添加注解,實現(xiàn)數(shù)據(jù)驗證、主鍵加解密、字典自動查詢等邏輯。
缺點就是表結(jié)構(gòu)調(diào)整后,需要手動修改需要調(diào)整的文件。一是繁瑣,二是沒有編譯期校驗,如果手誤寫錯了,直到運行期才可能發(fā)現(xiàn)
一種優(yōu)化方案是,第一次使用自動生成,后續(xù)手動修改。
但是結(jié)合前面的約束:
新項目,變化較頻繁
快速迭代
開發(fā)人員資歷較淺
此方法并不適用。 此方法只對于改動不太頻繁的項目還算適用,但是如果表結(jié)構(gòu)改動較頻繁,后續(xù)的每次修改還是要手動修改,非常的麻煩(無法適應(yīng)頻繁的變更,快速迭代)。且只能第一次使用自動生成這個規(guī)定并沒法強制實施,你沒法保證誰不會誤操作了自動生成(考慮開發(fā)人員資歷較淺),導(dǎo)致手寫的代碼被覆蓋了!
結(jié)合以上約束,為了盡量避免錯誤,優(yōu)先選擇自動生成!再來嘗試解決其短板,即生成的三個文件無法進行修改。是否有可行方案呢?
我們先考慮幾個問題:
Controller需要對頁面?zhèn)鬟^來的參數(shù)做哪些操作?
頁面?zhèn)鱽淼膮?shù)和Model是一個什么關(guān)系?
從Controller返回給頁面的數(shù)據(jù)又和Model是什么關(guān)系?
Controller對返回給頁面的數(shù)據(jù)又要做哪些操作?
為方便起見,我們把入?yún)⒎Q為Param,返回結(jié)果稱為Result。我們先回答第一個和第四個問題!
Controller需要對Param做哪些操作?
把從頁面?zhèn)鬟f過來的flat數(shù)據(jù)transform為對象(這是面向?qū)ο笳Z言的一種典型做法,我目前更偏向函數(shù)式做法,另開一篇討論)
對數(shù)據(jù)做校驗:類型對不對、格式對不對、是否為空等等等等
解密:有些字段數(shù)據(jù)可能是加過密的,比如主鍵,在transform的過程中需要對這些字段進行解密處理
Controller需要對Result做哪些操作?
加密:對需要加密的字段進行加密操作,比如主鍵
字典轉(zhuǎn)換:有些字段是code碼,頁面需要code碼對應(yīng)的值,方便人類閱讀。這里需要根據(jù)這些code碼從字典中獲取對應(yīng)的值(你可以在數(shù)據(jù)庫查詢的時候,直接關(guān)聯(lián)字典表查詢,但是這樣會帶來兩個麻煩,一個是model中需要包含字典value字段,就沒法自動生成了。第二個就是,一般字典會放在內(nèi)存中,關(guān)聯(lián)表查詢相對內(nèi)存取數(shù)據(jù),性能上會有劣勢)
字典列表:和字典轉(zhuǎn)換類似,有些頁面需要字典列表數(shù)據(jù),需要獲取這些數(shù)據(jù)到前臺供用戶選擇
這些操作都可以方便的處理:
SpringMVC已經(jīng)提供了數(shù)據(jù)綁定功能,將數(shù)據(jù)綁定到對象上
JSR303基于注解進行校驗
加解密、字典都可以通過自定義注解處理(擴展Jackson的注解處理即可。Jackson的注解只在方法上生效,本以為是個問題,卻助我構(gòu)思了一個方案:一個結(jié)合了自動生成的方便性和手寫的靈活性的方案!?。?!)
這些都是規(guī)約!
針對第二個和第三個問題,我們先看Param、Result和Model之間的關(guān)系:
從上圖可以看出,除了第一種情況(且這種情況很少),其它四種情況Param和Model實際是一個包含的關(guān)系。既然是一種包含的情況,那這種包含關(guān)系,在Java里我們可以使用繼承來實現(xiàn)。也就是說可以使Param extends Model,以這樣的方式來復(fù)用Model的內(nèi)容!
我們來看以這種方式來實現(xiàn)Param和Result,如何來解決上面的問題!
首先,因為Param和Result都繼承了Model,所以Model是不需要做任何改動的,就可以無限次的自動生成
其次,數(shù)據(jù)驗證、加解密的注解是可以添加到方法上的。我們對需要這些注解的字段,在Param/Result里覆蓋Model里的get/set方法,在其上添加注解,就可以使用基于注解的數(shù)據(jù)驗證和加解密。
假設(shè)數(shù)據(jù)字段有了修改,重新生成后,由于有@Override注解,在編譯期就可以定位到需要修改的get/set方法,結(jié)合IDE可以快速修復(fù)
如果是新增字段,則直接重新生成Mybatis的三個文件即可,原有代碼不受任何影響
?
盡量以擴展規(guī)約的方式來處理問題,在不增加理解難度的情況下提高易用性和開發(fā)效率!
在RESTful約束中,推薦使用HTTP的標(biāo)準(zhǔn)響應(yīng)來處理返回數(shù)據(jù)。SpringMVC中也提供了標(biāo)準(zhǔn)響應(yīng)的支持。
ResponseEntity.ok("body"); ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("");
但是由于HTTP的標(biāo)準(zhǔn)狀態(tài)碼太少了,見下表:
代碼 | 消息 | 描述 |
---|---|---|
100 | Continue | 只有請求的一部分已經(jīng)被服務(wù)器接收,但只要它沒有被拒絕,客戶端應(yīng)繼續(xù)該請求。 |
101 | Switching Protocols | 服務(wù)器切換協(xié)議。 |
200 | OK | 請求成功。 |
201 | Created | 該請求是完整的,并創(chuàng)建一個新的資源。 |
202 | Accepted | 該請求被接受處理,但是該處理是不完整的。 |
203 | Non-authoritative Information | ? |
204 | No Content | ? |
205 | Reset Content | ? |
206 | Partial Content | ? |
300 | Multiple Choices | 鏈接列表。用戶可以選擇一個鏈接,進入到該位置。最多五個地址 |
301 | Moved Permanently | 所請求的頁面已經(jīng)轉(zhuǎn)移到一個新的 URL。 |
302 | Found | 所請求的頁面已經(jīng)臨時轉(zhuǎn)移到一個新的 URL。 |
303 | See Other | 所請求的頁面可以在另一個不同的 URL 下被找到。 |
304 | Not Modified | ? |
305 | Use Proxy | ? |
306 | Unused | 在以前的版本中使用該代碼?,F(xiàn)在已不再使用它,但代碼仍被保留。 |
307 | Temporary Redirect | 所請求的頁面已經(jīng)臨時轉(zhuǎn)移到一個新的 URL。 |
400 | Bad Request | 服務(wù)器不理解請求。 |
401 | Unauthorized | 所請求的頁面需要用戶名和密碼。 |
402 | Payment Required | 你還不能使用該代碼。 |
403 | Forbidden | 禁止訪問所請求的頁面。 |
404 | Not Found | 服務(wù)器無法找到所請求的頁面。 |
405 | Method Not Allowed | 在請求中指定的方法是不允許的。 |
406 | Not Acceptable | 服務(wù)器只生成一個不被客戶端接受的響應(yīng)。 |
407 | Proxy Authentication Required | 在請求送達(dá)之前,您必須使用代理服務(wù)器的驗證。 |
408 | Request Timeout | 請求需要的時間比服務(wù)器能夠等待的時間長,超時。 |
409 | Conflict | 請求因為沖突無法完成。 |
410 | Gone | 所請求的頁面不再可用。 |
411 | Length Required | "Content-Length" 未定義。服務(wù)器無法處理客戶端發(fā)送的不帶 Content-Length 的請求信息。 |
412 | Precondition Failed | 請求中給出的先決條件被服務(wù)器評估為 false。 |
413 | Request Entity Too Large | 服務(wù)器不接受該請求,因為請求實體過大。 |
414 | Request-url Too Long | 服務(wù)器不接受該請求,因為 URL 太長。當(dāng)你轉(zhuǎn)換一個 “post” 請求為一個帶有長的查詢信息的 “get” 請求時發(fā)生。 |
415 | Unsupported Media Type | 服務(wù)器不接受該請求,因為媒體類型不被支持。 |
417 | Expectation Failed | ? |
500 | Internal Server Error | 未完成的請求。服務(wù)器遇到了一個意外的情況。 |
501 | Not Implemented | 未完成的請求。服務(wù)器不支持所需的功能。 |
502 | Bad Gateway | 未完成的請求。服務(wù)器從上游服務(wù)器收到無效響應(yīng)。 |
503 | Service Unavailable | 未完成的請求。服務(wù)器暫時超載或死機。 |
504 | Gateway Timeout | 網(wǎng)關(guān)超時。 |
505 | HTTP Version Not Supported | 服務(wù)器不支持“HTTP協(xié)議”版本。 |
這些標(biāo)準(zhǔn)的狀態(tài)碼無法詳細(xì)的表示一個項目中的所有情況。且目前SpringMVC不支持自定義狀態(tài)碼。就是類似這樣的代碼:
ResponseEntity.status(10001).body("");
雖然不報錯,但是無法正常響應(yīng),后臺會報類似“非標(biāo)準(zhǔn)狀態(tài)碼”的錯誤!
所以我自定義了一個對象Result,用來完成類似ResponseEntity的工作。Result的結(jié)構(gòu)如下:
public class Result { ????private int code;//200為正常,其它為相關(guān)業(yè)務(wù)報錯 ????private String msg;//對應(yīng)的錯誤信息,200為ok ????private Object body;//返回的業(yè)務(wù)對象 }
提供類似:
Result.ok("body") Result.error(e); Result.error(CommonConstants.SERVER_ERROR, e.getMessage());
這樣的構(gòu)造方法,方便使用。
?
異常處理在上面數(shù)據(jù)返回里涉及了一點(就是Result的構(gòu)造以及業(yè)務(wù)的各種場景處理)。這里詳細(xì)說明。
約束中需要能方便的追蹤異常!
Java里提供了CheckedException和UnCheckedException,而對于我們實際使用來說,還是需要區(qū)分業(yè)務(wù)場景。
異常是業(yè)務(wù)異常還是非業(yè)務(wù)異常?
這里的業(yè)務(wù)異常指的是:由于不符合業(yè)務(wù)需求而導(dǎo)致的異常,比如:用戶沒登錄,必要字段沒填寫導(dǎo)致校驗失敗,訂單的數(shù)量超出了庫存。
非業(yè)務(wù)異常則指的是:和業(yè)務(wù)場景不相關(guān)的異常。例如:數(shù)據(jù)庫連接失敗了,網(wǎng)絡(luò)連接失敗。
表現(xiàn)到代碼上,對于業(yè)務(wù)異常我們可以定義BusinessException來表示,所有繼承了BusinessException的異常,都是業(yè)務(wù)異常,而其它異常就是非業(yè)務(wù)異常。
更進一步,業(yè)務(wù)異常也可以分為:
通用業(yè)務(wù)異常,例如:用戶沒有登錄,必要字段沒填寫導(dǎo)致校驗失?。?/p>
和特定業(yè)務(wù)異常,例如:訂單的數(shù)量超出庫存了。
這兩種異常,我們可以通過異常碼來區(qū)分,例如:100開頭的為通用業(yè)務(wù)異常,300開頭的為訂單異常,400開頭的為產(chǎn)品異常,依此類推。
同時異常的Code和Msg與Result對應(yīng),方便構(gòu)建Result.error(e);直接返回。
再進一步,目前的應(yīng)用都是分布式的,甚至是微服務(wù)架構(gòu)!我們是否可以通過異常能快速的定位到是哪個應(yīng)用的哪個模塊里的哪個代碼出問題了呢?
一種可行方案還是通過異常碼來處理:以三位數(shù)字為間隔,來區(qū)分應(yīng)用+模塊+代碼,例如:001002301,可以理解為異常是001機器上的,002應(yīng)用,拋出的301(訂單相關(guān))異常。
當(dāng)系統(tǒng)變得越來越大后,難免不會出現(xiàn)系統(tǒng)內(nèi)不同應(yīng)用之間的相互調(diào)用;如果是微服務(wù)的話,那么服務(wù)間的相互調(diào)用是很常見的。如果處理不當(dāng),會使得各應(yīng)用之間相互依賴,無法獨立的運行。導(dǎo)致開發(fā)、測試、部署都很麻煩。
為了避免這樣的問題出現(xiàn),結(jié)合如下兩個約束:
符合行業(yè)規(guī)約
獨立性
故使用RESTful方式,作為應(yīng)用間通信的方式。這也是微服務(wù)推薦的通信方式!
應(yīng)用間調(diào)用會出現(xiàn)Model的依賴,故這里將Model從包提升為模塊。方便后續(xù)如果有其它應(yīng)用要依賴時,可直接依賴Model模塊,而不是整個應(yīng)用。
調(diào)整后代碼結(jié)構(gòu)如下:
intellijweb2 intellijweb2-web src/main java com.ivaneye.intellijweb2 controller TestController respository service Main resources application.properties logback-spring.xml intellijweb2-model src/main java com.ivaneye.intellijweb2 model param result
將model包移動到了intellijweb2-model模塊中,同時新增了param和result包!
測試SpringBoot本身提供了較為完善的測試功能。包括單元測試、Mocker、Spy等。
基于如下幾個考慮:
易于測試:我接觸的很多開發(fā)人員是不喜歡寫測試的。如果測試代碼不易編寫,那就更不愿意寫了。
不影響環(huán)境:我期望的是在發(fā)布時是包含測試的,測試不通過即不能發(fā)布。也就是說在部署時測試,會使用正式環(huán)境的庫表數(shù)據(jù),所以在測試時不能影響到這些數(shù)據(jù)。
小范圍測試:以最少的代碼,覆蓋最核心的代碼邏輯
故決定只對Service測試,原因如下:
在上面的分層架構(gòu)里描述了各層的職責(zé),可以看出,核心業(yè)務(wù)都在Service層,Controller和Model都沒有業(yè)務(wù)邏輯,只是一些標(biāo)準(zhǔn)化代碼,沒必要測試
SpringBoot對Controller的測試是在不同的線程內(nèi),不支持事務(wù),如果在正式環(huán)境測試的話,會影響正式庫數(shù)據(jù)
部署SpringBoot可以直接打包為jar包,直接運行啟動。這很方便,但是如果想快速的橫向擴容,配置文件就是一個問題。因為不同機器上的配置并不是完全相同的。
有兩個方案可以解決:
Docker
配置服務(wù)器
從便利性考慮,還是選擇配置服務(wù)器。
配置文件中均是開發(fā)環(huán)境配置,方便開發(fā)人員直接開發(fā)、測試。
在正式環(huán)境中,應(yīng)用啟動時會從配置服務(wù)器獲取對應(yīng)的配置,覆蓋本地測試進行部署。
在結(jié)束之前,先問個問題?你是喜歡代碼生成、還是封裝?
代碼生成就類似Mybatis這樣生成了對應(yīng)的文件,邏輯透明。你可以去改
封裝就類似Hibernate,你寫個對象,然后對對象操作就行了,底層數(shù)據(jù)庫操作由Hibernate來處理
我個人更偏向代碼生成,理由是:
簡單:易于使用,易于上手
行業(yè)標(biāo)準(zhǔn):生成的代碼是行業(yè)標(biāo)準(zhǔn)代碼,只要熟悉Mybatis,Spring就可以直接上手(而Mybatis和Spring目前是互聯(lián)網(wǎng)標(biāo)配)。如果公司內(nèi)部進行一些封裝,那么新手需要先理解這些封裝,增加了學(xué)習(xí)成本。
基于上面的原因,再考慮到其實我們的框架都是符合規(guī)約的(RESTful,JSR303,覆寫,Jackson),故對于標(biāo)準(zhǔn)CRUD,我們可以一鍵生成!
一鍵生成其實到上面一節(jié),整個框架應(yīng)該已經(jīng)符合預(yù)期了!但是為了得到超預(yù)期的效果,我們來更進一步!
我們先看目前的開發(fā)流程:
設(shè)計數(shù)據(jù)表
生成Model,Mapper
編寫Param,Result
編寫Respository
編寫Service
編寫Controller
編寫測試
執(zhí)行測試
提交代碼
對于一個典型的CRUD操作,這里有多少重復(fù)代碼呢?
篇幅有限,舉個簡單的例子:現(xiàn)在需要編寫Order和User的新增邏輯,Controller的代碼是什么樣的?
Controller:
package ${package.Controller}; import ... @Api(tags = "${table.controllerName}") @RestController @RequestMapping("$!{cfg.basePath}") public class ${table.controllerName} extends ${superControllerClass}{ @Autowired private ${table.serviceImplName} ${instanceName}Service; private Logger logger = LoggerFactory.getLogger(${table.controllerName}.class); @ApiOperation(value = "創(chuàng)建${entity}") @RequestMapping(value = "/$!{cfg.version}/${table.entityPath}", method = RequestMethod.POST) public Result create(@RequestBody @Validated(Create.class) ${entity}Param param, BindingResult bindingResult) { try { //驗證失敗 if (bindingResult.hasErrors()) { throw new ValidException(bindingResult.getFieldError().getDefaultMessage()); } Long recId = ${instanceName}Service.create(param); return Result.ok(recId); } catch (BusinessException e) { logger.error("create ${entity} Error!", e); return Result.error(e); } catch (Exception e) { logger.error("create ${entity} Error!", e); return Result.error(CommonConstants.SERVER_ERROR, e.getMessage()); } } }
如上的模板是否能符合OrderController和UserController?再往后看Service,Param,Result等是否都可以用類似的模板來統(tǒng)一處理?
所以,我們完全可以對相應(yīng)的代碼進行自動生成,盡可能的降低模板代碼的手動編寫。對于標(biāo)準(zhǔn)的CRUD邏輯,我們可以做到如下的開發(fā)流程:
設(shè)計數(shù)據(jù)表
生成CRUD,包括測試(我們測試的是Service,想想測試代碼和Controller代碼有多少區(qū)別?)
執(zhí)行測試
提交代碼
對于不可重復(fù)生成的文件,我們可以設(shè)置"存在即不覆蓋",在最大限度的提高開發(fā)效率的前提下,降低誤操作。
總結(jié)如上即是我基于約束所做的Web推導(dǎo)!目前的主要問題還是在Model層面:
數(shù)據(jù)表映射為Model是否是合理的?
基于Model的操作是否合適?
基于上面Param、Result和Model的關(guān)系圖來看,實際上Param、Result和Model大部分情況下都不是契合的!把這些Param、Result限制在Model上是否合適?數(shù)據(jù)結(jié)構(gòu)是否清晰?
目前個人覺得基于data的transform、filter、map操作更適合web開發(fā)(我會另開一篇討論這個)!或者你有什么好的方案,歡迎指教?
公眾號:ivaneye
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/68684.html
摘要:與的特點比較這兩個目前都是小眾語言做了些時間的研究寫了點東西有了點心得相似點有衛(wèi)生宏區(qū)別與的不衛(wèi)生宏在類或定義體之外定義函數(shù)代碼沒有分成頭與實現(xiàn)體例如的頭與實現(xiàn)的與定義的接口定義與實現(xiàn)定義是分開的而與是不分開的運用函數(shù)式編程高階函數(shù)目前是新 nim與rust的特點比較 這兩個目前都是小眾語言,做了些時間的研究,寫了點東西有了點心得 相似點: 有衛(wèi)生宏.區(qū)別與C++的(不衛(wèi)生)宏 在類...
摘要:前言本文系統(tǒng)的梳理了搭建項目的常見用法,目的在于讓你快速掌握獨立搭建項目的能力。思維導(dǎo)圖接下來,我們根據(jù)思維導(dǎo)圖,一步步來解釋和實現(xiàn)我們的目標(biāo)。這確保了最終包里數(shù)量的最小化。但是如果其中一個依賴需要特殊的,默認(rèn)情況下無法將其檢測出來。 前言 本文系統(tǒng)的梳理了vue-cli3搭建項目的常見用法,目的在于讓你快速掌握獨立搭建vue項目的能力。你將會了解如下知識點: 如何安裝項目插件 添加...
摘要:比如或者都會導(dǎo)致函數(shù)返回值類型時。和特性一樣,等于是函數(shù)返回值中的或。注意對比下面的寫法對于,它的返回值是可迭代的對象,并且每個類型都是或者。首先是不支持方法重載的,是支持的,而類型系統(tǒng)一定程度在對標(biāo),當(dāng)然要支持這個功能。 1 引言 精讀原文是 typescript 2.0-2.9 的文檔: 2.0-2.8,2.9 草案. 我發(fā)現(xiàn),許多寫了一年以上 Typescript 開發(fā)者,對 T...
摘要:蠎周刊年度最贊親俺們又來回顧又一個偉大的年份兒包去年最受歡迎的文章和項目如果你錯過了幾期就這一期不會丟失最好的嗯哼還為你和你的準(zhǔn)備了一批紀(jì)念裇從這兒獲取任何時候如果想分享好物給大家在這兒提交喜歡我們收集的任何意見建議通過來吧原文 Title: 蠎周刊 2015 年度最贊Date: 2016-01-09 Tags: Weekly,Pycoder,Zh Slug: issue-198-to...
摘要:不幸的是,在軟件包管理十分混亂,至少歷史上十分混亂。的最大改進是將函數(shù)的參數(shù)單獨放到一個的文件中這些成為包的元數(shù)據(jù)。基于的版本號管理。的版本推導(dǎo)這里重點說明一下基于的版本號管理這個功能。開發(fā)版本號的形式如下。 為什么寫這個系列 OpenStack是目前我所知的最大最復(fù)雜的基于Python項目。整個OpenStack項目包含了數(shù)十個主要的子項目,每個子項目所用到的庫也不盡相同。因此,對于...
閱讀 3000·2021-10-19 11:46
閱讀 991·2021-08-03 14:03
閱讀 2952·2021-06-11 18:08
閱讀 2923·2019-08-29 13:52
閱讀 2778·2019-08-29 12:49
閱讀 497·2019-08-26 13:56
閱讀 937·2019-08-26 13:41
閱讀 861·2019-08-26 13:35