Netflix API 實(shí)踐(二)使用FieldMask 進(jìn)行數(shù)據(jù)變更

背景

在上一篇文章中窟她,我們討論了如何使用FieldMask 作為設(shè)計(jì) API 時(shí)的解決方案纳账,以便消費(fèi)者可以通過 gRPC 只獲取返回他們需要的數(shù)據(jù)兄旬。在這篇博文中,我們將繼續(xù)介紹 Netflix Studio Engineering 如何使用 FieldMask 進(jìn)行更新和刪除等變更操作凡纳。

Example: Netflix Studio Production

之前我們概述了產(chǎn)品是什么窃植,以及產(chǎn)品服務(wù)如何對其他微服務(wù)(例如計(jì)劃服務(wù)和腳本服務(wù))進(jìn)行 gRPC 調(diào)用,以檢索特定產(chǎn)品(例如 La Casa De Papel)的排期和腳本(又名劇本)荐糜。我們可以采用該模型進(jìn)一步展示我們?nèi)绾卧诋a(chǎn)品中改變特定字段巷怜。

修改產(chǎn)品細(xì)節(jié)

假設(shè)由于我們的制作添加了一些動畫元素因此我們想要將格式字段從 LIVE_ACTION 更新為 HYBRID。我們解決這個(gè)問題的一個(gè)簡單方法是添加一個(gè) updateProductionFormatRequest 方法和 gRPC 端點(diǎn)來更新 productionFormat:

message UpdateProductionFormatRequest {
  string id = 1;
  ProductionFormat format = 2;
}

service ProductionService {
  rpc UpdateProductionFormat (UpdateProductionFormatRequest) 
      returns (UpdateProductionFormatResponse);
}

