Domain Driven Design X User Story 淺談從使用者需求走到軟體規格的過程

me
林彥成
2022-04-30 | 5 min.
文章目錄
  1. 1. 使用者旅程圖 (User Journey Map)
  2. 2. 使用者故事 (User Story)
    1. 2.1. 使用者訪談
  3. 3. 領域驅動設計 (Domain Driven Design)
    1. 3.1. 領域 (Domain)
    2. 3.2. 領域模型 (Domain Model)
    3. 3.3. 有界限的語境 (Bounded Context)
    4. 3.4. 通用語言 (Ubiquitous Language)
  4. 4. Designing with types
    1. 4.1. Wrapping primitive types
    2. 4.2. 實際的範例

領域驅動設計 (Domain Driven Design) 是軟體程式碼的結構及語言需符合業務領域中的習慣用法,將實作對應到持續進化的模型。

把專案的重點放在核心領域和領域邏輯

在軟體開發上,通常會在與使用者進行訪談後,產生使用者故事和人物誌去理解和釐清核心領域以及領域邏輯。

小編在前公司曾經歷過讓行銷帶專案,行銷會認為自己為需求方,工作就是提出需求後事情就會自動完成。

工作流程: 老闆下指令 -> 行銷整理 -> 出圖實作 -> 老闆下指令

在這樣的過程中,該怎麼協助專案進行?

常見釐清需求的方式會是透過 User Journey Map 和 User Story 來進行描述

使用者旅程圖 (User Journey Map)

使用者的工作或流程通常會用使用者故事 (User Story) 來進行描述,目的是在特定的產品範圍技術限制下去釐清相關需求。

使用者的工作、流程中的體驗大致區分為前中後三個階段:

  • 使用前的預期: 預期能夠經歷或得到什麼
  • 使用中的體驗: 使用的過程中是否能真的解決問題
  • 使用後的體驗: 相關的社群互動、評價等等

使用者故事 (User Story)

使用者故事常見有兩種描述的句型如下:

  • As a Actor I want to Action So that I can Outcome
  • Given Actor When Action then Outcome

在一句簡單的使用者故事句型中分別包含了 Actor、Action、Outcome,其中對於產品來說最重要的就是要得到使用者的 Why,確認使用者真正想要知道的是什麼。

  • Actor: 誰 (Who) 在什麼情境 (Where or Which) 下使用這個產品
  • Action: 做什麼 (What),需要實作的相關功能
  • Outcome: 強調的是為什麼 (Why) 想看到或得到什麼,決定產品中功能開發的優先順序

以這個部落格的使用者故事當例子:

As a 部落格經營者 I want to 優化文章關鍵字 So that I can 部落格 SEO 成效提升
Given 部落格經營者 When 優化文章關鍵字完成時 then 部落格 SEO 成效提升

使用者訪談

訪談的重點在凸顯痛點及用戶需求,也就是找出背後的 Why,訪談的過程則會專注在

  • 定義研究範圍和問題
  • 辨識出誰是使用者或是顧客
  • 分析資料列出目標
  • 建立使用者的任務流程

訪談的流程

  1. 開場介紹目的和問題
  2. 嘗試發現痛點:
  3. 留下後續紀錄

引導方式期待會是能問到使用者背後真正的原因、動機以及真正想達成的目標,舉個闖紅燈很危險的例子來說。

痛點: 闖紅燈危險

上班常遲到 -> 睡過頭 -> 沒聽到鬧鐘 -> 睡前沒設鬧鐘 -> 跟可愛女孩子聊天聊到忘記設定

該問的

  • 過去經驗
  • 完成任務需要完成哪些事

不該問和假設

  • 價值判斷 ex 是來了解和認識不是來打分數的
  • 引導性問題 ex 你討厭這個按鈕的擺放位置嗎?
  • 想要什麼 ex 對方不一定知道自己想要什麼
  • 假想的解決方案

領域驅動設計 (Domain Driven Design)

  1. 促進跨團隊的溝通、理解領域知識
  2. 專注在核心業務上,業務邏輯不受技術實作細節影響
  3. 模組化利於拆分合設計微服務

做生意的本質是商業,在各個領域中除了技術本身外最重要的就是商業邏輯。

透過將複雜的問題解構,利用領域模型 (Domain Model)、領域詞彙 (Domain Terms)、通用語言 (Ubiquitous Language) 來設計和描述系統,進而可以更快的去了解和在各部門同步使用者的流程、商業模式、系統的運行。

跨部門溝通的目標是建立 Shared Mental Model

領域 (Domain)

能將遇到的商業情境面對的問題和解法切割成問題和解決方案兩種空間

  • Problem Space 依照優先權和外包程度還可以細分成三種類型
    • Core Domain: 有價值不可被取代
    • Supporting Subdomain: 支援性質可能還是需要實作
    • Generic Subdomain: 可以直接外包或購買現成方案
  • Solution Space

領域模型 (Domain Model)

定義領域模型,在商業的世界中除了實作的工程師外最重要的就是該領域的專家 (Domain Expert)。

小編過去曾在教育領域的新創服務,當時的老闆 (領域專家) 就是補教界的名師,而我們就是在領域的相討論中將系統實做出來。

