軟體系統架構是建構者賦予系統的樣貌,而該樣貌是由不同元件組合而成,元件之間會有不同的合作與溝通方式,目的是為了讓軟體系統在開發、部署、運行和維護都能輕鬆理解與開發,也讓系統的生命週期成本趨近最小化,使程式設計師生產力最大化。—《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 部份定義需求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37interface StudentController {
/**
* 取得 Student 所有資料
*/
fun getStudentData(): MutableList<Student>
/**
* 新增 Student 資料
*/
fun addStudentData(Student) student: : Student
/**
* 利用姓名查詢學生資料
*/
fun getStudentByName(String) name: : ResponseEntity<List<Student>>
/**
* 修改學生全部資料
*/
fun updateStudent(Int, student: Student) id: : ResponseEntity<Student?>
/**
* 修改學生信箱(欲更新部份資料)
*/
fun updateStudentEmail(Int, student: Student) id: : ResponseEntity<Student?>
/**
* 刪除學生資料
*/
fun deleteStudent(Int) id: : ResponseEntity<Any>
}implement controller 進行實作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class StudentControllerImpl( 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 定義業務邏輯需求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37interface 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
}Implement Service 進行實作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class StudentServiceImpl( 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)
}修改 Controller 對業務邏輯層的呼叫請求方法(原先是直接使用 DAO 物件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class StudentControllerImpl( 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 供大家參考