建立第一個 API
本指南將帶你建立一個簡單的 RESTful API,展示如何使用 AppFuse Server 快速開發。
範例:任務管理 API
我們將建立一個簡單的任務(Task)管理 API,包含:
- Entity 定義(使用 Lombok 簡化程式碼)
- Repository 查詢
- Service 業務邏輯
- REST API Controller
步驟 1:定義 Entity
在 entity/task/ 套件中建立 Task.java:
package com.yourcompany.yourapp.entity.task;
import io.leandev.appfuse.entity.AuditableEntity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/// 任務實體
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "task")
public class Task extends AuditableEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(length = 36)
private String id;
@NotBlank
@Size(max = 200)
@Column(nullable = false, length = 200)
private String title;
@Column(length = 2000)
@Size(max = 2000)
private String description;
@Column(nullable = false)
private boolean completed = false;
// ============================================
// 業務方法
// ============================================
/// 標記為完成
public void markAsCompleted() {
this.completed = true;
}
/// 標記為未完成
public void markAsIncomplete() {
this.completed = false;
}
}
說明:
@Getter,@Setter,@NoArgsConstructor:Lombok 自動生成 getter/setter 和預設建構子AuditableEntity:appfuse 提供的基底類別,自動管理createdAt、updatedAt欄位GenerationType.UUID:使用 UUID 作為主鍵,適合分散式系統///:Java 21+ Markdown 文檔註解
步驟 2:建立 Repository
在 repository/task/ 套件中建立 TaskRepository.java:
package com.yourcompany.yourapp.repository.task;
import com.yourcompany.yourapp.entity.task.Task;
import io.leandev.appfuse.repository.SearchableRepository;
import java.util.List;
public interface TaskRepository extends SearchableRepository<Task, String> {
List<Task> findByCompleted(boolean completed);
}
說明:
SearchableRepository:appfuse 擴展的 Repository 介面,支援Filter條件查詢- 泛型參數改為
<Task, String>,對應 UUID 主鍵型別
步驟 3:實作 Service
在 service/task/ 套件中建立 TaskService.java:
package com.yourcompany.yourapp.service.task;
import com.yourcompany.yourapp.entity.task.Task;
import com.yourcompany.yourapp.repository.task.TaskRepository;
import io.leandev.appfuse.exception.NotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
/// 任務服務
@RequiredArgsConstructor
@Service
@Transactional
public class TaskService {
private final TaskRepository taskRepository;
// ============================================
// 查詢方法
// ============================================
@Transactional(readOnly = true)
public List<Task> findAll() {
return taskRepository.findAll();
}
@Transactional(readOnly = true)
public List<Task> findByCompleted(boolean completed) {
return taskRepository.findByCompleted(completed);
}
@Transactional(readOnly = true)
public Optional<Task> findById(@NonNull String id) {
return taskRepository.findById(id);
}
// ============================================
// CRUD 操作
// ============================================
public Task create(@NonNull Task task) {
return taskRepository.save(task);
}
public Task update(@NonNull String id, String title, String description) {
Task task = taskRepository.findById(id)
.orElseThrow(() -> new NotFoundException("Task not found: ${0}", id));
if (title != null) {
task.setTitle(title);
}
if (description != null) {
task.setDescription(description);
}
return taskRepository.save(task);
}
public void delete(@NonNull String id) {
Task task = taskRepository.findById(id)
.orElseThrow(() -> new NotFoundException("Task not found: ${0}", id));
taskRepository.delete(task);
}
public Task markAsCompleted(@NonNull String id) {
Task task = taskRepository.findById(id)
.orElseThrow(() -> new NotFoundException("Task not found: ${0}", id));
task.markAsCompleted();
return taskRepository.save(task);
}
}
說明:
@RequiredArgsConstructor:Lombok 自動生成 final 欄位的建構子,實現依賴注入@Transactional:類別層級預設所有方法都在交易中執行@Transactional(readOnly = true):查詢方法使用只讀交易,提升效能NotFoundException:appfuse 內建例外,支援${0}格式的參數替換
步驟 4:建立 REST Controller
在 controller/task/ 套件中建立 TaskController.java:
package com.yourcompany.yourapp.controller.task;
import com.yourcompany.yourapp.entity.task.Task;
import com.yourcompany.yourapp.service.task.TaskService;
import io.leandev.appfuse.exception.NotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.util.List;
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/tasks")
public class TaskController {
private final TaskService taskService;
@GetMapping
public List<TaskResponse> findAll(@RequestParam(required = false) Boolean completed) {
List<Task> tasks = completed != null
? taskService.findByCompleted(completed)
: taskService.findAll();
return tasks.stream().map(TaskResponse::from).toList();
}
@GetMapping("/{id}")
public TaskResponse findById(@PathVariable String id) {
Task task = taskService.findById(id)
.orElseThrow(() -> new NotFoundException("Task not found: ${0}", id));
return TaskResponse.from(task);
}
@PostMapping
public ResponseEntity<TaskResponse> create(@RequestBody TaskCreateRequest request) {
Task task = new Task();
task.setTitle(request.title());
task.setDescription(request.description());
Task saved = taskService.create(task);
TaskResponse response = TaskResponse.from(saved);
return ResponseEntity.created(URI.create("/api/tasks/" + saved.getId()))
.body(response);
}
@PutMapping("/{id}")
public TaskResponse update(@PathVariable String id, @RequestBody TaskUpdateRequest request) {
Task task = taskService.update(id, request.title(), request.description());
return TaskResponse.from(task);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable String id) {
taskService.delete(id);
return ResponseEntity.noContent().build();
}
@PatchMapping("/{id}/complete")
public TaskResponse markAsCompleted(@PathVariable String id) {
Task task = taskService.markAsCompleted(id);
return TaskResponse.from(task);
}
}
步驟 5:建立 DTO
在 controller/task/ 套件中建立請求/回應 DTO:
package com.yourcompany.yourapp.controller.task;
import com.yourcompany.yourapp.entity.task.Task;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
// 建立任務請求
record TaskCreateRequest(
@NotBlank @Size(max = 200) String title,
String description
) {}
// 更新任務請求
record TaskUpdateRequest(
@Size(max = 200) String title,
String description
) {}
// 任務回應
record TaskResponse(
String id,
String title,
String description,
boolean completed,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
static TaskResponse from(Task task) {
return new TaskResponse(
task.getId(),
task.getTitle(),
task.getDescription(),
task.isCompleted(),
task.getCreatedAt(),
task.getUpdatedAt()
);
}
}
步驟 6:測試 API
Schema 自動建立
app-server 使用 ddl-auto: update 讓 Hibernate 根據 Entity 定義自動建立/更新資料表。
啟動應用程式時,Hibernate 會自動建立 task 資料表。
生產環境建議使用 Flyway 或 Liquibase 管理 Schema 遷移。
啟動應用程式:
./gradlew bootRun
使用 curl 測試:
# 建立任務
curl -X POST http://localhost:8080/api/tasks \
-H "Content-Type: application/json" \
-d '{"title": "Learn AppFuse", "description": "Complete the getting started guide"}'
# 回應範例:{"id":"550e8400-e29b-41d4-a716-446655440000","title":"Learn AppFuse",...}
# 查詢所有任務
curl http://localhost:8080/api/tasks
# 查詢單一任務(使用回傳的 UUID)
curl http://localhost:8080/api/tasks/550e8400-e29b-41d4-a716-446655440000
# 更新任務
curl -X PUT http://localhost:8080/api/tasks/550e8400-e29b-41d4-a716-446655440000 \
-H "Content-Type: application/json" \
-d '{"title": "Learn AppFuse Server", "description": "Updated description"}'
# 標記為完成
curl -X PATCH http://localhost:8080/api/tasks/550e8400-e29b-41d4-a716-446655440000/complete
# 刪除任務
curl -X DELETE http://localhost:8080/api/tasks/550e8400-e29b-41d4-a716-446655440000
專案結構總覽
完成後的專案結構:
src/main/java/com/yourcompany/yourapp/
├── entity/
│ └── task/
│ └── Task.java # Entity(使用 Lombok)
├── repository/
│ └── task/
│ └── TaskRepository.java # Repository(擴展 SearchableRepository)
├── service/
│ └── task/
│ └── TaskService.java # Service(使用 Lombok)
├── controller/
│ └── task/
│ ├── TaskController.java # REST Controller
│ ├── TaskCreateRequest.java # Request DTO
│ ├── TaskUpdateRequest.java # Request DTO
│ └── TaskResponse.java # Response DTO
與傳統寫法的差異
本範例使用 appfuse 和 Lombok 簡化程式碼:
- 不需要手動撰寫 getter/setter、建構子
- 不需要自訂例外類別,使用 appfuse 內建的
NotFoundException - 審計欄位自動管理,繼承
AuditableEntity即可
下一步
- Entity 設計最佳實踐 - 學習 Entity 設計模式(待建立)
- API 設計指南 - RESTful API 設計原則(待建立)
- 測試策略 - 撰寫單元測試與整合測試(待建立)
- 安全性 - 添加認證與授權
參考資源
- app-server 範例 - 更多實際範例