接下來三天,我會講解如何寫出乾淨架構(Clean Architecture)的程式碼。
現在坊間有許多書籍和文章在介紹為什麼要乾淨架構和什麼是乾淨架構,例如:為什麼要使用洋蔥架構、每一層代表什麼意思、什麼是SOLID原則。
有些進階的書籍會給出聖經般的程式碼,例如:六邊形架構(Hexagonal Architecture)。但是,開發者們平常不是那麼思考的,甚至也根本很難把程式寫成那樣,就算真的按照教科書把程式寫成六邊形架構,後續接手維護的人也不一定有辦法順利擴充和修改。
因此,在乾淨架構實戰三部曲中,我不會用那種近乎苛求的範式作為示例,取而代之的是,我會用平易近人的寫法解決一個真實的案例作為示範。雖然是簡單的實作,但真的能夠滿足乾淨架構原則,也更容易被多數人接受。
在許多手遊和商業網站上,為了提升客戶留存率,都會使用簽到任務作為手段。這次,我們要來實作一個簽到任務,當有人簽到,那麼就可以獲得一些獎勵。
若是不間斷的每天簽到,那就會得到加碼禮盒。開禮盒則會獲得額外獎勵。一但簽到滿七天或未連續簽到,就會開啟新一輪的週期,獎勵重新計算。
讓我們來完成這樣的功能需求吧!
使用者連續登入的獎勵列表如下:
Days | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th |
---|---|---|---|---|---|---|---|
Diamond | 10 | 10 | 15 | 15 | 30 | 30 | 100 |
Extra Box | 0 | 100 | 0 | 0 | 100 | 0 | 100 |
為了簡化問題,我們讓每次開禮盒都是獲得100鑽石。
以表為例,若使用者連續登入四天,那在第五天登入時可以獲得30鑽石和一個禮盒。
每次使用者進入首頁,他可以看到一個簽到表,這告訴使用者他的簽到進度和領到的獎勵總額。
有兩種截然不同的設計風格:資料導向(data-oriented)設計和領域驅動設計。
在首部曲,我們先以資料導向設計開始,並且解釋為什麼這不會是主流的設計方案。
資料導向設計的意思是,當我們看到問題,我們首先先想到該如何儲存資料,接著透過操作定義好的資料來解決問題。
因此,當我們拿到問題,第一個步驟就是思考要用什麼資料庫、要怎麼定義綱要(schema)並且決定資料的格式。
讓我們回到簽到任務。假設我們要設計一個小型單體,我們通常會用關聯式資料庫,因此我們首先要定義表格。
最直覺的作法就是把每次的簽到者和簽到日期都儲存下來,這樣我們就可以根據簽到紀錄推算可以領到多少獎勵。
User | Sign Date |
---|---|
John | 2022-01-01 |
Mary | 2022-01-01 |
John | 2022-01-02 |
Mary | 2022-01-02 |
John | 2022-01-03 |
從上表得知,如果今天是2022-01-04
,那麼當John簽到時可以拿到15鑽石,至於Mary則因為沒有連續簽到所以開始一個新的簽到週期,只能拿到10鑽。
整個運算邏輯是先過濾出對應的使用者紀錄,接著根據日期排序,最後取得最新的N筆資料。透過計算連續簽到的日期,就可以知道這次能拿到多少獎勵。
這樣的設計看起來很理想,但有一個問題,當使用者簽到根本沒有中斷過,也就是連續簽到時間超過N天,那麼這樣的資料就無法讓我們知道現在的獎勵有多少。
儘管如此,我們也不希望讓N變得很大,使一次拉取的資料過多影響資料庫效能。因此,我們試著在資料面上下功夫,加一個新的欄位:鑽石。
User | Sign Date | Diamond |
---|---|---|
John | 2022-01-01 | 10 |
John | 2022-01-02 | 10 |
John | 2022-01-03 | 15 |
John | 2022-01-04 | 15 |
John | 2022-01-05 | 30 |
John | 2022-01-06 | 30 |
John | 2022-01-07 | 100 |
John | 2022-01-08 | 10 |
對於John來說,在2022-01-09
簽到會拿到10鑽,因為我們可以知道上一個週期結束在2022-01-07
。
故事還沒結束,當使用者數量變多,這對於關聯式資料是個挑戰。每一次使用者進入首頁,我們都必須要從資料庫拉取定量(至少N+1
筆)資料,並且透過複雜的計算才能得到結果。
這個過程很沒效率。
為了提升首頁的效能,我們通常會引入快取來記住簽到狀態。但是,你要怎麼確保快取沒有資料不一致?也許我們可以加入一些手段,例如更新資料庫的時候清除快取等來確保快取是即時的。
要確保快取一致性有很多種作法,在之後的文章中會仔細介紹,但無論哪種作法都沒有辦法百分之百確保快取一致,只能盡可能提高機率。換言之,這樣的做法複雜度會很高,且效率不佳。
根據上述介紹,我們發現即使快取也有非常多面向需要考慮,整個資料導向設計很複雜,且過程非常艱辛。
因此,領域驅動設計誕生了。
明天,我們會用乾淨架構的作法來解決簽到問題,並且會實作單元測試,以測試導向開發(Test-driven develop, TDD)的流程進行實作。
根據明天提供的流程,你可以輕鬆運用在你碰到的所有問題上,並簡化設計流程。讓我們明天見。