成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Web開發(fā)框架推導(dǎo)

vpants / 2153人閱讀

摘要:邊界清晰,有利于理解開發(fā)測(cè)試和部署。前后端分離考慮到目前開發(fā)流行前后端分離,為了適應(yīng)潮流,引入前后端分離的約束。該請(qǐng)求被接受處理,但是該處理是不完整的。

本文欲回答這樣一個(gè)問題:在 「特定環(huán)境 」下,如何規(guī)劃Web開發(fā)框架,使其能滿足 「期望 」?

假設(shè)我們的「特定環(huán)境 」如下:

技術(shù)層面

使用Java語言進(jìn)行開發(fā)

通過Maven構(gòu)建

基于SpringBoot

使用IntellijIDEA作為IDE

使用Mybatis作為持久層框架

前后端分離

非技術(shù)層面

新項(xiàng)目,變化較頻繁

快速迭代

開發(fā)人員資歷較淺

人員流動(dòng)性較大

我們的 「期望 」是:

快速上手:鑒于人員流動(dòng)性較大、開發(fā)人員的資歷較淺和項(xiàng)目的快速迭代需求,期望開發(fā)框架易于開發(fā)人員開發(fā)。易于入門,易于部署。

符合行業(yè)規(guī)約:盡量不定義私有規(guī)范,使用行業(yè)標(biāo)準(zhǔn),進(jìn)一步降低學(xué)習(xí)難度

快速開發(fā):盡可能復(fù)用代碼,盡可能自動(dòng)化生成模板代碼

獨(dú)立性:應(yīng)用能獨(dú)立運(yùn)行,不過多的依賴其它應(yīng)用或中間件。邊界清晰,有利于理解、開發(fā)、測(cè)試和部署。反例:就是沒有規(guī)劃的RPC調(diào)用。

易于測(cè)試:能方便的進(jìn)行單元/集成測(cè)試,不影響真實(shí)數(shù)據(jù)

易于部署:能方便的進(jìn)行部署,便于快速的擴(kuò)容

異??勺粉?/strong>:對(duì)異常,可快速定位到具體是哪個(gè)應(yīng)用,哪個(gè)類,哪行代碼的問題

本文從一個(gè)空框架開始,逐步加入上面的約束,最終推導(dǎo)出符合期望的Web框架!
本文提供的是一種思路!如有紕漏、或不同意見,歡迎討論指正!

從「空框架」開始

我們從一個(gè)「空框架」開始我們的框架推導(dǎo)!所謂「空框架」是一個(gè)沒有任何約束的接收HTTP的可運(yùn)行代碼,比如對(duì)任何請(qǐng)求都只返回Hello World的servlet!
這里我們基于Maven和SpringBoot快速搭建一個(gè)「空框架」!

代碼結(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)后,當(dāng)訪問http://localhost:8080時(shí),頁面上將顯示Hello world!字樣!

我們完全可以基于這個(gè)「空框架」進(jìn)行開發(fā),但是這個(gè)「空框架」離我們的期望還很遠(yuǎn)。我們來一步步的改造!

分層架構(gòu)

分層架構(gòu)可以說是Web項(xiàng)目的默認(rèn)架構(gòu)風(fēng)格,可以說是行業(yè)標(biāo)準(zhǔn)!所以我們首先引入分層架構(gòu)這個(gè)約束!

分層架構(gòu)有其優(yōu)勢(shì)和劣勢(shì):

優(yōu)勢(shì):通過將組件對(duì)系統(tǒng)的知識(shí)限制在單一層內(nèi),為整個(gè)系統(tǒng)的復(fù)雜性設(shè)置了邊界,并且提高了底層獨(dú)立性。使用層來封裝遺留的服務(wù),使新的服務(wù)免受遺留客戶端的影響;通過將不常用的功能轉(zhuǎn)移到一個(gè)共享的中間組件中,從而簡(jiǎn)化組件的實(shí)現(xiàn)。中間組件還能夠通過支持跨多個(gè)網(wǎng)絡(luò)和處理器的負(fù)載均衡,來改善系統(tǒng)的可伸縮性。