這允許我們更新特定產(chǎn)品的制作格式暴氏,但是如果我們想要更新其他字段(例如標(biāo)題)甚至多個(gè)字段(例如 productionFormat延塑、schedule 等)怎么辦?在此基礎(chǔ)上答渔,我們可以為每個(gè)字段實(shí)現(xiàn)一個(gè)更新方法:一個(gè)用于生產(chǎn)格式关带,另一個(gè)用于標(biāo)題,依此類推:`

// separate RPC for every field, not recommended
service ProductionService {
  rpc UpdateProductionFormat (UpdateProductionFormatRequest) {...}

  rpc UpdateProductionTitle (UpdateProductionTitleRequest) {...}

  rpc UpdateProductionSchedule (UpdateProductionScheduleRequest) {...}

  rpc UpdateProductionScripts (UpdateProductionScriptsRequest) {...}
}

message UpdateProductionFormatRequest {...}

message UpdateProductionTitleRequest {...}

message UpdateProductionScheduleRequest {...}

message UpdateProductionScriptsRequest {...}

由于產(chǎn)品中包含大量的字段沼撕,在維護(hù)我們的 API 時(shí)宋雏,這可能會變得難以管理。如果我們想更新多個(gè)字段并在單個(gè) RPC 中以原子方式進(jìn)行务豺,該怎么辦磨总?為各種字段組合創(chuàng)建額外的方法將導(dǎo)致變更操作 API 的爆炸式增長。此解決方案不可擴(kuò)展笼沥。
與其嘗試創(chuàng)建每個(gè)可能的組合舍败,另一種解決方案提供一個(gè) UpdateProduction 方法,該方法需要包含消費(fèi)者的所有字段:

message Production {
  string id = 1;
  string title = 2;
  ProductionFormat format = 3;
  repeated ProductionScript scripts = 4;
  ProductionSchedule schedule = 5;
  // ... more fields
}
service ProductionService {
  rpc UpdateProduction (UpdateProductionRequest) returns (UpdateProductionResponse);
}

message UpdateProductionRequest {
  Production production = 1;
}

這個(gè)解決方案帶來兩個(gè)問題敬拓,因?yàn)橄M(fèi)者必須知道并提供生產(chǎn)中的每一個(gè)必填字段,即使他們只想更新一個(gè)字段裙戏,例如格式乘凸。另一個(gè)問題是,由于 Production 具有許多字段累榜,因此請求有效負(fù)載可能會變得非常大营勤,特別是如果 Production 具有排期或腳本信息
如果我們只發(fā)送我們真正想要更新的字段而不是所有字段灵嫌,不設(shè)置其他字段怎么辦?在我們的示例中葛作,我們將只設(shè)置生產(chǎn)格式字段(以及引用生產(chǎn)的 ID):

UpdateProduction updateProduction = UpdateProduction.newBuilder()
    .setProductionFormat(PRODUCTION_FORMAT_HYBRID)
    .build();

// Send the update request
UpdateProductionResponse response = client.updateProduction(LA_CASA_DE_PAPEL_PRODUCTION_ID, 
    updateProductionRequest);

如果我們永遠(yuǎn)不需要刪除或清空任何字段寿羞,這可能會起作用。但是如果我們想去掉title字段的值呢赂蠢?同樣绪穆,我們可以引入像 RemoveProductionTitle 這樣的一次性方法,但如上所述虱岂,該解決方案不能很好地?cái)U(kuò)展玖院。如果我們想從排期中刪除嵌套字段的值,例如計(jì)劃的啟動日期字段第岖,該怎么辦难菌?我們最終會為每個(gè)可以為空的子字段添加刪除 RPC。

使用FieldMask進(jìn)行數(shù)據(jù)變更

我們可以使用 FieldMask 來處理我們所有的變更蔑滓,而不是大量的 RPC 或需要大的有效負(fù)載郊酒。 FieldMask 將列出我們想要顯式更新的所有字段。首先键袱,讓我們更新我們的 proto 文件以添加到 UpdateProductionRequest 中燎窘,該文件將包含我們想要從生產(chǎn)中更新的數(shù)據(jù),以及應(yīng)該更新的 FieldMask:

message ProductionUpdateOperation {
  string production_id = 1;
  string title = 2;
  ProductionFormat format = 3;
  ProductionSchedule schedule = 4;
  repeated ProductionScript scripts = 5;
  ... // more fields
}

message UpdateProductionRequest {
  // contains production ID and fields to be updated
  ProductionUpdateOperation update = 1;
  google.protobuf.FieldMask update_mask = 2;
}

現(xiàn)在杠纵,我們可以使用 FieldMask 進(jìn)行數(shù)據(jù)變更荠耽。我們可以通過使用 FieldMaskUtil.fromStringList() 方法為格式字段創(chuàng)建 FieldMask 來更新格式,該方法為特定類型的字段路徑列表構(gòu)造 FieldMask比藻。在這種情況下铝量,我們將有一種類型,并在后續(xù)的例子中進(jìn)行演示:

FieldMask updateFieldMask = FieldMaskUtil.fromStringList(Production.class, 
    Collections.singletonList(“format”);

// Update the production format type
ProductionUpdateOperation productionUpdateOperation = ProductionUpdateOperation
    .newBuilder()
    .setProductionId(LA_CASA_DE_PAPEL_PRODUCTION_ID)
    .setProductionFormat(PRODUCTION_FORMAT_HYBRID)
    .build();

// Build the UpdateProductionRequest including the updatefieldmask
UpdateProductionRequest updateProductionRequest = UpdateProductionRequest
    .newBuilder()
    .setUpdate(productionUpdateOperation)
    .setUpdateMask(updateFieldMask)
    .build();

// Send the update request
UpdateProductionResponse response = 
    client.updateProduction(LA_CASA_DE_PAPEL_PRODUCTION_ID, updateProductionRequest);

由于我們的 FieldMask 僅指定格式字段银亲,即使我們在 ProductionUpdateOperation 中提供更多字段慢叨,該字段也將是唯一被更新的字段。通過修改路徑务蝠,向我們的 FieldMask 添加或刪除更多字段變得更加容易拍谐。在有效負(fù)載中提供但未添加到 FieldMask 路徑中的數(shù)據(jù)將不會被更新,并且在操作中也會被忽略馏段。但是轩拨,如果我們省略一個(gè)值,它將對該字段執(zhí)行刪除突變院喜。讓我們修改上面的示例來展示如何更新格式亡蓉,并刪除排期的發(fā)布日期,發(fā)布日期是 ProductionSchedule 上的一個(gè)嵌套字段喷舀,為“schedule.planned_launch_date”:

FieldMask updateFieldMask = FieldMaskUtil.fromStringList(Production.class,
    Arrays.asList("format", "schedule.planned_launch_date"));

// Update the format, in addition remove schedule.planned_launch_date by not including it in our request
ProductionUpdateOperation productionUpdateOperation = ProductionUpdateOperation
    .newBuilder()
    .setProductionId(LA_CASA_DE_PAPEL_PRODUCTION_ID)
    .setProductionFormat(PRODUCTION_FORMAT_HYBRID)   
    .build();

UpdateProductionRequest updateProductionRequest = UpdateProductionRequest
    .newBuilder()
    .setUpdate(productionUpdateOperation)
    .setUpdateMask(updateFieldMask)
    .build();

// Send the update request
UpdateProductionResponse response = 
    client.updateProduction(LA_CASA_DE_PAPEL_PRODUCTION_ID, updateProductionRequest);

在此示例中砍濒,我們正在執(zhí)行更新和刪除突操作淋肾,因?yàn)槲覀円褜ⅰ癴ormat”和“schedule.planned_launch_date”路徑添加到我們的 FieldMask。當(dāng)我們在有效負(fù)載中提供此信息時(shí)爸邢,這些字段將更新為新值樊卓,但在構(gòu)建有效負(fù)載時(shí),我們僅提供格式并省略 schedule.planned_launch_date杠河。從有效負(fù)載中省略它但在我們的 FieldMask 中定義它將起到刪除的效果:


空FiledMask處理

當(dāng)FieldMask未設(shè)置或沒有路徑時(shí)碌尔,更新操作適用于所有有效負(fù)載字段。這意味著調(diào)用者必須發(fā)送整個(gè)有效負(fù)載感猛,或者如上所述七扰,任何未設(shè)置的字段都將被刪除。
這個(gè)約定對模式演變有影響:當(dāng)一個(gè)新字段被添加到消息中時(shí)陪白,所有消費(fèi)者必須開始在更新操作中發(fā)送它的值颈走,否則它將被刪除。
假設(shè)我們要添加一個(gè)新字段:生產(chǎn)預(yù)算咱士。我們將擴(kuò)展 Production 消息和 ProductionUpdateOperation:

// update operation with new ‘budget’ field
message ProductionUpdateOperation {
  string production_id = 1;
  string title = 2;
  ProductionFormat format = 3;
  ProductionSchedule schedule = 4;
  repeated ProductionScript scripts = 5;
  ProductionBudget budget = 6;            // new field
}

如果有消費(fèi)者不知道這個(gè)新字段或尚未更新客戶端方法立由,它可能會因未在更新請求中發(fā)送 FieldMask 而意外地將預(yù)算字段清空。
為避免此問題序厉,生產(chǎn)者應(yīng)考慮要求所有更新操作的字段掩碼锐膜。另一種選擇是實(shí)現(xiàn)版本控制協(xié)議:強(qiáng)制所有調(diào)用者發(fā)送他們的版本號并實(shí)現(xiàn)自定義邏輯以跳過舊版本中不存在的字段。

總結(jié)

API 設(shè)計(jì)者應(yīng)該遵循簡單 開放 可擴(kuò)展和發(fā)展的設(shè)計(jì)原則弛房。保持 API 簡單且面向未來通常并不容易道盏。在 API 中使用 FieldMask 有助于我們實(shí)現(xiàn)簡單性和靈活性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末文捶,一起剝皮案震驚了整個(gè)濱河市荷逞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌粹排,老刑警劉巖种远,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異顽耳,居然都是意外死亡坠敷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事【榉撸” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我掂恕,道長,這世上最難降的妖魔是什么弛槐? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任懊亡,我火速辦了婚禮,結(jié)果婚禮上乎串,老公的妹妹穿的比我還像新娘店枣。我一直安慰自己,他們只是感情好叹誉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布鸯两。 她就那樣靜靜地躺著,像睡著了一般长豁。 火紅的嫁衣襯著肌膚如雪钧唐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天匠襟,我揣著相機(jī)與錄音钝侠,去河邊找鬼。 笑死酸舍,一個(gè)胖子當(dāng)著我的面吹牛帅韧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播啃勉,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼忽舟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了淮阐?” 一聲冷哼從身側(cè)響起叮阅,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枝嘶,沒想到半個(gè)月后帘饶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡群扶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年及刻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竞阐。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缴饭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出骆莹,到底是詐尸還是另有隱情颗搂,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布幕垦,位于F島的核電站丢氢,受9級特大地震影響傅联,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疚察,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一蒸走、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧貌嫡,春花似錦比驻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至夫椭,卻和暖如春掸掸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背益楼。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工猾漫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人感凤。 一個(gè)月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓悯周,卻偏偏與公主長得像,于是被迫代替她去往敵國和親陪竿。 傳聞我的和親對象是個(gè)殘疾皇子禽翼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

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