【真的假的】2017/4 Mapping refactor
Current structure
為求專注在元件邏輯本身,author 相關 mappings(users、apps)與相關欄位外鍵(userId、from)之間的線省略。
graph mappings{
rankdir=LR;
node[shape=record];
users [label="
𝐮𝐬𝐞𝐫𝐬
|
email\l
name\l
avatarUrl\l
facebookId\l
githubId\l
twitterId\l
createdAt\l
updatedAt\l
"]
apps [label="
𝐚𝐩𝐩𝐬
|
adminUserId\l
secret\l
redirectUrl\l
createdAt\l
updatedAt\l
"]
replyrequests [fixedsize="true" width="2" height="1.5" label="
<id>𝐫𝐞𝐩𝐥𝐲𝐫𝐞𝐪𝐮𝐞𝐬𝐭𝐬
|
<user>
userId\l
from\l
|
createdAt\l
updatedAt\l
"]
replyconnections [fixedsize="true" width="2" height="2" label="
<id>𝐫𝐞𝐩𝐥𝐲𝐜𝐨𝐧𝐧𝐞𝐜𝐭𝐢𝐨𝐧𝐬
|
<user>
userId\l
from\l
|
<reply>
replyId\l
|
<feedbacks>
feedbackIds\l
|
createdAt\l
updatedAt\l
"]
replyconnectionfeedbacks [fixedsize="true" width="3" height="2" label="
<id>𝐫𝐞𝐩𝐥𝐲𝐜𝐨𝐧𝐧𝐞𝐜𝐭𝐢𝐨𝐧𝐟𝐞𝐞𝐝𝐛𝐚𝐜𝐤𝐬
|
userId\l
from\l
|
score\l
comment\l
createdAt\l
updatedAt\l
"]
replies [label="
<id>𝐫𝐞𝐩𝐥𝐢𝐞𝐬
|
createdAt\l
|
versions[].userId\l
versions[].from\l
|
versions[].type\l
versions[].text\l
versions[].reference\l
versions[].createdAt\l
"]
articles [label="
𝐚𝐫𝐭𝐢𝐜𝐥𝐞𝐬
|
<replyconnections>replyConnectionIds\l
|
<deletedreplyconnections>deletedReplyConnectionIds\l
|
<replyrequests>replyRequestIds\l
|
text\l
createdAt\l
updatedAt\l
|
userId\l
from\l
|
references[].type\l
references[].permalink\l
references[].createdAt\l
"]
articles:replyrequests -- replyrequests:id [headlabel="n",taillabel="1"];
articles:replyconnections -- replyconnections:id [headlabel="n",taillabel="1"];
articles:deletedreplyconnections -- replyconnections:id [headlabel="n",taillabel="1"];
replyconnections:feedbacks -- replyconnectionfeedbacks:id [headlabel="n",taillabel="1"];
replyconnections:reply -- replies:id [headlabel="1",taillabel="n"];
}
Authentication mappings & fields
當使用者在使用 LINE bot 的時候,使用者並沒有來到 cofacts 網站進行登入。然而,LINE bot 卻能夠送出 article 以及 replyrequest。此時,articles 與 replyrequests 的作者相關欄位,應該要怎麼填寫呢?
LINE bot client 本身拿得到 LINE user ID,因此可以拿 LINE 本身的 user ID 來作為判斷使用者是否為一則 article 或 replyrequest 的 identifier。但是,各個 client (LINE、web、甚至是未來會支援的第三方 client)的 user ID 都不同,因此需要 from 欄位。
from 欄位是由 rumors-api 填寫的。Client app 會送 secret 與想要存入的 userId 到 rumors-api,rumors-api 比對 secret 正確之後,就會把 userId 以及相對應的 from 欄位值填入。未來開放第三方 client 之後,我們可以發放 app ID & secrets(也就是目前沒有作用的 apps index),而 from 欄位可以用拿來填寫 app ID。
當 client app 存取 rumors-api 的 users 相關欄位的時候,rumors-api 會比對 app ID 以及 userId,如果是現在的使用者,才會回傳 users 相關欄位,避免個資外洩。
Design choice
articles 對 replyconnections 的 1:n 關係中,replyConnectionIds 的外鍵之所以會存放在 articles 而非 replyConnections,主要是為了讓「列出文章時可以列出 reply 數」這個 query 可以只要查 articles 即可。
replyconnections - replyconnectionfeedbacks、articles - replyrequests 這兩個 1:n 關係會這樣設計也是同樣的理由,即使實際上採用了這種設計,其實無法保證 1:n 關係不會變成 m:n 關係(因為一個 replyrequests 的 ID 可以出現在複數個 articles 裡頭,所以其實不是完美的 1:n)。
問題:新 filter / sorting 需求
原本用來應付 cofacts 網頁文章列表 的 index mapping 機制,無法應付下面的這些花俏的 requests——但偏偏這些 requests 非常重要。
(From: https://github.com/cofacts/rumors-db/issues/7#issuecomment-293507662 )
- 我標記成「等等回應」的文章( #34
articles需新增 fieldpendingRepliers - 沒人標記成「等等回應」的所有文章( #34 )
- 文章 tag ( #32 )
articles需新增 fieldtags - 我回應過的 article
列出
replies中userId相符、或replyconections中userId相符的articles。或許需要按照回應時間排序? - 回應中有「含有真實資訊」or「含有不實資訊」or「非文章」
> 要查找
replies中,最新 version 的 type,以此來往回找 article - 回應中不含有「含有真實資訊」or「含有不實資訊」or「非文章」
- 我送出過 replyRequest 的 article (我想知道)( Related: cofacts/rumors-site#13
>列出
replyrequest之後再找articles - 所有人都認為現有 reply 沒用的 article / 照無用度 sort (「正向」+「負向」遞增排序)
> 按照
replyconnectionfeedbacks的 score、group byreplyconnection之後抓articles - 使用「各文章最近一次被回報的時間」排序
> 按照
replyrequest的createdAt欄位排序
上述 filter / sort 希望可以 aggregate 在一起,例如:找出「沒人標記為『等等回應』」的文章中,回應同時有「含有真實資訊」又有「含有不實資訊」的醫療相關文章。
最後,我們也希望能在新的 schema 中放入 segment 的設計,用處是在顯示文章列表時,可以給小編更細緻的、針對個別段落的 reply 連結建議。
Proposed structure
graph mappings{
rankdir=LR;
node[shape=record];
replyrequests [fixedsize="true" width="2" height="1.5" label="
<id>𝐫𝐞𝐩𝐥𝐲𝐫𝐞𝐪𝐮𝐞𝐬𝐭𝐬
|
<user>
userId\l
appId\l
|
<article>
_parent\l
|
createdAt\l
updatedAt\l
"]
replyconnections [fixedsize="true" width="2.5" height="5" label="
<id>𝐚𝐫𝐭𝐢𝐜𝐥𝐞𝐫𝐞𝐩𝐥𝐢𝐞𝐬
|
<user>
userId\l
appId\l
|
<article>
_parent\l
|
currentReply.userId\l
currentReply.appId\l
currentReply.type\l
currentReply.text\l
currentReply.reference\l
currentReply.createdAt\l
|
<reply>
replyId\l
|
segment\l
segmentRangeStart\l
segmentRangeEnd\l
|
status\l
createdAt\l
updatedAt\l
"]
replyconnectionfeedbacks [fixedsize="true" width="3" height="2" label="
<id>𝐚𝐫𝐭𝐢𝐜𝐥𝐞𝐫𝐞𝐩𝐥𝐲𝐟𝐞𝐞𝐝𝐛𝐚𝐜𝐤𝐬
|
<replyconnection>
_parent\l
|
userId\l
appId\l
|
score\l
comment\l
createdAt\l
updatedAt\l
"]
replies [label="
<id>𝐫𝐞𝐩𝐥𝐢𝐞𝐬
|
createdAt\l
|
{
versions\n(nested) |
{
userId\l
appId\l
|
type\l
text\l
reference\l
createdAt\l
}
}
"]
articles [label="
<id>
𝐚𝐫𝐭𝐢𝐜𝐥𝐞𝐬
|
text\l
createdAt\l
updatedAt\l
|
userId\l
appId\l
|
{
references\n
(nested)
|
{
type\l
permalink\l
createdAt\l
|
userId\l
appId\l
}
}
|
{
pendingRepliers
\n(nested)
|
{
userId\l
appid\l
|
createdAt\l
}
}
|
<tags>tags\l
"]
tags[label="
<id>𝐭𝐚𝐠𝐬
|
<title>title\l
|
description\l
|
userId\l
appId\l
"]
articles:id -- replyrequests:article [headlabel="n",taillabel="1"];
articles:id -- replyconnections:article [headlabel="n",taillabel="1"];
articles:tags -- tags:title [headlabel="m",taillabel="n"];
replyconnections:id -- replyconnectionfeedbacks:replyconnection [headlabel="n",taillabel="1"];
replyconnections:reply -- replies:id [headlabel="1",taillabel="n"];
}
Design choice
- 針對 children 多、或很需要
has_childquery 的replyrequest、replyconnectionfeedbacks使用 parent/child 儲存;其餘盡量使用 nested object 以求效率。 from欄位正名為appId。author 相關 mappings(users、apps)省略,僅列出相關欄位外鍵(userId、appId)。replies會把 cached field(currentReply) 塞進其所有的replyConnection中,以利 aggregation 查詢。- segments 併入
replyconnecitons:replyconnecitons是articles—repliesn:m 關係的 join table,也就是articles—replies的「邊」。”segments” 是使用者圈選的字串或位置,理論上每當reply與article建立新關係時,segment 就會不同(至少字串在article上的位置會不一樣),feedbacks 也應該要重算。因此,我們選擇將segments直接實做在replyconnection上。 - 增加
pendingReplers(「等等回應」 #34 ) - 增加
tagsindex 擺放 tag 的 metadata(例如說解釋某 tag 之類的,或是之後做 alias / redirection 消歧義之類的功能)。ID 為sha1(title)以對 tag title 做 unique constraint。 (#32),而artices增加tags欄位擺放 tags 本體。
需求確認
- 沒人回應過的文章、有人回應過的文章
計算 child 數量,可以
針對用has_childquery 的 constant score 做 aggregation sumhas_childquery +min_children參數;使用數量做 filter 可以參考這篇。 - 我標記成「等等回應」的文章( #34 )
articles.pendingRepliersnestedquery - 沒人標記成「等等回應」的所有文章( #34 )
articles.pendingReplies數量為 0 - 文章 tag ( #32 )
直接從 article.tags 找
- 我回應過的 article
articlesindexhas_child特定currentReply.userId - 回應中有「含有真實資訊」or「含有不實資訊」or「非文章」
articlesindexhas_child特定currentReply.type - 回應中不含有「含有真實資訊」or「含有不實資訊」or「非文章」
not (
articlesindexhas_child特定currentReply.type) - 我送出過 replyRequest 的 article (我想知道)( Related: cofacts/rumors-site#13
articlesindexhas_child特定replyRequest.userId - 照 reply 的無用度排序 (「正向」+「負向」遞增排序)
aggregate
articles/connections/replyconnectionfeedbacks的scoresum 作為排序基礎。 (https://www.elastic.co/guide/en/elasticsearch/reference/5.0/query-dsl-has-child-query.html#_scoring_capabilities) - 使用「各文章最近一次被回報的時間」排序
以
/articles/replyrequest的createdAtmax 作為排序基礎 (https://www.elastic.co/guide/en/elasticsearch/reference/5.0/query-dsl-has-child-query.html#_scoring_capabilities)