前一篇的小結有提到 Service 應該要把取資料的邏輯切出去,讓 Service 只專注在處理資料,而取資料的邏輯正是放在 Repository 中,這樣做的好處是當有其他模組需要取相同資料的時候,只需要從 Repository 調用即可,不需要再寫一次取資料的程式碼。
在 src
下新增 repositories
資料夾,並建立 todo.repository.ts
,將 TodoModel 相關操作抽離至此, TodoRepository
程式碼如下:
import { QueryFindOneAndUpdateOptions } from 'mongoose';
import { TodoModel, TodoDocument } from '../models/todo.model';
export class TodoRepository {
public async addTodo(content: string): Promise<TodoDocument> {
const todo = new TodoModel({ content, completed: false });
const document = await todo.save();
return document;
}
public async getTodo(id: string): Promise<TodoDocument | null> {
const todo = await TodoModel.findById(id);
return todo;
}
public async getTodos(limit: number, skip: number): Promise<TodoDocument[]> {
const todos = await TodoModel.find().skip(skip).limit(limit);
return todos;
}
public async completedTodo(id: string, completed: boolean) {
const options: QueryFindOneAndUpdateOptions = {
new: true,
runValidators: true
};
const todo = await TodoModel.findByIdAndUpdate(id, { completed }, options);
return todo;
}
public async removeTodo(id: string) {
const todo = await TodoModel.findByIdAndRemove(id);
return todo;
}
}
將操作 Model 的部分抽離至 Repository 後,Service 只需要針對資料進行處理,並把相關參數給 Repository 取資料即可。下方為修改後的 TodoService
:
import { TodoRepository } from '../../../repositories/todo.repository';
import { TodoDTO } from '../../../dtos/todo.dto';
import { DefaultQuery } from '../../../types/request.type';
export class TodoService {
private readonly todoRepo = new TodoRepository();
public async getTodos(
limit: number = DefaultQuery.LIMIT,
skip: number = DefaultQuery.SKIP
): Promise<TodoDTO[]> {
const todos = await this.todoRepo.getTodos(
Math.min(limit, DefaultQuery.MAX_LIMIT),
skip
);
const dtos = todos.map(todo => new TodoDTO(todo));
return dtos;
}
public async getTodo(id: string): Promise<TodoDTO | null> {
const todo = await this.todoRepo.getTodo(id);
const dto = todo ? new TodoDTO(todo) : null;
return dto;
}
public async addTodo(content: string): Promise<TodoDTO> {
const document = await this.todoRepo.addTodo(content);
const dto = new TodoDTO(document);
return dto;
}
public async completedTodo(id: string, completed: boolean): Promise<TodoDTO | null> {
const todo = await this.todoRepo.completedTodo(id, completed);
const dto = todo ? new TodoDTO(todo) : null;
return dto;
}
public async removeTodo(id: string): Promise<TodoDTO | null> {
const todo = await this.todoRepo.removeTodo(id);
const dto = todo ? new TodoDTO(todo) : null;
return dto;
}
}
資料夾結構如下:
├── src
| ├── index.ts
| ├── app.ts
| ├── app.routing.ts
| ├── + bases
| ├── + common/resonse
| ├── + exceptions
| ├── main
| | └── api
| | ├── todo.controller.ts
| | ├── todo.service.ts //本篇修改
| | └── todo.routing.ts
| ├── + models
| ├── repositories
| | └── todo.repository.ts //本篇新增
| ├── + dtos
| ├── + types
| ├── + environments
| ├── + database
| └── + validators
├── package.json
└── tsconfig.json
為了方便維護系統,我在這邊與大家分享我自己的規範:
一個 Repository 只處理一個 Model,要取得多個不同 Collection 資料的話,應該設計多個 Repository,並在 Service 中使用這些 Repository,畢竟 Service 才是處理資料的地方!
Repository 應該只專注於操作 Model,所有傳入的參數都在 Service 中處理好再給 Repository 使用,這樣會是比較好的職責分離法,以上方 TodoService
的 getTodos
為例,在 Service 就將 limit
處理好,這樣 TodoRepository
就可以專注在 TodoModel
上。
多了 Repository 以後,所有元件都有明確的職責了,這就是 比較符合現代大型專案的 MVC 分層架構 。現在的 TodoList 並沒有辦法讓多名使用者來體驗,因為沒有帳戶的機制,下一篇將會帶大家設計帳戶認證機制!