Play! Framework 系列(二):play 的專案結構

ScalaCool發表於2017-09-06

本文由 Shaw 發表在 ScalaCool 團隊部落格。

Play! Framework 系列(一)中我們初步瞭解了一下 Play! 的各種特性以及優勢,那麼從現在開始我們將正式接觸 Play!。本文將介紹一下 Play! 的整體結構,然後通過一個非常簡單的例子來闡述各個結構之間的關係以及如何利用 Play! 約定的結構去合理地組織我們的業務邏輯。

結構概覽


Image of play
Image of play

上圖為基於 Play! 而建立的一個簡單的 Web 應用,在上一篇文章中我們說過 Play! 是「ROR」風格的框架,通過上圖我們也可以看到 Play! 是典型的 MVC 架構框架,另外 Play! 也採用 「約定由於配置」,我們只需要按照其約定的結構去組織我們的程式碼就可以很輕鬆地實現一個 Web 應用,那麼接下來我們就去了解一下 Play! 中各個結構的特點以及功能吧。

業務描述

我們將通過實現一個小應用的方式去了解 Play! 的基本結構,這樣會更加清晰一些。需求描述:

  • 實現一個簡單的公司員工資訊列表。

可以看到,我們將要實現的 Web 應用非常簡單,接下來我們就通過這個小小的需求去把玩一下 Play! 吧。

app

app
 └ controllers
 └ models
 └ views複製程式碼

目錄 app 排在結構圖中的最上面,因為是按照首字母排列的,所以它理應在最前面。當然,它在我們整個 Play 應用中也是非常重要的,幾乎我們所有的業務程式碼都包含在該目錄下面,既然它如此重要,排在最前面也無可厚非。在 app 下三個子目錄,分別是:controllers、models 以及 views。

我們也可以在 app 目錄下增加一些目錄,比如,我們需要利用 Play! 的 Filter (後面會介紹)來實現一些需求,那麼我們可以在該目錄下新增一個 filters 目錄,專門用來管理 Filter 的業務邏輯。例如:

app
 └ controllers
 └ models
 └ views
 └ filters複製程式碼

接下來我們將詳細介紹該目錄下的三個核心結構:controllers、models 以及 views。

models

在 MVC 結構的 Web 應用中,M 對應的就是 Model,在 models 下,我們實現資料訪問的一些邏輯,一般來說,資料庫中的一個表就對應一個 model 類。例如:

我們將要顯示「員工」列表,這裡我們需要資料庫中的「員工表」,那麼在 models 下,我們建立一個表示員工資訊的 model:

case class Employee (
  id: Long,
  name: String,
  sex: String,
  position: String
)複製程式碼

一般情況下,我們也需要在 models 下實現運算元據庫的邏輯,但是當業務比較複雜的時候,整個檔案看上去會特別凌亂,並且後期也不好維護,所以這裡我們引入 services,我們將在 services 下實現所有與資料庫打交道的邏輯,而 models 下,我們只需要它定義相應的 model 類就可以了。

app
 └ controllers
 └ models
 └ views
 └ services複製程式碼

services

我們將在 services 下新建一個 EmployeeService 去實現員工資訊的查詢操作:

注:本文不涉及資料庫,所以在這裡我們把資料都寫死,資料庫連線後面的文章會詳細講解。

class EmployeeService {

  val jilen = Employee(
    id = 1,
    name = "Jilen",
    sex = "男",
    position = "全乾工程師"
  )

  val yison = Employee(
    id = 2,
    name = "Yison",
    sex = "女",
    position = "程式設計師鼓勵師"
  )

  def getEmployees: Seq[Employee] = Seq(jilen, yison)
}複製程式碼

views

View 對應的就是 MVC 結構中的 V,在該結構下,我們實現程式中的檢視,也就是利用 Play! 的模板去實現 html 頁面,在 view 中,我們一般只做資料的渲染,很少實現複雜的邏輯。為了呈現員工列表,我們在 views 下建立一個名為 employeeList.scala.html 的檔案,在該檔案下,我們主要實現資料的渲染,這裡只寫一些主要的程式碼:


@(employees: Seq[Employee])

<table class="employee-list">
  <tr>
    <th>編號</th>
    <th>姓名</th>
    <th>性別</th>
    <th>職位</th>
  </tr>
  @for(e <- employees){
    <tr>
      <td>@e.id</td>
      <td>@e.name</td>
      <td>@e.sex</td>
      <td>@e.position</td>
    </tr>
  }
</table>複製程式碼

controllers

前面我們建立好了 model、servic 以及 view,那如何將 model、service 中的資料渲染到 view 中去呢?這個時候就需要 controller 了,Controller 對應於 MVC 中的 的 C,在 controllers 下面,我們需要實現一些列的 action,通過這些 action 來將整個 Web 程式的資料聯絡在一起。為了將前面建立的 model、service 以及 view 聯絡起來,我們在 controllers 下建立一個 EmployeeController:


