天天看點

Web 開發太 low,沒技術含量?你可别逗了!

引言

網上經常有這樣的言論:

1.web開發太low,沒技術含量。

2.web開發根本涉及不到多線程的問題等。

對于第一點,我想說技術沒有高低貴賤之分,能把自己領域方向做到極緻的才是最吊的。

對于第二點,談一下個人對web應用的了解。web應用的定義:提供http協定支援的應用。 每一個系統都不是封閉的,肯定得和其它系統或者人互動。http協定因為其簡單、支援廣泛的特性被不同領域的系統作為其輸入輸出的協定。近幾年微服務的出現,越來越多的web應用不再是隻輸出html頁面了。更多的是Restful規範的API接口,json資料格式,以及http協定。

是以說,web應用既然有這麼多的應用場景,肯定有複雜的系統涉及到多線程問題。

本文要講述的是如何開發規範Java Web應用。規範包括:如何分層、每一層的職責、層之間如何互動、資料如何流通等。

我相信大部分人都知道怎麼實作一個功能,也知道最簡單的三層模型Controller、Service、Dao。以及資料模型對象:VO,BO,PO,DTO,Model。但是,我以及我身邊很多的開發其實并不是非常清楚每個元件的定義和職責。是以本文的目标就是理清楚這些概念、元件。

分層

典型的web應用分為三層,即:Controller層、Service層、Dao層。

如下圖所示:

Web 開發太 low,沒技術含量?你可别逗了!

Controller層

Controller層,我認為是系統的Facade。職責包括以下幾點:

  1. 接收系統輸入
  2. 資料校驗
  3. 協定轉化
  4. 系統輸出
  5. 定義系統接口

接收系統輸入

常見的包括從request中提取path variable,query param,request payload、使用者認證資訊等。

資料校驗

基本的資料校驗包括:資料類型,資料取值範圍、資料格式。舉個例子,假設有一個轉賬接口,其中有一個金額字段。這裡對金額字段做的校驗包括:不能為負數。而業務型的檢查包括金額不能大于賬戶餘額、不能大于賬戶類型所對應的最大轉賬額度不應該放在Controller層進行校驗。

協定轉化

協定轉化包含兩個方面。

  1. 系統内部資料類型轉化
  2. 資料内容協定轉化
  3. 資料傳輸格式協定轉化

系統内部資料類型轉化

包括:BO轉化成DTO、BO轉化成VO。這幾種資料模型含義下一節會具體講述。

資料内容協定轉化

舉個例子說明會更加容易了解。假設有一個Open API,功能是傳回User資訊。這個Open API對A公司開放的資訊包括:昵稱、頭像兩個字段,而B公司是本公司的VIP使用者,對其開放的資訊不僅包含昵稱、頭像還包括電話、email等隐私資訊。

在Service層隻有一個傳回UserBO的接口,UserBO包含使用者所有的資訊,在Controller層根據不同公司的類型,生成不同的UserDTO,此過程稱為資料内容協定轉化。

資料傳輸格式協定轉化

包括:把對象序列化成json、xml格式資料、html頁面等

系統輸出

把協定轉化之後的資料,組裝成Http Response輸出給外部應用。

定義系統接口

一個系統提供了那些能力,則由系統接口決定的。接口包含三部分内容:

  1. 輸入值
  2. 接口辨別:url+http method
  3. 傳回值

Service層

service層主要負責系統業務邏輯的處理。上面提到的轉賬金額上限的校驗應該放在此層。service層根據業務系統的複雜度又可以劃分成多層。以兩層為例:

  1. 跟資料表一一對應的資源Service層
  2. 在資源Service層之上的聚合業務邏輯層

資源Service層 一般跟一張表、一個Dao對應。在SOA領域裡,把一部分高度内聚的資源作為一個SOA Service,例如UserSerivce,OrderService等。其它應用不應該直接通路User相關的資料庫,而應該調用UserService。資源高度内聚,便于管理和控制。

在分布式系統如此,在一個系統内部也應該如此。也就是說,一張表也可以作為一個資源,其它的Service不應該直接通路這張表,而應該通過這張表對應的Service來通路。當然,有些時候可以把幾張表的資源内聚到一個Service當中。

