軟體系統架構是建構者賦予系統的樣貌,而該樣貌是由不同元件組合而成,元件之間會有不同的合作與溝通方式,目的是為了讓軟體系統在開發、部署、運行和維護都能輕鬆理解與開發,也讓系統的生命週期成本趨近最小化,使程式設計師生產力最大化。—《Clean Architecture》
而本章將要介紹架構—分層架構(Layered Architecture)
,又稱為N層架構模式(N-tier Architecture Pattern),是軟體開發中經常看到的架構之一,它的每一層都有自己所負責的任務,每一層也有許多好處,例如:
而在 Spring Boot 常見的階層架構會將專案分為四個主要類別:
表示層 Presentation Layer
屬於該架構頂層,主要負責 Http 請求、路由處理、身份驗證與Json資料轉換處理,會將資料傳遞到業務邏輯層進行溝通
業務邏輯層 Business Layer
主要處理專案所有相關業務邏輯,包含處理業務規則、流程、資料完整性等,並接收來自表示層的資料請求,進行邏輯處理後,會轉向與資料持久層提交請求並傳遞資料結果。
資料持久層 Persistence Layer
作為應用程式與資料庫之間的抽象層,將業務層需要使用的物件映射到資料庫進行相互轉換與溝通
資料庫層 Database Layer
主要由資料庫組成,所有資料庫相關操作與設定都會於此層處理
在實作上,可參考下圖《 Spring Boot Flow Architecture》,Client 端會與 Controller 層進行 Http 請求溝通,而 Service 層會針對專案業務邏輯進行處理與請求數據,持久層則是利用 DAO 物件進行資料庫溝通實現,達到不同層處理各自的職責。
接下來我們進入實作步驟部份:
首先在專案內建立 Controller
資料夾並將之前的 Controller 改用 Interface
進行定義,此作法主要是為了解耦合,當我們要修改Controller 實現方法時,只要修改實作 Implement 即可
interface StudentController {
/**
* 取得 Student 所有資料
*/
@GetMapping("/students")
fun getStudentData(): MutableList<Student>
/**
* 新增 Student 資料
*/
@PostMapping("/students")
fun addStudentData(@RequestBody student: Student) : Student
/**
* 利用姓名查詢學生資料
*/
@PostMapping("/students/search")
fun getStudentByName(@RequestParam name: String) : ResponseEntity<List<Student>>
/**
* 修改學生全部資料
*/
@PutMapping("/students/{id}")
fun updateStudent(@PathVariable id: Int, @RequestBody student: Student) : ResponseEntity<Student?>
/**
* 修改學生信箱(欲更新部份資料)
*/
@PatchMapping("/students/{id}")
fun updateStudentEmail(@PathVariable id: Int, @RequestBody student: Student): ResponseEntity<Student?>
/**
* 刪除學生資料
*/
@DeleteMapping("/students/{id}")
fun deleteStudent(@PathVariable id: Int): ResponseEntity<Any>
}
@RestController
@RequestMapping("/api")
class StudentControllerImpl(@Autowired val studentDao: StudentDao) : StudentController {
override fun getStudentData(): MutableList<Student> = studentDao.findAll()
override fun addStudentData(student: Student): Student = studentDao.save(student)
override fun getStudentByName(name: String): ResponseEntity<List<Student>>
= studentDao
.findByName(name)
.let {
return ResponseEntity(it, HttpStatus.OK)
}
override fun updateStudent(id: Int, student: Student): ResponseEntity<Student?>
= studentDao
.findById(id)
.run {
this ?: return ResponseEntity<Student?>(null, HttpStatus.NOT_FOUND)
}.run {
return ResponseEntity<Student?>(studentDao.save(this), HttpStatus.OK)
}
override fun updateStudentEmail(id: Int, student: Student): ResponseEntity<Student?>
= studentDao
.findById(id)
.run {
this ?: return ResponseEntity<Student?>(null, HttpStatus.NOT_FOUND)
}
.run {
Student(
id = this.id,
name = this.name,
email = student.email
)
}
.run {
return ResponseEntity<Student?>(studentDao.save(this), HttpStatus.OK)
}
override fun deleteStudent(id: Int): ResponseEntity<Any>
= studentDao
.findById(id)
.run {
this ?: return ResponseEntity<Any>(null, HttpStatus.NOT_FOUND)
}
.run {
return ResponseEntity<Any>(studentDao.delete(this), HttpStatus.NO_CONTENT)
}
}
建立 Data
資料夾存放 DAO
、 Entity
物件,再建立 Service
資料夾準備建立 Service 物件,資料夾結構應如下圖:
建立 Service
物件 StudentService.kt
,建立時如同第一步驟的Controller,先使用 Interface
定義業務邏輯需求再進行實作,最後再將原本的Controller改使用Service進行資料請求,程式如下:
interface StudentService {
/**
* 查詢所有學生資料
*/
fun findAllStudent(): MutableList<Student>
/**
* 新增學生資料
*/
fun addStudent(student: Student): Student
/**
* 查詢符合姓名條件的學生資料
*/
fun findByStudentId(id: Int): Student?
/**
* 查詢符合姓名條件的學生資料
*/
fun findByStudentName(name: String): List<Student>
/**
* 更新學生整個資料
*/
fun updateStudent(student: Student): Student
/**
* 更新學生信箱資料
*/
fun updateStudentEmail(student: Student): Student
/**
* 刪除學生資料
*/
fun deleteStudent(student: Student): Unit
}
@Service
class StudentServiceImpl(@Autowired val studentDao: StudentDao) : StudentService {
override fun findAllStudent(): MutableList<Student> = studentDao.findAll()
override fun addStudent(student: Student): Student =
Student(
name = student.name.trim(),
email = student.email.trim()
).run {
return studentDao.save(this)
}
override fun findByStudentId(id: Int): Student? = studentDao.findById(id)
override fun findByStudentName(name: String): List<Student> = studentDao.findByName(name)
override fun updateStudent(student: Student): Student =
Student(
id = student.id,
name = student.name.trim(),
email = student.email.trim()
).run {
return studentDao.save(this)
}
override fun updateStudentEmail(student: Student): Student =
Student(
id = student.id,
name = student.name,
email = student.email.trim()
).run {
return studentDao.save(this)
}
override fun deleteStudent(student: Student): Unit = studentDao.delete(student)
}
@RestController
@RequestMapping("/api")
class StudentControllerImpl(@Autowired val studentService: StudentService) : StudentController {
/**
* 取得 Student 所有資料
*/
override fun getStudentData(): MutableList<Student> = studentService.findAllStudent()
/**
* 新增 Student 資料
*/
override fun addStudentData(student: Student): Student = studentService.addStudent(student)
/**
* 利用姓名查詢學生資料
*/
override fun getStudentByName(name: String): ResponseEntity<List<Student>>
= studentService
.findByStudentName(name)
.let {
return ResponseEntity(it, HttpStatus.OK)
}
/**
* 修改學生全部資料
*/
override fun updateStudent(id: Int, student: Student): ResponseEntity<Student?>
= studentService
.findByStudentId(id)
.run {
this ?: return ResponseEntity<Student?>(null, HttpStatus.NOT_FOUND)
}.run {
return ResponseEntity<Student?>(studentService.updateStudent(this), HttpStatus.OK)
}
/**
* 修改學生信箱(欲更新部份資料)
*/
override fun updateStudentEmail(id: Int, student: Student): ResponseEntity<Student?>
= studentService
.findByStudentId(id)
.run {
this ?: return ResponseEntity<Student?>(null, HttpStatus.NOT_FOUND)
}
.run {
Student(
id = this.id,
name = this.name,
email = student.email
)
}
.run {
return ResponseEntity<Student?>(studentService.updateStudentEmail(this), HttpStatus.OK)
}
/**
* 刪除學生資料
*/
override fun deleteStudent(id: Int): ResponseEntity<Any>
= studentService
.findByStudentId(id)
.run {
this ?: return ResponseEntity<Any>(null, HttpStatus.NOT_FOUND)
}
.run {
return ResponseEntity<Any>(studentService.deleteStudent(this), HttpStatus.NO_CONTENT)
}
}
此文章有提供範例程式碼在 Github 供大家參考