接下來就會以補教業的系統為例子

  1. 了解系統跨足的領域 => 出題、考試、金流
  2. 了解使用案例 => 學生測試積點可到咖啡廳換薯條或飲料 (考試、金流領域)、老師想建立補教系統與智慧題庫協助學生學習 (建題領域)
  3. 透過領域詞彙去定義通用詞彙和切割子領域 => 每日測驗系統、建題系統、兌換系統 (金流領域)

有界限的語境 (Bounded Context)

當有人說出午餐想要吃一號餐,請問是肯德基的一號餐還是麥當勞的一號餐呢? 今天如果領域發生在麥當勞就會知道是大麥克餐。

所以將 “一號餐” 就需要框出範圍被隔離在有界限 (麥當勞或是肯德基) 的語境中,關鍵問題會是怎麼框出範圍?

  • 同領域詞彙 -> 意義不同
    • 以使用者來說,建題系統的 “使用者” 是老師、測驗系統的 “使用者” 是學生
  • 同領域詞彙、意義 -> 不同使用案例
    • 一號餐是大麥克還是炸雞餐、票根回收是電影票根還是餐影兌換票根
  • 外部系統

參考資料: Bounded Contexts 和其應用

通用語言 (Ubiquitous Language)

當團隊有通用語言時,就不會產生溝通上的誤解,但需要注意的是要搭配 Bounded Context 才不會誤會一號餐是大麥克還是炸雞餐、票根回收是電影票根還是餐影兌換票根。


圖片來源: https://ithelp.ithome.com.tw/articles/10218943

Designing with types

F# 這個語言提供了大量定義 type,透過宣告 Type 的寫法去描述複雜的領域知識也建立了通用語言。

1
2
3
4
5
type HotelRoom = RoomNumber of int | RoomName of string

// 一號房跟商務房都是 HotelRoom
let room1 = RoomNumber 1
let royalSuite = RoomName "商務房"

參考資料

  1. https://fsharpforfunandprofit.com/posts/designing-with-types-intro/
  2. https://fsharpforfunandprofit.com/ddd/
  3. https://blog.scottlogic.com/2018/06/01/magical-domain-modelling-with-fsharp.html

Wrapping primitive types

1
2
3
type EmailAddress = EmailAddress of string
type EmailAddress = { EmailAddress: string }

實際的範例

  1. 將所有想到的類別都列出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Contact =
{
FirstName: string;
MiddleInitial: string;
LastName: string;

EmailAddress: string;
//true if ownership of email address is confirmed
IsEmailVerified: bool;

Address1: string;
Address2: string;
City: string;
State: string;
Zip: string;
//true if validated against address service
IsAddressValid: bool;

Start : DateTime;
}
  1. 重構並依照相關類型進行分類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
type PersonalName =
{
FirstName: string;
MiddleInitial: string option;
LastName: string;
}

type EmailAddress = EmailAddress of string

type EmailContactInfo =
{
EmailAddress: EmailAddress;
IsEmailVerified: bool;
}

type ZipCode = ZipCode of string
type StateCode = StateCode of string

type PostalAddress =
{
Address1: string;
Address2: string;
City: string;
State: StateCode;
Zip: ZipCode;
}

type PostalContactInfo =
{
Address: PostalAddress;
IsAddressValid: bool;
}

type Contact =
{
Name: PersonalName;
EmailContactInfo: EmailContactInfo;
PostalContactInfo: PostalContactInfo;
}
  1. Contact 中的聯絡方式改為選填
1
2
3
4
5
6
type Contact =
{
Name: PersonalName;
EmailContactInfo: EmailContactInfo option;
PostalContactInfo: PostalContactInfo option;
}
  1. 定義複合類型,簡化 Contact 中的聯絡方式寫法
1
2
3
4
5
6
7
8
9
10
11
12

type ContactInfo =
| EmailOnly of EmailContactInfo
| PostOnly of PostalContactInfo
| EmailAndPost of EmailContactInfo * PostalContactInfo

type Contact =
{
Name: Name;
ContactInfo: ContactInfo;
}

  1. List 的類型
1
2
3
4
5
6
7
8
9
10
type ContactMethod =
| Email of EmailContactInfo
| PostalAddress of PostalContactInfo
| HomePhone of PhoneContactInfo
| WorkPhone of PhoneContactInfo

type ContactInformation =
{
ContactMethods : ContactMethod list;
}

參考資料

  1. Making illegal states unrepresentable
  2. Discovering new concepts

投影片可以跳到 45 頁

1
2
3
4
5
6
7
8
9
// Bounded Context 用 module 定義
module CardGame =
// 開始定義通用語言 (Ubiquitous Language)
type Suit = Club | Diamond | Spade | Heart
type Rank = Two | Three | Four | Five | SIx | Seven | Eight
| Nine | Ten | Jack | Queen | King |Ace
// 在定義花色和數字後定義排組
type Card = Suit * Rank

Domain Driven Design with the F# type System -- F#unctional Londoners 2014 from Scott Wlaschin

喜歡這篇文章,請幫忙拍拍手喔 🤣

share