換句話說,Dao不應該到處散落在不同的Service中,通路資源應該調用資源對應的Serivce。資源Service層理論上應該涉及很薄的、跟資源相關的業務邏輯。附加dao一些簡單的業務邏輯能力。另外一個職責就是資料類型轉換,也就是PO轉化為BO,後面會詳細講述。

聚合業務邏輯層 這一層是真正核心業務邏輯處理的地方,在資源Service層之上。完全負責處理業務邏輯,不用關心資源通路。

對于不複雜的應用系統來說,大部分的Service其實可以合為一層,有些特别複雜的業務邏輯可以單獨抽象出一層,切記Service角色要清晰。判斷是否清晰最簡單的方式就是能否自然的想出Service的名字。

另外插一句題外話,很多公司或者書籍提倡面向接口程式設計,導緻一個很常見的現象就是一個Service包含兩部分:XXService和XXServiceImpl。這樣寫好處就是接口和實作分離,接口亦是文檔,清晰。壞處就是多了一個類。

我們不應該不假思索的按照慣性思維去實踐,在剛提到的這種面向接口程式設計不是完全可取的。接口的本意是可以有多種實作,也就是可能有多個子類。但是上面提到的這種Service基本上都隻有一個實作類,那麼接口的意義何在?當然并不是說就不需要接口實作分離。

我覺得以下情況可以考慮分離:

  1. 接口可能會有多種實作
  2. SOA系統對外提供服務的Facade Service
  3. 複雜的系統,架構面向接口程式設計更邏輯理清楚元件之間的關系

很顯然對于大部分的web應用,以上三點并不符合,是以我覺得沒必要接口實作分離,多出一個沒有意思類實在是很醜。

Dao層

dao層比較簡單,應該隻負責和資料庫打交道,不應該涉及業務邏輯,隻涉及跟資料存儲相關的邏輯。

資料類型

資料類型一般分為以下幾種:PO、BO、VO、DTO、Model、POJO。

PO(persistence object)

持久化對象,一般表示一張表,屬性跟表字段一一對應。

BO (business object)

業務對象,在業務元件中流通的對象。字段集合可能比PO多,也可能比PO少。一個PO可能對應多個BO。

VO (view object)

視圖對象,隻用來給前端頁面渲染的資料結構。

DTO (data transaction object)

資料傳輸對象,在各個系統間傳輸的對象,一般需要實作Serializable接口。

Model

表單資料模型,一般對應request payload。

POJO (plain ordinary Java object)

隻用來表示資料類型,遊離在系統業務之外的java bean。

資料類型和分層結合

理論上每一種資料類型隻能在特定的層中出現。 po => dao層, 資源Service層 bo => Service層,Controller層 * vo、dto、model => Controller層。

推薦一個 Spring Boot 基礎教程及實戰示例:

​​​https://github.com/javastacks/spring-boot-best-practice​​

如下圖所示:

Web 開發太 low,沒技術含量?你可别逗了!

任何的規範都是靈活的,如果按照上面嚴格執行的話,就會産生很多屬性基本一緻的類,而且類型轉換代碼非常機械化。對于大部分簡單的系統來說,各個類型之間,字段幾乎是完全一緻。

靈活的做法是下層的對象可以上升到上層,比如某一個資源沒有BO,也沒有VO,隻有PO。也就是說PO存在于Dao,Service和Controller三層。但是反之則不行,例如VO、DTO、Model不應該在Service層出現,更不能在Dao層出現。是以最佳實踐則是最少要兩層。

如下圖所示:

Web 開發太 low,沒技術含量?你可别逗了!

另外需要注意一點就是:其它系統的DTO等于自身系統的PO,也就是說上面提到的所有的類型其實是相對于資料流的位置而定的。是以,在Service層流通的DTO其實是PO的角色。但是自身系統對外的DTO就不應該在Service層出現。

結語

做任何事情都需要規範,web開發亦是如此。規範的好處是:整潔、易維護、易了解。規範也是每個程式員進階的必經之路。上述對于分層和資料類型的了解都是個人對于項目開發的思考,可能跟其它規範有出入。規範不是協定,規範是約定并不是強制,隻要清晰可實踐即可。每個人都可以有自己的規範,但是需要要大部分人所能接受了解。

注:以上分層和類型的稱呼隻是定義角色,具體系統中使用的叫法可以不一緻。隻要團隊内部約定好即可。