劣勢(shì):增加了數(shù)據(jù)處理的開銷和延遲,因此降低了用戶可覺察的性能。可以通過在中間層使用共享緩存來彌補(bǔ)這一缺點(diǎn)。

Web里最常用的切分方式就是MVC模式!我們對(duì)我們的「空框架」引入MVC模式!
那我們這里是切分包?還是切分模塊呢?考慮到最小影響原則,這里先切分包。如果有后續(xù)約束,再做進(jìn)一步調(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!";
????}
}

?
這里暫時(shí)切分了Controller,Service,Model,Respository四個(gè)包,職責(zé)如下:

Controller:接收前臺(tái)的請(qǐng)求,驗(yàn)證數(shù)據(jù),組裝需要的數(shù)據(jù),委托Service執(zhí)行具體業(yè)務(wù)邏輯,并將結(jié)果組裝返回給前臺(tái)

Service:處理核心業(yè)務(wù)邏輯,包含事務(wù)

Model:數(shù)據(jù)模型,與數(shù)據(jù)庫表的對(duì)應(yīng)類

Respository:數(shù)據(jù)操作類包,操作Model中的類,進(jìn)行基本的CRUD操作

?
分層后的框架邏輯清晰,且切分方式符合行業(yè)規(guī)約,更易于上手。

前后端分離

考慮到目前Web開發(fā)流行前后端分離,為了適應(yīng)潮流,引入前后端分離的約束。

為了適應(yīng)前后端分離,后端不負(fù)責(zé)頁面的渲染,只接收和返回JSON數(shù)據(jù)。SpringBoot對(duì)此有直接的支持,直接將@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!";
????}
}

?
整個(gè)URL符合RESTful,即符合行業(yè)規(guī)約!至于REST相關(guān)內(nèi)容另行討論。

實(shí)際上完整的RESTful應(yīng)用不只是URL符合RESTful,需要符合四個(gè)核心的約束:

資源的識(shí)別(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)用都不是百分百符合這四個(gè)約束,特別是超媒體作為應(yīng)用狀態(tài)引擎(hypermedia as the engine of application state)這個(gè)約束。
?

基于注解的數(shù)據(jù)處理

確定了以JSON的方式進(jìn)行參數(shù)的傳遞后,就需要確定如何來處理參數(shù)和返回結(jié)果?這涉及到幾個(gè)問題:

Controller如何接收參數(shù)?

Controller如何返回結(jié)果?

Controller如何將數(shù)據(jù)傳遞給Respository進(jìn)行持久化處理?

Respository又如何將數(shù)據(jù)從數(shù)據(jù)庫中查出來返回給Controller?

這里選擇了Mybatis作為持久化框架,我們先從Mybatis的角度來回答上面的幾個(gè)問題!

