用戶注冊服務(wù)進(jìn)階(一)

Hello, Users

通過本系列文章,我試圖通過一個簡單的UC來構(gòu)建我的架構(gòu)世界觀和方法論,其中核心的線索是SOC,使用的語言是Scala,編程范式為FP

第一篇 編譯器的歸編譯器抒蚜,運行時的歸運行時

無論是2C 還是 2B,用戶都是公司的命根子柜裸,當(dāng)用戶被千方百計吸引過來時炼吴,我們一定不希望用戶被一個不穩(wěn)定的用戶注冊服務(wù)拒之門外。

那如何構(gòu)造一個穩(wěn)定的用戶注冊服務(wù)呢?

這還不簡單么

從業(yè)務(wù)角度扁眯,這確實沒什么難的壮莹,我們可以簡單的提煉出一個Case:

UC 1. 用戶注冊/Register
流程:
1. 校驗用戶提交的注冊要素(手機號碼,驗證碼姻檀,登陸名稱命满,密碼)
2. 保存用戶注冊信息,并返回成功注冊的用戶信息
  2.1 如果用戶注冊要素校驗不通過绣版,則返回注冊要素不合規(guī)范的錯誤信息
  2.2 如果保存失敗胶台,則返回服務(wù)端暫時不可用的錯誤信息

前置條件:
1. 通知服務(wù)已經(jīng)發(fā)送該手機號碼的驗證碼,并提供驗證碼驗證服務(wù)杂抽。

后置條件:
1. 注冊成功后诈唬,用戶可通過登錄名和密碼登錄登錄以獲取服務(wù)。

基于業(yè)務(wù)缩麸,我們很容易建模:

/** 用戶信息
  * @param mobile: 手機號碼
  * @param otp: 驗證碼
  * @param loginName: 登錄名
  * @param password: 密碼
  */
case class User(mobile: String, otp: String, loginName: String, password: Vector[Char])

/** 用戶服務(wù)
  *
  */
trait UserService {
  /** 注冊用戶铸磅,完成 UC1 的業(yè)務(wù)規(guī)則
    */
  def register(user: User): Unit
}

我們可以把這些交給Coding小伙伴來實現(xiàn)了吧:

class UserServiceImpl extends UserService {
  /** 注冊用戶,完成 UC1 的業(yè)務(wù)規(guī)則
    */
  def register(user: User): Unit = {
    // 校驗手機號碼格式
    if (! validateMobile(user.mobile)) throw new Exception("手機號碼格式錯誤")
    
    // 調(diào)用驗證碼驗證Restful服務(wù)
    if (! validateOtp(user.otp)) throw new Exception("驗證碼錯誤")
    
    // 校驗用戶登錄名格式
    if (! validateLoginName(user.loginName)) throw new Exception("用戶名格式錯誤")

    // 校驗用戶登錄名是否沖突
    if (! hasExisted(user.loginName)) throw new Exception("用戶名沖突")

    // 校驗密碼強度
    if (! validatePassword(user.password)) throw new Exception("密碼強度不符合要求")

    // 持久化用戶信息
    persistUser(user)
  }

  private def validateMobile(mobile: String): Boolean = ???
  private def validateOtp(otp: String): Boolean = ???
  private def validateLoginName(loginName: String): Boolean = ???
  private def hasExisted(loginName: String): Boolean = ???
  private def validatePassword(password: Vector[Char]): Boolean = ???
  private def persistUser(user: User): Unit = ???
}

So far, so good!

減少編碼錯誤

上線后杭朱,這段代碼正常運行了一段時間阅仔,沒有出現(xiàn)啥問題,Good弧械!
But八酒,But,在一次上線后梦谜,突然發(fā)現(xiàn)丘跌,所有的用戶無法注冊了袭景,What !1帐鳌耸棒!
在比較代碼變更時發(fā)現(xiàn),構(gòu)建User對象時报辱,一個小伙伴無意中將otp參數(shù)賦給了loginName与殃!編譯、發(fā)布碍现,一切正常幅疼,但在運行時,校驗不通過昼接,所以捅了大簍子爽篷!

