由前後端團隊遠端協作打造社交網站專案的歷程與感想
專案簡介
Simple Twitter 是一個由前後分離模式開發的社群網站,也是在每一位在 Alpha Camp 學習必經的畢業專案,從開發到完成挑戰功能總計 14 天,分別由 2 位前端及 2 位後端一起以遠端協作的方式進行開發。
Simple Twitter Live Demo
最終開發的成果如下:
Simple Twitter Live Demo
Simple Twitter 前端 Repo
Simple Twitter 後端 Repo
在專案中扮演的角色
這次的專案中我和 Rita 負責後端開發,DoubleC 和 YuSian 則負責前端的部分。
後端主要是採用 Node.js、Express 框架及 MySQL 資料庫等技術進行開發,並採取 RESTFul API 風格規劃及開發網站資料所需的 API 。
我負責開發的功能主要包含:
- 規劃資料庫 user、tweet、reply、like、followship models
- 使用者 CRUD 相關的 API
- 管理員 CRUD 相關的 API
- 使用 passport-jwt、jsonwebtoken 進行使用者身分驗證
- 使用 socket.io 開發公開聊天室使用者上線、離線通知與公開聊天室線上使用者列表
- 加入 validator 實作後端資料驗證
- 使用 multer 、imgur-node-api 實作圖片上傳功能
專案本身的挑戰與克服方法
對於我來說這樣的開發模式十分的新鮮,整個專案過程中充滿了很多個「第一次」,包括第一次進行前端、後端分離的專案,以及第一次進行團隊協作並且還是完全線上遠端的型態。
相比之前的單打獨鬥,我認為這次的專案可能會遇到一些與以往經驗不同的挑戰:例如團隊合作。能順利完成一個團隊協作專案的關鍵在於需要一再的溝通、溝通、再溝通!所以在進行專案之前,我就一直在思考如何有效地降低溝通的成本,讓專案執行的過程更順暢,於是面對以下幾個挑戰,我們分別採取了一些方法和工具。
團隊的溝通方法
1. Trello: 專案進度管理
這一次我們專案的進度管理主要使用 Trello 管理,使用 Trello 的好處就在於可以很清楚大家現在各自在做什麼,功能開發到哪一個階段,卡片還可以串接 Git hub、Slack,方便進行 code review ,開發上哪個功能遇到困難也可以立刻讓隊友知道。
2. google 文件: 會議記錄
相信大家都有不少開過很冗長會議的經驗吧~為了促進有效率的開會,組長 Rita 在每一次開會前都會先用 google 文件將要討論的事項列成大綱,在開會前大家也可以把自己的想法、提案寫上去,在會議中也會把最終決定或討論的事項整理在上面。如此一來,開會不僅有效率,也比較有方向、有條理地去討論內容,降低不少時間成本。
3. google 試算表、簡報: 前後端資料串接格式
在規劃 API 時我就在想如何將所有的 API 統整,讓協作的人可以一目了然,於是我就開了一個 google 試算表把所有規劃中的 API 統整,並且依據以資料為中心,再依據格式的不同進行最小粒度切割、分類,並且搭配篩選器的功能使用,如此一來可以協作的人更有系統地去消化這些 API 的格式和功能,在最後後端撰寫 API 文件時也方便對照。
雖然開了一個試算表統整,後端的協作順暢許多,但我發現前端要直接去讀這些生硬的 API 好像有點吃力,於是我又使用 google 簡報來製作「 API 資料格式圖文好讀版」,把所有的頁面都列出,改以畫面為中心,逐一解釋每一個 API 在頁面相對應的功能,以及預期後端會撈出的資料或回應的訊息,還有後端希望前端提供的資料。
於是在開發前期,前後端討論資料格式透過簡報內容就可以很清楚資料大概會長怎麼樣、API 會用在哪些地方、變數的命名,有問題也可以直接反應、修改,大幅了降低前後端溝通的成本,雖然前面會花一些時間製作簡報,但可以快速釐清彼此的需求,我認為是值得的!
4. git flow :遠端協作
我和 Rita git flow 操作的流程是依照開發的功能開不同的分支,當開發完成後再發 PR 經過彼此審核再 Merge 到 Master 上。
在審核別人的程式碼時,可以看到不一樣的思路,蠻有趣的~在這過程中我會檢查程式碼是否符合 user story 、基本規格,甚至分享我覺得可以優化的地方。
專案指定功能開發的過程
關於專案指定功能開發的過程,我們後端的流程如下:
- 研讀測試檔,確認好 Model 和 API 的規格
- 依據測試檔規劃資料庫 Model
- 依據測試檔規劃資料庫 API
- 建立 Model 並確認通過測試檔
- 與前端溝通資料格式、API
- 建立 Seeders
- 分開進行開發 API 並確認通過測試檔
- 藉由發 PR 來 code review 修改優化程式碼並排除 conflict
- 重構程式碼
當然過程中免不了充滿了各種 debug ,但大致上我們後端進行得很順利,在第 5 天時就已經完成了所有 API 的開發。剩下的時間大都在進行優化、依照前端的需求修改資料、幫助前端 git flow 的操作、研究 Socket.io 的文件。
黑客松衝刺挑戰功能的紀錄和心得
雖然說我們後端的進度非常的快速,但是前端兩位隊友因為白天有工作,能運用的時間有限,所以專案開發的進度較落後。顧慮到這樣的狀況以及作品功能完整度的考量,我們把黑克松挑戰的目標放在完成挑戰一: 公開聊天室。
在進行黑客松前我們在網路上看了很多有關 Socket.io 的教學、官網的文件,但即使如此我們還是面臨了一個問題,似乎沒有只單純做 Socket server 的範例(?)大多是要前端跟後端一起做的範例,單純撰寫後端 Socket server 邏輯,沒有前端的配合,無法確定是否正確,而且也不知道是否有成功拿到 user 的資料,因此有別於撰寫指定功能,前端和後端各做各的,黑客松我們是 4 人開線上會議一起開發的。
關於取得進入公開聊天室的使用者資料,我們一開始使用的方式是請前端傳送該名使用者的 id 到後端 server ,然後後端再從資料庫去撈出該名使用者的資訊,但是這一做法取得使用者資料非常麻煩,只要觸發一次事件就要撈一次資料。
後來在網路上搜尋資料發現可以使用 jsonwebtoken 來實作 socket 的驗證,於是透過驗證,順利地取得 userId ,從資料庫找到進入聊天室的使用者資料後,再將使用者資訊存在 socket.user ,就可以直接應用在後續的事件上。
即使黑客松挑戰只有完成一個,過程中很累很辛苦,但大家一起燒腦解決開發過程中的 bug 跟問題,真的是一個有趣的體驗呢。
回顧與收穫
對我來說這個專案帶給我很多很寶貴的收穫:
熟悉後端開發的模式
有別與以往用全端的方式去開發,這一次用純後端的方式開發,明顯有很大的差異,不需要處理畫面,可以讓我專心在處理每一個 API 細節。
舉例來說:使用者追蹤的功能
全端開發
完成基本的功能和基本的錯誤處理(防止自己追蹤自己)
const db = require('../models')
const { Followship } = db
const followController = {
addFollowing: async (req, res) => {
try {
const followerId = req.user.id
const followingId = req.body.id
// cannot follow self.
if (followerId === Number(followingId)) {
return req.flash('success_messages', 'You cannot follow yourself.')
}
await Followship.create({
followerId,
followingId
})
req.flash('success_messages', `You followed @${followingId.account} successfully.`)
return res.redirect('back')
} catch (err) {
console.log(err)
}
}
}
module.exports = followController
後端開發
除了基本的功能與防止自己追蹤自己的錯誤以外,我還增加了更多錯誤處理細節,讓 API 測試可以反應出正確的狀態。
EX:
- 檢查 followerId 、followingId 的 user 確實是存在於資料庫且身分是 user
- followerId、followingId 是否有一方不存在資料庫
- 從資料庫中確認這兩位使用者的關係是否已經存在
- 根據以上的不同情況撰寫回傳的 message
- 回傳適當的 HTTP 狀態碼
const db = require('../models') const { Followship } = db const followController = { addFollowing: async (req, res, next) => { try { const followerId = req.user.id const followingId = req.body.id // check this followingId's role should be user. const followingUser = await User.findOne({ where: { id: followingId, role: 'user' } }) // check this followerId's role should be user. const followerUser = await User.findOne({ where: { id: followerId, role: 'user' } }) // check both followerId and followingId are existed. if (!followingUser || !followerUser) { return res.status(404).json({ status: 'error', message: 'Cannot find this followingId or followerId.' }) } // cannot follow self. if (followerId === Number(followingId)) { return res.status(403).json({ status: 'error', message: 'You cannot follow yourself.' }) } // check followship const followship = await Followship.findOne({ where: { followerId, followingId } }) if (followship) { return res.status(409).json({ status: 'error', message: `You already followed @${followingUser.account}` }) } await Followship.create({ followerId, followingId }) return res.status(200).json({ status: 'success', message: `You followed @${followingUser.account} successfully.` }) } catch (err) { next(err) } } module.exports = followController
了解如何與前端溝通
在撰寫 API 前先確認好前端要的資料需求(透過上述各種工具),在處理回傳前端的資料的時候,我則會依據前端的需求將撈出來的資料客製化成他們需要的格式,而非直接把撈好的資料丟給他們,我想整理過的資料應該會讓他們串接 API 更方便吧。
向同儕學習不同的經驗
這次與 Rita、DoubleC 、 YuSian 一起合作開發讓我從中汲取到很多元的觀點和經驗,這是一個人開發時遇不到的! 很多時候想破頭的(笨)問題往往就在神隊友的助攻下迎刃而解了XD
謝謝我的隊友很 Carry,大家都很負責任,堅持到最後一天(沒有臨陣脫逃真是太感謝了~)才有這麼棒的作品產出,相信這會是我學習開發的歷程中最寶貴的經驗。
注: 本文同步發表於 Medium