首先Mybatis作為框架,會(huì)生成幾個(gè)文件:Model.java,Mapper.java和Mapper.xml!(這里不做過多解釋!對(duì)Mybatis不熟悉的朋友請(qǐng)自行g(shù)oogle?。┻@幾個(gè)文件可以自動(dòng)生成,也可以手寫!

不論是自動(dòng)生成還是手寫都有其優(yōu)缺點(diǎn):

先說自動(dòng)生成的優(yōu)缺點(diǎn)

優(yōu)點(diǎn)就是在修改表結(jié)構(gòu)以后,直接一條命令就可以自動(dòng)生成新文件。

缺點(diǎn)就是這三個(gè)文件不能修改,如果修改了就不能再次自動(dòng)生成了,否則會(huì)被覆蓋。

手動(dòng)編寫的優(yōu)缺點(diǎn)

優(yōu)點(diǎn)是完全自主控制,可復(fù)用Model,在里面添加注解,實(shí)現(xiàn)數(shù)據(jù)驗(yàn)證、主鍵加解密、字典自動(dòng)查詢等邏輯。

缺點(diǎn)就是表結(jié)構(gòu)調(diào)整后,需要手動(dòng)修改需要調(diào)整的文件。一是繁瑣,二是沒有編譯期校驗(yàn),如果手誤寫錯(cuò)了,直到運(yùn)行期才可能發(fā)現(xiàn)

一種優(yōu)化方案是,第一次使用自動(dòng)生成,后續(xù)手動(dòng)修改。

但是結(jié)合前面的約束:

新項(xiàng)目,變化較頻繁

快速迭代

開發(fā)人員資歷較淺

此方法并不適用。 此方法只對(duì)于改動(dòng)不太頻繁的項(xiàng)目還算適用,但是如果表結(jié)構(gòu)改動(dòng)較頻繁,后續(xù)的每次修改還是要手動(dòng)修改,非常的麻煩(無法適應(yīng)頻繁的變更,快速迭代)。且只能第一次使用自動(dòng)生成這個(gè)規(guī)定并沒法強(qiáng)制實(shí)施,你沒法保證誰不會(huì)誤操作了自動(dòng)生成(考慮開發(fā)人員資歷較淺),導(dǎo)致手寫的代碼被覆蓋了!

結(jié)合以上約束,為了盡量避免錯(cuò)誤,優(yōu)先選擇自動(dòng)生成!再來嘗試解決其短板,即生成的三個(gè)文件無法進(jìn)行修改。是否有可行方案呢?

我們先考慮幾個(gè)問題:

Controller需要對(duì)頁面?zhèn)鬟^來的參數(shù)做哪些操作?

頁面?zhèn)鱽淼膮?shù)和Model是一個(gè)什么關(guān)系?

從Controller返回給頁面的數(shù)據(jù)又和Model是什么關(guān)系

Controller對(duì)返回給頁面的數(shù)據(jù)又要做哪些操作?

為方便起見,我們把入?yún)⒎Q為Param,返回結(jié)果稱為Result。我們先回答第一個(gè)和第四個(gè)問題!

Controller需要對(duì)Param做哪些操作?

把從頁面?zhèn)鬟f過來的flat數(shù)據(jù)transform為對(duì)象(這是面向?qū)ο笳Z言的一種典型做法,我目前更偏向函數(shù)式做法,另開一篇討論)

對(duì)數(shù)據(jù)做校驗(yàn):類型對(duì)不對(duì)、格式對(duì)不對(duì)、是否為空等等等等

解密:有些字段數(shù)據(jù)可能是加過密的,比如主鍵,在transform的過程中需要對(duì)這些字段進(jìn)行解密處理

Controller需要對(duì)Result做哪些操作?

加密:對(duì)需要加密的字段進(jìn)行加密操作,比如主鍵

字典轉(zhuǎn)換:有些字段是code碼,頁面需要code碼對(duì)應(yīng)的值,方便人類閱讀。這里需要根據(jù)這些code碼從字典中獲取對(duì)應(yīng)的值(你可以在數(shù)據(jù)庫查詢的時(shí)候,直接關(guān)聯(lián)字典表查詢,但是這樣會(huì)帶來兩個(gè)麻煩,一個(gè)是model中需要包含字典value字段,就沒法自動(dòng)生成了。第二個(gè)就是,一般字典會(huì)放在內(nèi)存中,關(guān)聯(lián)表查詢相對(duì)內(nèi)存取數(shù)據(jù),性能上會(huì)有劣勢(shì))

字典列表:和字典轉(zhuǎn)換類似,有些頁面需要字典列表數(shù)據(jù),需要獲取這些數(shù)據(jù)到前臺(tái)供用戶選擇

這些操作都可以方便的處理:

SpringMVC已經(jīng)提供了數(shù)據(jù)綁定功能,將數(shù)據(jù)綁定到對(duì)象上

JSR303基于注解進(jìn)行校驗(yàn)

加解密、字典都可以通過自定義注解處理(擴(kuò)展Jackson的注解處理即可。Jackson的注解只在方法上生效,本以為是個(gè)問題,卻助我構(gòu)思了一個(gè)方案:一個(gè)結(jié)合了自動(dòng)生成的方便性和手寫的靈活性的方案!?。?!)

這些都是規(guī)約!

