接下來就要設計 Model
與建立 Repository
來跟資料庫進行交互。
首先,設計 Model
的範本是來自 Cheseto Restaurant POS App - Full Preview,此範本包含四個 table:
- Table:餐廳裡的桌子
- MenuItem:菜單品項
- Order:訂單
- Customer:客人的資訊
完成 Model
後,先寫出 repository.base
再套用到各自的 table 上,以上。
Github:et860525/restaurant-management
設計 Model
以上資料庫的重點為:
- 一個顧客 (Customer) 可以有多張訂單 (Order)
- 一張桌子 (Table) 可以有多張訂單 (Order)
- 訂單 (Order) 與 菜單品項 (MenuItem) 是多對多 (many-to-many) 的關係,而這裡使用 Explicit many-to-many relations,原因是我想將菜單品項的數量放在裡面
以下是 Prisma 的程式碼:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Customer {
id Int @id @default(autoincrement())
name String
phone String
email String @unique
address String
orders Order[]
}
model MenuItem {
id Int @id @default(autoincrement())
name String
description String?
price Float
orders OrderItem[]
}
model Order {
id Int @id @default(autoincrement())
customer Customer @relation(fields: [customerId], references: [id])
customerId Int
table Table @relation(fields: [tableId], references: [id])
tableId Int
items OrderItem[]
tag String @default(dbgenerated("lpad(nextval('order_tag_seq')::text, 6, '0')"))
status Order_status @default(PLACED)
payment_method Payment @default(Cash)
createdAt DateTime @default(now())
}
model OrderItem {
order Order @relation(fields: [orderId], references: [id])
orderId Int
menuItem MenuItem @relation(fields: [menuItemId], references: [id])
menuItemId Int
quantity Int
@@id([orderId, menuItemId])
}
model Table {
id Int @id @default(autoincrement())
number Int
capacity Int
status Table_status @default(Free)
orders Order[]
}
enum Order_status {
PLACED
IN_PROGRESS
COMPLETED
CANCELLED
}
enum Table_status {
Free
Checked_in
Reserved
}
enum Payment {
Cash
Debit
E_wallet
}
首先要注意的是 Order 裡面的 tag
欄位,它的作用是將 tag 顯示成 000001
。 @default(dbgenerated("lpad(nextval('order_tag_seq')::text, 6, '0')"))
:
- dbgenerated():表示有些功能沒辦法再 Primsa 使用,但是在資料庫裡可以
- lpad(string, length[, fill]):將指定字串的左邊填充指定字串到指定長度
- nextval:根據
Sequences
的INCREMENT
來獲得下一個值
由於 order_tag_seq
它的欄位型別是 SEQUENCE
,但是在 Primsa 是無法加入這個型別的欄位,所以這裡必須要先將 migrate
檔案產生出來後,再來改裡面的 SQL 語法。其步驟為:
- 先輸入
prisma migrate dev --create-only
( 參考來源 Customizing migrations ) - 到剛剛產生的 migrate 檔案 (
prisma/migrations/20230310190516_test/migration.sql
) 下新增CREATE SEQUENCE order_tag_seq START 1;
- 再使用
prisma migrate dev
即可完成
如果沒有上面的步驟,而是直接 migrate 就會出現錯誤,因為找不到 order_tag_seq
這個欄位。
Repository
如果是使用 Mongoose 的專案,為了滿足Layered Architecture 就要先建立一個 Repository 來存放與資料庫交互的程式碼,再由需要資料庫資料的相關程式碼來呼叫,如:Service。但在 Prisma 裡就會比較簡單,這是因為 Prisma Client 本身就是一個 Object 直接使用即可,相關的資料格式可以在 Service
檔案裡做調整。
嘗試寫一個 Repository Base
在使用 Prisma Client 時,我一直在嘗試讓程式碼更簡潔。因為我有四個 tables 就要建立四個 Repository
的檔案,我有想過讓每一個 table 都繼承一個 Repository Base
物件,並讓這個 Repository Base
直接實作所有獲取資料的方式,以下只是思考的程式碼並不能執行:
import { PrismaClient, MenuItem, Order, Customer, Table } from '@prisma/client';
export abstract class RepositoryBase {
protected modelName: string = '';
private prisma = new PrismaClient();
public async get(id: number): Promise<MenuItem | Order | Customer | Table | null> {
return await this.[modelName].findUnique({
where: { id: id },
})
}
}
雖然這個樣子可以少寫程式碼,但是在有些 table 有 relations 時,可能就會需要使用到 include
。如果是在 create
的情況下,就要在相應的 Repository
下重寫整個 create
程式碼,那這樣還不如就直接根據相對應的 table,來寫相對應的程式碼就好了。
我有試過使用
switch
,但是那個可讀性還不如寫在各自的Repository
檔案裡
實作
上面有說到,直接使用 Prisma Client 物件就可以了,所以這裡我會直接在 Service
檔案裡直接使用 Prisma Client:
import { Prisma, PrismaClient, MenuItem } from '@prisma/client';
export class MenuItemService {
private readonly prisma = new PrismaClient();
public async get(id: number): Promise<MenuItem | null> {
return await this.prisma.menuItem.findUnique({
where: { id: id },
});
}
public async get_many(
skip: number,
take: number
): Promise<MenuItem[] | null> {
return await this.prisma.menuItem.findMany({
skip: skip,
take: take,
});
}
public async create(
name: string,
description: string,
price: string
): Promise<MenuItem> {
return await this.prisma.menuItem.create({
data: {
name: name,
description: description,
price: Number(price),
},
});
}
public async delete(id: number): Promise<MenuItem> {
return await this.prisma.menuItem.delete({
where: { id: id },
});
}
}
Controller
會將獲得的參數 ( req
或 Controller
本身的參數 ) 送到 Service
裡,Service
會使用 Prisma Client 來跟資料庫交互,最後獲得的結果再回傳給 Controller
。
目前我只完成
MenuItem
table,後面三個參數我會慢慢完成,完成後,就會開始把前端的部分完成
結語
因為官網文件很完整,所以設計 Model
的部分就沒有那麼多的問題。但是在設計 Repository
的部分就卡的比較久一點,就像上面嘗試的部分撰寫的一樣,我一直很想少寫這些近乎重複的程式碼,但也就是這些微的不同就會影響資料顯示的不同,但我還是想先把它們都寫出來後,再來慢慢的修改。