當(dāng)然,我們可以通過代碼之外的手段來減少這種錯誤慢睡,比如測試逐工。但一些常規(guī)更新中,很可能漏掉無關(guān)的一些功能的測試漂辐,從“反求諸己”的原則自我要求的話泪喊,我們必須檢視,有沒有針對這種錯誤的改進(jìn)的空間髓涯?我們的代碼是否有足夠的自我防御能力袒啼,盡早發(fā)現(xiàn)這種錯誤呢?

一種可選的答案是Typeful 纬纪。也就是強類型且類型完全的蚓再,讓編譯器對類型進(jìn)行檢查,在編譯期間就為我們排出這種低級錯誤育八。

讓我們再次審視我們的代碼对途,它是否真的反應(yīng)了業(yè)務(wù)?業(yè)務(wù)用例說髓棋,用戶注冊要素包含手機號碼实檀、驗證碼 等,再看看我們的User類是怎樣對這兩個要素建模的按声。發(fā)現(xiàn)差異了嗎膳犹?loginNameotp 兩個參數(shù)都是String類型, 我們用屬性名稱來建模签则,而沒有使用類型來建模须床!然而編譯器不會檢查變量名,但會檢查變量的類型(Scala是強類型語言)渐裂。

讓我們充分利用強類型豺旬,讓編譯器替我們干最臟最累的活吧钠惩!Let's Do it!

/** 手機號碼
  */
case class Mobile(value: String) extends AnyVal

/** 手機驗證碼
  */
case class OTP(value: String) extends AnyVal

/** 登錄名
  */
case class LoginName(value: String) extends AnyVal

/** 密碼
  */
case class Password(value: Vector[Char]) extends AnyVal

/** 用戶
  */
case class User(mobile: Mobile, otp: OTP, loginName: LoginName, password: Password)

嗯族阅,這樣一來篓跛,我們的代碼更加類型安全了!不會再出現(xiàn)錯傳參數(shù)的低級錯誤了坦刀。因此愧沟,我們要盡可能的Typeful,讓編譯器檢查低級錯誤鲤遥。

更深一層考慮沐寺,我們在做一件事情:SOCSeparation Of Concerns), 分離的是什么呢?我們分離的是編譯和運行盖奈,充分利用編譯的類型檢查職責(zé)混坞,避免將類型的檢查延遲到運行時!之前的代碼很明顯沒有意識到這種分離钢坦。(除了使用變量名稱來指稱業(yè)務(wù)含義拔第,我們經(jīng)常犯的錯還包括在運行時對對象進(jìn)行類型檢查,根據(jù)對象的類型決定業(yè)務(wù)的走向)

這種重構(gòu)场钉,在實際的開發(fā)過程中出現(xiàn)過,比如在開發(fā)加解密工具包時懈涛,一開始對所有的參數(shù)都是用Array[Byte]類型逛万,導(dǎo)致外部調(diào)用方經(jīng)常講公鑰與私鑰參數(shù)順序搞反了,自己的單元測試完全通過批钠,但集成到其他模塊時就出錯宇植,查這種錯花了不少時間,可謂是教訓(xùn)深刻埋心!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末指郁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拷呆,更是在濱河造成了極大的恐慌闲坎,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茬斧,死亡現(xiàn)場離奇詭異腰懂,居然都是意外死亡,警方通過查閱死者的電腦和手機项秉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門绣溜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人娄蔼,你說我怎么就攤上這事怖喻〉谆” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵锚沸,是天一觀的道長跋选。 經(jīng)常有香客問我,道長咒吐,這世上最難降的妖魔是什么野建? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮恬叹,結(jié)果婚禮上候生,老公的妹妹穿的比我還像新娘。我一直安慰自己绽昼,他們只是感情好唯鸭,可當(dāng)我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著硅确,像睡著了一般目溉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上菱农,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天缭付,我揣著相機與錄音,去河邊找鬼循未。 笑死陷猫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的的妖。 我是一名探鬼主播绣檬,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嫂粟!你這毒婦竟也來了娇未?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤星虹,失蹤者是張志新(化名)和其女友劉穎零抬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宽涌,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡媚值,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了护糖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褥芒。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锰扶,到底是詐尸還是另有隱情献酗,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布坷牛,位于F島的核電站罕偎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏京闰。R本人自食惡果不足惜颜及,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹂楣。 院中可真熱鬧俏站,春花似錦、人聲如沸痊土。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赁酝。三九已至犯祠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酌呆,已是汗流浹背衡载。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留隙袁,地道東北人月劈。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像藤乙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子惭墓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容