針對(duì)第二個(gè)和第三個(gè)問題,我們先看Param、Result和Model之間的關(guān)系:

從上圖可以看出,除了第一種情況(且這種情況很少),其它四種情況Param和Model實(shí)際是一個(gè)包含的關(guān)系。既然是一種包含的情況,那這種包含關(guān)系,在Java里我們可以使用繼承來實(shí)現(xiàn)。也就是說可以使Param extends Model,以這樣的方式來復(fù)用Model的內(nèi)容!
我們來看以這種方式來實(shí)現(xiàn)Param和Result,如何來解決上面的問題!

首先,因?yàn)镻aram和Result都繼承了Model,所以Model是不需要做任何改動(dòng)的,就可以無限次的自動(dòng)生成

其次,數(shù)據(jù)驗(yàn)證、加解密的注解是可以添加到方法上的。我們對(duì)需要這些注解的字段,在Param/Result里覆蓋Model里的get/set方法,在其上添加注解,就可以使用基于注解的數(shù)據(jù)驗(yàn)證和加解密。

假設(shè)數(shù)據(jù)字段有了修改,重新生成后,由于有@Override注解,在編譯期就可以定位到需要修改的get/set方法,結(jié)合IDE可以快速修復(fù)

如果是新增字段,則直接重新生成Mybatis的三個(gè)文件即可,原有代碼不受任何影響

?
盡量以擴(kuò)展規(guī)約的方式來處理問題,在不增加理解難度的情況下提高易用性和開發(fā)效率!

數(shù)據(jù)返回

在RESTful約束中,推薦使用HTTP的標(biāo)準(zhǔn)響應(yīng)來處理返回?cái)?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 只有請(qǐng)求的一部分已經(jīng)被服務(wù)器接收,但只要它沒有被拒絕,客戶端應(yīng)繼續(xù)該請(qǐng)求。
101 Switching Protocols 服務(wù)器切換協(xié)議。
200 OK 請(qǐng)求成功。
201 Created 該請(qǐng)求是完整的,并創(chuàng)建一個(gè)新的資源。
202 Accepted 該請(qǐng)求被接受處理,但是該處理是不完整的。
203 Non-authoritative Information ?
204 No Content ?
205 Reset Content ?
206 Partial Content ?
300 Multiple Choices 鏈接列表。用戶可以選擇一個(gè)鏈接,進(jìn)入到該位置。最多五個(gè)地址
301 Moved Permanently 所請(qǐng)求的頁面已經(jīng)轉(zhuǎn)移到一個(gè)新的 URL。
302 Found 所請(qǐng)求的頁面已經(jīng)臨時(shí)轉(zhuǎn)移到一個(gè)新的 URL。
303 See Other 所請(qǐng)求的頁面可以在另一個(gè)不同的 URL 下被找到。
304 Not Modified ?
305 Use Proxy ?
306 Unused 在以前的版本中使用該代碼?,F(xiàn)在已不再使用它,但代碼仍被保留。
307 Temporary Redirect 所請(qǐng)求的頁面已經(jīng)臨時(shí)轉(zhuǎn)移到一個(gè)新的 URL。
400 Bad Request 服務(wù)器不理解請(qǐng)求。
401 Unauthorized 所請(qǐng)求的頁面需要用戶名和密碼。
402 Payment Required 你還不能使用該代碼。
403 Forbidden 禁止訪問所請(qǐng)求的頁面。
404 Not Found 服務(wù)器無法找到所請(qǐng)求的頁面。
405 Method Not Allowed 在請(qǐng)求中指定的方法是不允許的。
406 Not Acceptable 服務(wù)器只生成一個(gè)不被客戶端接受的響應(yīng)。
407 Proxy Authentication Required 在請(qǐng)求送達(dá)之前,您必須使用代理服務(wù)器的驗(yàn)證。
408 Request Timeout 請(qǐng)求需要的時(shí)間比服務(wù)器能夠等待的時(shí)間長(zhǎng),超時(shí)。
409 Conflict 請(qǐng)求因?yàn)闆_突無法完成。
410 Gone 所請(qǐng)求的頁面不再可用。
411 Length Required "Content-Length" 未定義。服務(wù)器無法處理客戶端發(fā)送的不帶 Content-Length 的請(qǐng)求信息。
412 Precondition Failed 請(qǐng)求中給出的先決條件被服務(wù)器評(píng)估為 false。
413 Request Entity Too Large 服務(wù)器不接受該請(qǐng)求,因?yàn)檎?qǐng)求實(shí)體過大。
414 Request-url Too Long 服務(wù)器不接受該請(qǐng)求,因?yàn)?URL 太長(zhǎng)。當(dāng)你轉(zhuǎn)換一個(gè) “post” 請(qǐng)求為一個(gè)帶有長(zhǎng)的查詢信息的 “get” 請(qǐng)求時(shí)發(fā)生。
415 Unsupported Media Type 服務(wù)器不接受該請(qǐng)求,因?yàn)槊襟w類型不被支持。
417 Expectation Failed ?
500 Internal Server Error 未完成的請(qǐng)求。服務(wù)器遇到了一個(gè)意外的情況。
501 Not Implemented 未完成的請(qǐng)求。服務(wù)器不支持所需的功能。
502 Bad Gateway 未完成的請(qǐng)求。服務(wù)器從上游服務(wù)器收到無效響應(yīng)。
503 Service Unavailable 未完成的請(qǐng)求。服務(wù)器暫時(shí)超載或死機(jī)。
504 Gateway Timeout 網(wǎng)關(guān)超時(shí)。
505 HTTP Version Not Supported 服務(wù)器不支持“HTTP協(xié)議”版本。