class EmployeeController @Inject() (
  cc: ControllerComponents
) extends AbstractController(cc) {

  val employeeSerivce = new EmployeeSerivce

  def employeeList = Action { implicit request: Request[AnyContent] =>
    val employees = employeeSerivce.getEmployees()
    Ok(views.html.employeeList(employees))
  }
}複製程式碼

這裡我們簡單介紹一下 Play 中的 Action,Play 中的 「Action」 實際上是一個「特質(trait)」,我們上面的程式碼實現了一個 「Action」, 這裡實際上是使用了 object Action,然後 「object Action」 中的 「apply」 方法會返回一個 Action:


// object Action 的 apply 方法

final def apply(block: ⇒ Result): Action[AnyContent]複製程式碼

conf


conf
 └ application.conf
 └ routes複製程式碼

在 conf 下面,我們主要放置整個專案的配置檔案和路由檔案。

application.conf

該檔案將配置 Play! 應用的一系列資訊,比如 secret key,資料庫資訊等,由於我們的應用比較簡單,所以這裡不需要配置該項,在後面的文章中,我們將專門介紹如何管理 application.conf。

routes

前面我們實現了 model、service、controller 以及 view,那我們如何通過瀏覽器去訪問我們的應用呢,這裡就需要使用「路由」了,應用程式的所有路由都將在 routes 中實現,這些路由就是應用程式的入口。例如:

要想訪問我們之前實現的「員工列表」,我們就需要在 routes 中指定相應的路由:


GET    /employee/employee-list    controllers.EmployeeController.employeeList複製程式碼

指定好路由之後,當我們在瀏覽器中輸入 http://localhost:9000/employee/employee-list 的時候,就能訪問到「員工列表」頁面了。


Image of employee-list
Image of employee-list

關於 routes,我們在 route 檔案中只是寫了這麼一段去指定,當編譯完成之後,我們在 target/scala-2.12/routes/main/router/ 下可以看到一個名為 Route.scala 的檔案,在檔案的末尾可以看到:


def routes: PartialFunction[RequestHeader, Handler] = {

    case controllers_EmployeeController_employeeList0_route(params) =>
      call {
        controllers_EmployeeController_employeeList0_invoker.call(EmployeeController_0.employeeList)
      }
  }複製程式碼

可見其實 routes 在 play! 中的實現是一個方法,它是一個「偏函式」當某個請求被匹配到了就呼叫相應的方法,如果沒有匹配到則報錯,所以我們也可以自己實現某個路由,而不用 play! 的這種方式,當然用 play! 約定好會更加清晰和簡單。

在介紹完 routes 之後,我們有必要知道一下當我們在瀏覽器中輸入某個連結的時候,play! 的各個模組之間是如何呼叫的,如下圖:


Image of play-mvc
Image of play-mvc

當我們訪問某個連結的時候,該連結就是對應的一個路由,該路由會去匹配某個 Controller 中的 Action,接下來 Action 要去呼叫所依賴的 Service 中的方法,這些方法將資料獲取到傳遞給 Action,然後 Action 將這些資料送給 View,View 就會將我們所需要的頁面渲染出來了。這個流程如圖中的實線所示,同時 Controller 也會依賴 Model,有時候 View 也會去依賴 Model 以及 Service。

build.sbt

該檔案用來定義我們專案的一些基本資訊以及專案所需要的一些依賴的資訊,比如專案的名稱、所屬組織、版本資訊、scala 的版本以及一些依賴的定義等等,在我們的應用中,build.sbt 可以這樣定義:


name := "HelloWorld"
organization := "com.shawdubie"

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.12.2"

libraryDependencies += guice
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.1.0" % Test複製程式碼

build.sbt 檔案在 sbt 啟動的時候就會被讀取,然後 sbt 就會去載入我們在裡面定義的一些資訊,比如我們宣告的一些依賴。build.sbt 可以包含許多資訊,關於更詳細的我們後面再討論,這裡只需要知道她。

project


project
 └ build.properties
 └ plugins.sbt複製程式碼

該目錄下主要放置 sbt 構建之後的檔案,在構建之前,該目錄下一般就只有上面所列的兩個檔案。

build.properties

這裡定義了該專案所依賴的 sbt 的版本資訊,例如該專案中 sbt 的版本就可以這樣宣告:

sbt.version=0.13.15複製程式碼

plugins.sbt

在該檔案下我們宣告該專案所依賴的一些外掛,比如我們使用了 play sbt 外掛:

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3")複製程式碼

結語

本文通過一個例子讓我們大致瞭解了 Play! 的基本結構,文中有一些一筆帶過的內容我們將在後面的文章中詳細介紹,這裡只需要知道就可以了。本文的例子請戳 原始碼連結

相關文章