這些標(biāo)準(zhǔn)的狀態(tài)碼無法詳細(xì)的表示一個(gè)項(xiàng)目中的所有情況。且目前SpringMVC不支持自定義狀態(tài)碼。就是類似這樣的代碼:

ResponseEntity.status(10001).body("");

雖然不報(bào)錯(cuò),但是無法正常響應(yīng),后臺(tái)會(huì)報(bào)類似“非標(biāo)準(zhǔn)狀態(tài)碼”的錯(cuò)誤!
所以我自定義了一個(gè)對(duì)象Result,用來完成類似ResponseEntity的工作。Result的結(jié)構(gòu)如下:

public class Result {
????private int code;//200為正常,其它為相關(guān)業(yè)務(wù)報(bào)錯(cuò)
????private String msg;//對(duì)應(yīng)的錯(cuò)誤信息,200為ok
????private Object body;//返回的業(yè)務(wù)對(duì)象
}

提供類似:

Result.ok("body")
Result.error(e);
Result.error(CommonConstants.SERVER_ERROR, e.getMessage());

這樣的構(gòu)造方法,方便使用。
?

異常處理

異常處理在上面數(shù)據(jù)返回里涉及了一點(diǎn)(就是Result的構(gòu)造以及業(yè)務(wù)的各種場(chǎng)景處理)。這里詳細(xì)說明。
約束中需要能方便的追蹤異常!
Java里提供了CheckedException和UnCheckedException,而對(duì)于我們實(shí)際使用來說,還是需要區(qū)分業(yè)務(wù)場(chǎng)景。

異常是業(yè)務(wù)異常還是非業(yè)務(wù)異常?

這里的業(yè)務(wù)異常指的是:由于不符合業(yè)務(wù)需求而導(dǎo)致的異常,比如:用戶沒登錄,必要字段沒填寫導(dǎo)致校驗(yàn)失敗,訂單的數(shù)量超出了庫存。

非業(yè)務(wù)異常則指的是:和業(yè)務(wù)場(chǎng)景不相關(guān)的異常。例如:數(shù)據(jù)庫連接失敗了,網(wǎng)絡(luò)連接失敗。

表現(xiàn)到代碼上,對(duì)于業(yè)務(wù)異常我們可以定義BusinessException來表示,所有繼承了BusinessException的異常,都是業(yè)務(wù)異常,而其它異常就是非業(yè)務(wù)異常。

更進(jìn)一步,業(yè)務(wù)異常也可以分為:

通用業(yè)務(wù)異常,例如:用戶沒有登錄,必要字段沒填寫導(dǎo)致校驗(yàn)失??;

和特定業(yè)務(wù)異常,例如:訂單的數(shù)量超出庫存了。

這兩種異常,我們可以通過異常碼來區(qū)分,例如:100開頭的為通用業(yè)務(wù)異常,300開頭的為訂單異常,400開頭的為產(chǎn)品異常,依此類推。
同時(shí)異常的Code和Msg與Result對(duì)應(yīng),方便構(gòu)建Result.error(e);直接返回。
再進(jìn)一步,目前的應(yīng)用都是分布式的,甚至是微服務(wù)架構(gòu)!我們是否可以通過異常能快速的定位到是哪個(gè)應(yīng)用的哪個(gè)模塊里的哪個(gè)代碼出問題了呢?
一種可行方案還是通過異常碼來處理:以三位數(shù)字為間隔,來區(qū)分應(yīng)用+模塊+代碼,例如:001002301,可以理解為異常是001機(jī)器上的,002應(yīng)用,拋出的301(訂單相關(guān))異常。

獨(dú)立性

當(dāng)系統(tǒng)變得越來越大后,難免不會(huì)出現(xiàn)系統(tǒng)內(nèi)不同應(yīng)用之間的相互調(diào)用;如果是微服務(wù)的話,那么服務(wù)間的相互調(diào)用是很常見的。如果處理不當(dāng),會(huì)使得各應(yīng)用之間相互依賴,無法獨(dú)立的運(yùn)行。導(dǎo)致開發(fā)、測(cè)試、部署都很麻煩。
為了避免這樣的問題出現(xiàn),結(jié)合如下兩個(gè)約束:

符合行業(yè)規(guī)約

獨(dú)立性

故使用RESTful方式,作為應(yīng)用間通信的方式。這也是微服務(wù)推薦的通信方式!
應(yīng)用間調(diào)用會(huì)出現(xiàn)Model的依賴,故這里將Model從包提升為模塊。方便后續(xù)如果有其它應(yīng)用要依賴時(shí),可直接依賴Model模塊,而不是整個(gè)應(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包移動(dòng)到了intellijweb2-model模塊中,同時(shí)新增了param和result包!

測(cè)試

SpringBoot本身提供了較為完善的測(cè)試功能。包括單元測(cè)試、Mocker、Spy等。
基于如下幾個(gè)考慮:

易于測(cè)試:我接觸的很多開發(fā)人員是不喜歡寫測(cè)試的。如果測(cè)試代碼不易編寫,那就更不愿意寫了。

不影響環(huán)境:我期望的是在發(fā)布時(shí)是包含測(cè)試的,測(cè)試不通過即不能發(fā)布。也就是說在部署時(shí)測(cè)試,會(huì)使用正式環(huán)境的庫表數(shù)據(jù),所以在測(cè)試時(shí)不能影響到這些數(shù)據(jù)。

小范圍測(cè)試:以最少的代碼,覆蓋最核心的代碼邏輯

故決定只對(duì)Service測(cè)試,原因如下:

在上面的分層架構(gòu)里描述了各層的職責(zé),可以看出,核心業(yè)務(wù)都在Service層,Controller和Model都沒有業(yè)務(wù)邏輯,只是一些標(biāo)準(zhǔn)化代碼,沒必要測(cè)試

SpringBoot對(duì)Controller的測(cè)試是在不同的線程內(nèi),不支持事務(wù),如果在正式環(huán)境測(cè)試的話,會(huì)影響正式庫數(shù)據(jù)

部署

SpringBoot可以直接打包為jar包,直接運(yùn)行啟動(dòng)。這很方便,但是如果想快速的橫向擴(kuò)容,配置文件就是一個(gè)問題。因?yàn)椴煌瑱C(jī)器上的配置并不是完全相同的。
有兩個(gè)方案可以解決:

Docker

配置服務(wù)器

從便利性考慮,還是選擇配置服務(wù)器。
配置文件中均是開發(fā)環(huán)境配置,方便開發(fā)人員直接開發(fā)、測(cè)試。
在正式環(huán)境中,應(yīng)用啟動(dòng)時(shí)會(huì)從配置服務(wù)器獲取對(duì)應(yīng)的配置,覆蓋本地測(cè)試進(jìn)行部署。

代碼生成OR封裝

在結(jié)束之前,先問個(gè)問題?你是喜歡代碼生成、還是封裝?

代碼生成就類似Mybatis這樣生成了對(duì)應(yīng)的文件,邏輯透明。你可以去改

封裝就類似Hibernate,你寫個(gè)對(duì)象,然后對(duì)對(duì)象操作就行了,底層數(shù)據(jù)庫操作由Hibernate來處理

我個(gè)人更偏向代碼生成,理由是:

簡(jiǎn)單:易于使用,易于上手

行業(yè)標(biāo)準(zhǔn):生成的代碼是行業(yè)標(biāo)準(zhǔn)代碼,只要熟悉Mybatis,Spring就可以直接上手(而Mybatis和Spring目前是互聯(lián)網(wǎng)標(biāo)配)。如果公司內(nèi)部進(jìn)行一些封裝,那么新手需要先理解這些封裝,增加了學(xué)習(xí)成本。

基于上面的原因,再考慮到其實(shí)我們的框架都是符合規(guī)約的(RESTful,JSR303,覆寫,Jackson),故對(duì)于標(biāo)準(zhǔn)CRUD,我們可以一鍵生成!

一鍵生成

其實(shí)到上面一節(jié),整個(gè)框架應(yīng)該已經(jīng)符合預(yù)期了!但是為了得到超預(yù)期的效果,我們來更進(jìn)一步!

我們先看目前的開發(fā)流程:

設(shè)計(jì)數(shù)據(jù)表

生成Model,Mapper

編寫Param,Result

編寫Respository

編寫Service

編寫Controller

編寫測(cè)試

執(zhí)行測(cè)試

提交代碼

對(duì)于一個(gè)典型的CRUD操作,這里有多少重復(fù)代碼呢?
篇幅有限,舉個(gè)簡(jiǎn)單的例子:現(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 {
            //驗(yàn)證失敗
            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)一處理?
所以,我們完全可以對(duì)相應(yīng)的代碼進(jìn)行自動(dòng)生成,盡可能的降低模板代碼的手動(dòng)編寫。對(duì)于標(biāo)準(zhǔn)的CRUD邏輯,我們可以做到如下的開發(fā)流程:

設(shè)計(jì)數(shù)據(jù)表

生成CRUD,包括測(cè)試(我們測(cè)試的是Service,想想測(cè)試代碼和Controller代碼有多少區(qū)別?)

執(zhí)行測(cè)試

提交代碼

對(duì)于不可重復(fù)生成的文件,我們可以設(shè)置"存在即不覆蓋",在最大限度的提高開發(fā)效率的前提下,降低誤操作。

總結(jié)

如上即是我基于約束所做的Web推導(dǎo)!目前的主要問題還是在Model層面:

數(shù)據(jù)表映射為Model是否是合理的?

基于Model的操作是否合適?

基于上面Param、Result和Model的關(guān)系圖來看,實(shí)際上Param、Result和Model大部分情況下都不是契合的!把這些Param、Result限制在Model上是否合適?數(shù)據(jù)結(jié)構(gòu)是否清晰?

目前個(gè)人覺得基于data的transform、filter、map操作更適合web開發(fā)(我會(huì)另開一篇討論這個(gè))!或者你有什么好的方案,歡迎指教?

公眾號(hào):ivaneye

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/68684.html

相關(guān)文章

  • [原創(chuàng)]nim與rust的特點(diǎn)比較

    摘要:與的特點(diǎn)比較這兩個(gè)目前都是小眾語言做了些時(shí)間的研究寫了點(diǎn)東西有了點(diǎn)心得相似點(diǎn)有衛(wèi)生宏區(qū)別與的不衛(wèi)生宏在類或定義體之外定義函數(shù)代碼沒有分成頭與實(shí)現(xiàn)體例如的頭與實(shí)現(xiàn)的與定義的接口定義與實(shí)現(xiàn)定義是分開的而與是不分開的運(yùn)用函數(shù)式編程高階函數(shù)目前是新 nim與rust的特點(diǎn)比較 這兩個(gè)目前都是小眾語言,做了些時(shí)間的研究,寫了點(diǎn)東西有了點(diǎn)心得 相似點(diǎn): 有衛(wèi)生宏.區(qū)別與C++的(不衛(wèi)生)宏 在類...

    DevTalking 評(píng)論0 收藏0
  • 一張圖教你快速玩轉(zhuǎn)vue-cli3

    摘要:前言本文系統(tǒng)的梳理了搭建項(xiàng)目的常見用法,目的在于讓你快速掌握獨(dú)立搭建項(xiàng)目的能力。思維導(dǎo)圖接下來,我們根據(jù)思維導(dǎo)圖,一步步來解釋和實(shí)現(xiàn)我們的目標(biāo)。這確保了最終包里數(shù)量的最小化。但是如果其中一個(gè)依賴需要特殊的,默認(rèn)情況下無法將其檢測(cè)出來。 前言 本文系統(tǒng)的梳理了vue-cli3搭建項(xiàng)目的常見用法,目的在于讓你快速掌握獨(dú)立搭建vue項(xiàng)目的能力。你將會(huì)了解如下知識(shí)點(diǎn): 如何安裝項(xiàng)目插件 添加...

    chaosx110 評(píng)論0 收藏0
  • 精讀《Typescript2.0 - 2.9》

    摘要:比如或者都會(huì)導(dǎo)致函數(shù)返回值類型時(shí)。和特性一樣,等于是函數(shù)返回值中的或。注意對(duì)比下面的寫法對(duì)于,它的返回值是可迭代的對(duì)象,并且每個(gè)類型都是或者。首先是不支持方法重載的,是支持的,而類型系統(tǒng)一定程度在對(duì)標(biāo),當(dāng)然要支持這個(gè)功能。 1 引言 精讀原文是 typescript 2.0-2.9 的文檔: 2.0-2.8,2.9 草案. 我發(fā)現(xiàn),許多寫了一年以上 Typescript 開發(fā)者,對(duì) T...

    william 評(píng)論0 收藏0
  • 蠎周刊 2015 年度最贊

    摘要:蠎周刊年度最贊親俺們又來回顧又一個(gè)偉大的年份兒包去年最受歡迎的文章和項(xiàng)目如果你錯(cuò)過了幾期就這一期不會(huì)丟失最好的嗯哼還為你和你的準(zhǔn)備了一批紀(jì)念裇從這兒獲取任何時(shí)候如果想分享好物給大家在這兒提交喜歡我們收集的任何意見建議通過來吧原文 Title: 蠎周刊 2015 年度最贊Date: 2016-01-09 Tags: Weekly,Pycoder,Zh Slug: issue-198-to...

    young.li 評(píng)論0 收藏0
  • 通過demo學(xué)習(xí)OpenStack開發(fā)所需的基礎(chǔ)知識(shí) -- 軟件包管理

    摘要:不幸的是,在軟件包管理十分混亂,至少歷史上十分混亂。的最大改進(jìn)是將函數(shù)的參數(shù)單獨(dú)放到一個(gè)的文件中這些成為包的元數(shù)據(jù)?;诘陌姹咎?hào)管理。的版本推導(dǎo)這里重點(diǎn)說明一下基于的版本號(hào)管理這個(gè)功能。開發(fā)版本號(hào)的形式如下。 為什么寫這個(gè)系列 OpenStack是目前我所知的最大最復(fù)雜的基于Python項(xiàng)目。整個(gè)OpenStack項(xiàng)目包含了數(shù)十個(gè)主要的子項(xiàng)目,每個(gè)子項(xiàng)目所用到的庫也不盡相同。因此,對(duì)于...

    blastz 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<