# 今週何知った? week:16 ## 各自発表 > [name=ken3ypa] # しっかり学ぶmermaid記法 〜フローチャート編〜 ## きっかけ - [GitHub がMermaid記法をサポート](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) - 決済まわりを触っていて、シーケンス図が頻出 => これはすらすらかけるようになりたい ## mermaidとは - markdown記法にインスパイアされた、JavaScriptベースの図表作成ライブラリ。mermaid記法を用いて、開発にまつわる諸々の図作成できる - 公式:http://mermaid-js.github.io/mermaid/#/ - ライブエディタ: https://mermaid-js.github.io/mermaid-live-editor/edit ### 作れる図 - フローチャート / シーケンス図 / クラス図 / ステート図 / ER図 / ユーザージャーニー / ガントチャート / 円グラフ…etc ### 他のライブラリとの比較 - 似たようなライブラリとしてはPlantUMLがある。記法については差異はあるものの、出来ることはほぼ同じ(のように見える。深掘りはしていない) - https://qiita.com/Tachy_Pochy/items/ee79fc5c572fa5661989 ### おすすめの学び方 - 記法の種類が多いので全部を学ぼうとすると途中で力尽きがち。作りたい図に絞って学ぶとよい - 一つの図に絞れば記法はそう多くないので、2時間ぐらいやればガッツリ書けるようになる ## シーケンス図の構文について ここではシーケンス図作成のために用意されている14の記法について見ていく ### 1 Participants - シーケンス図における主体を記載できる。 - 省略も可能だが、participantを明示することで並び順を指定することが可能 #### 省略した例 ```mermaid sequenceDiagram ken->>maki: 巻いてますか? maki-->>ken: 巻いてます ken-->>maki: ですよね… ``` #### 明記した例 ```mermaid sequenceDiagram participant maki participant ken ken->>maki: 巻いてますか? maki-->>ken: 巻いてます ken-->>maki: ですよね… ``` ### 2 Actors - Participantsの亜種。図中で表示されるアイコンが棒人間になる ```mermaid sequenceDiagram actor ken actor maki   ken->>maki: 今日も巻きますか? maki->>ken: 今日は巻きませんね… ken->>maki: えっ ``` ### 3 Aliases participant, actor にAliasを指定できる。SQLっぽい ```md participant <alias_name> as <name> actor <alias_name> as <name> ``` ```mermaid sequenceDiagram participant k as ken participant m as maki k->>m: mってなんですか? m->>k: 私の別名です k->>m: そうですか… ``` ```mermaid sequenceDiagram actor k as ken actor m as maki k->>m: mってなんですか? m->>k: 私の別名です k->>m: そうですか… ``` ### 4 Messages 8種類の矢印と `: `以降に指定する文言を元に、A to B へのメッセージを記載できる。 ``` a->b: message ``` Type|説明| --- | --- | ->|矢印なしの直線| -->|ドット付き直線| ->>|矢印あり直線| -->>|ドット・矢印あり直線| -x| 終端×・矢印あり直線| --x|終端×・矢印・ドットあり直線| -)|かっこいい矢印| --)|ドット付きかっこいい矢印| ```mermaid sequenceDiagram actor k as ken actor m as maki k->m: 矢印なしの直線 k-->m: ドット付き直線 k->>m: 矢印あり直線 k-->>m: ドット・矢印あり直線 k-xm: 終端×・矢印あり直線 k--xm: 終端×・矢印・ドットあり直線 k-)m: かっこいい矢印 k--)m: ドット付きかっこいい矢印 ``` ### 5 Activations 有効化・無効化を設定できる ```mermaid sequenceDiagram ken->>+maki: Transaction ken->>+maki: ActivateB maki->>-ken: DeActivateB ken->>+maki: ActivateC maki->>-ken: DeActivateC maki->>-ken: Fin Transaction ``` ### 6 Notes Note <right of / left of / over> message でコメントを残せる ```mermaid sequenceDiagram ken->>maki: こんにちは Note left of ken: これは日本での一般的な挨拶 ``` ```mermaid sequenceDiagram ken->>maki: 巻いてますか? Note over ken, maki: ※ 初対面でこんな挨拶はリスクが伴う ``` ### 7 Loops ```mermaid sequenceDiagram ken->>maki: ここで装備していくかい? loop 「はい」って言うまで続く maki->>ken: いいえ end ``` ### 8 Alt ```mermaid sequenceDiagram ken->>maki: ここで装備していくかい? alt はい    maki->>ken: はい else いいえ   maki->>ken: いいえ end opt ほい    maki->ken: ほい! end ``` ### 9 Parallel 並行で起こっている事象について表すことができる ```md par [Action 1] ... statements ... and [Action 2] ... statements ... and [Action N] ... statements ... end ``` ```mermaid sequenceDiagram         actor User User->>Subject: submit par Subject to Observer Subject->>Observer1: call update and Subject to Observer2 Subject->>Observer2: call update end Observer1-->>User: Notify Observer2-->>User: Notify ``` ### 10 Background Highlighting `rect <rgb/rgba> ~ end`で囲うことで、スコープ内のフローへ背景色を追加できる ``` rect rgb(0, 255, 0) ... content ... end rect rgba(0, 0, 255, .1) ... content ... end ``` ```mermaid sequenceDiagram participant Alice participant John rect rgb(191, 223, 255) note right of Alice: Alice calls John. Alice->>+John: Hello John, how are you? rect rgb(200, 150, 255) Alice->>+John: John, can you hear me? John-->>-Alice: Hi Alice, I can hear you! end John-->>-Alice: I feel great! end Alice ->>+ John: Did you want to go to the game tonight? John -->>- Alice: Yeah! See you there. ``` ### 11 Comments mermaid中にコメントを残したい場合は %% で記載可能。 つけたコメントはパーサから無視されるため、表示には影響しない。 ```md sequenceDiagram Alice->>John: Hello John, how are you? %% this is a comment John-->>Alice: Great! ``` ### 12 Entity codes to escape characters エンティティコードを指定して文字や記号の実体参照ができる ```mermaid sequenceDiagram A->>B: #9731; B->>A: #127759; A->>B: #128511; ``` ### 13 sequenceNumbers `autonumber` を記載するか、optionを設定することでフローに番号を振ることができる ```mermaid sequenceDiagram autonumber ken->>maki: どもども ken->>maki: 元気ですか? ken->>maki: あれ? ken->>maki: おーい ken->>maki: 聞こえますか? ``` ### 14 Actor Menus `link [participant name] link_name @ URL` で、participant をマウスオーバーした際にリンクを表示させることができる(が、esaやHackMD、GitHubなどmermaid をサポートしている各種ツールでもこの機能は未対応) ```md sequenceDiagram participant Alice participant John link Alice: google @ https://dashboard.contoso.com/alice link Bob: google @ https://dashboard.contoso.com/bob Alice->>John: Hello John, how are you? ``` ### 練習 https://developer.amazon.com/ja/docs/amazon-pay-checkout/overview.html ```mermaid sequenceDiagram     autonumber %% 登場人物 actor b as #128102; 購入者 participant mf as #128722; EC運営者フロントエンド participant mb as #128331; EC運営者バックエンド(API) participant a as #128509; AmazonPayがホストしているページ participant ab as #128331; AmazonPayBackend(API) %% 決済処理の流れについて mb->>mb: 「AmazonPayボタン」のペイロードとシグニチャを生成 mb->>mf: 「AmazonPayボタン」をレンダリング mf->>b: ECサイトは「AmazonPayボタン」を含む<br> /product/cart/checkout ページをレンダリング b->>mf: 「AmazonPayボタン」をクリック mf->>a: 「CheckoutSessionConfig」を入力値として<br> AmazonPayプレオーダーページを起動 b->>a: Amazon Payでログイン b->>a: Amazonに登録している住所・支払方法を操作し<br> 「続ける」をクリック a->>mf: checkoutReviewReturnUrl(amazonCheckoutSessionIdを付与)にリダイレクト mf->>mb: getCheckoutSession()のエンドポイントを叩く mb->>ab: req: getCheckoutSession(amazonCheckoutSessionId) ab->>mb: res: ユーザー入力の住所・支払方法を更新したレスポンスを返す mb->>mf: 下記4つを含む注文確認画面を表示<br> 1: 選択した住所・決済方法と「変更」ボタン<br> 2: 合計金額と購入商品情報<br> 3: (もし指定が必要な場合)配送方法<br> 4: 「注文」ボタン rect rgba(10,10,100,0.2) alt 購入者が注文確認画面で更新する場合 b->>mf: 「住所 or 決済方法変更」ボタンをクリック mf->>a: amazonCheckoutSessionIdを保持して<br>AmazonPayプレオーダーページを起動 b->>a: Amazonに登録している住所・支払方法を操作し<br>「続ける」をクリック a->>mf: checkoutReviewReturnUrl(amazonCheckoutSessionIdを付与)にリダイレクト mf->>mb: getCheckoutSession()のエンドポイントを叩く mb->>ab: req: getCheckoutSession(amazonCheckoutSessionId) ab->>mb: res: ユーザー入力の住所・支払方法を更新したレスポンスを返す mb->>mf: 更新された情報をもとに注文確認画面を再度表示 end end b->>mf: 「注文」ボタンをクリック mf->>mb: バックエンドに情報を送る mb->>ab: req: updateCheckoutSession(amazonCheckoutSessionId, paymentIntent,<br>checkoutResultReturnUrl, amount, metadataなど) ab->>mb: res: amazonPayRedirectUrl を含んだレスポンスを返す mb->>a: amazonPayRedirectUrlへリダイレクトする b->>a: リダイレクト先で下記のいずれかが表示される<br>1: 多要素認証/認証失敗フロー<br>2: checkoutResultReturnUrlへのリダイレクト Note over b, a: ※ 多要素認証が必要か否かはAmazonPay側が判断 rect rgba(10,10,100,0.2) alt 多要素認証が必要な場合 b->>a: 多要素認証を完了させる end end rect rgba(100,10,10,0.2) alt 認証が失敗した場合 b->>a: 認証失敗フローに進む Note right of a: 多要素認証ページが再度表示されるケースもある end end a->>mf: checkoutResultReturnUrl(amazonCheckoutSessionIdを付与)にリダイレクト mb->>ab: req: completeCheckoutSession(amazonCheckoutSessionIdを付与) ab->>mb: completeCheckoutSessionのレスポンス alt CheckoutSessionStatusが「Completed」の場合 rect rgba(10,100,10,0.2) mb->>mb: CcompleteCheckoutSessionのレスポンスに含まれるChargeID・ChargePermissionId を保存 mb->>mf: 購入完了ページにリダイレクト mf->>b: 購入完了ページを表示する end else CheckoutSessionStatusが「Cancelled」の場合 rect rgba(100,10,10,0.2) mb->>mf: 決済失敗ページにリダイレクト mf->>b: 決済失敗ページを表示する end end ``` > [name=makicamel] ## ストリーム - ストリームとは > データを、比較的小さい単位が連続したものと捉え、上流から下流へ「流れるもの」とみなし、そのデータの入出力・送受信(途中段階を含む)を最小限の滞留とさせ低遅延処理となるように扱う形態を指す。またその操作のための抽象データ型を指す。 [ストリーム (プログラミング) - Wikipedia](https://ja.wikipedia.org/wiki/ストリーム_(プログラミング)) - ふつうにファイルを読んだ場合、ファイルをすべてメモリ上に展開する - ファイルが大きくなるとメモリの枯渇、パフォーマンスの悪化の可能性が出る - ストリームはファイルの一部を読み込み・書き込みする ![](https://i.imgur.com/PRBR6Sg.jpg) ー [ハンズオン Node.js](https://www.oreilly.co.jp/books/9784873119236/) p.122 ref [Net::HTTPResponse#read_body (Ruby 3.1 リファレンスマニュアル)](https://docs.ruby-lang.org/ja/latest/method/Net=3a=3aHTTPResponse/i/read_body.html) ## http モジュール 「TODO リストを返すサーバ」は以下のように実装できる ```javascript const todos = [ { id: 1, title: 'ネーム', completed: false }, { id: 2, title: '下書き', completed: true } ] // req は読み込みストリーム、res は書き込みストリーム const server = http.createServer((req, res) => { // リクエストの URL や HTTP メソッドに応じて適切なレスポンスを返す if (req.url === '/api/todos') { if (req.method === 'GET') { // GET メソッドの場合、全 TODO を JSON 形式で返す res.setHeader('Content-Type', 'application/json') return res.end(JSON.stringify(todos)) } // GET 以外の HTTP メソッドはサポートしないため 405(Method Not Allowed) res.statusCode = 405 } else { // /api/todos 以外の URL はないので 404 (Not Found) res.statusCode = 404 } res.end() }).listen(3000) ``` - `createServer` はコールバック関数を受け取る - コールバック関数は 2 つ引数を受け取る - 第一引数はリクエストを表す。読み込みストリーム - 第二引数はレスポンスを表す。書き込みストリーム 上記のサーバにアクセスするリクエストは以下のように実装できる ```javascript http.request('http://localhost:3000/api/todos', res => { let responseData = '' console.log('statusCode', res.statusCode) res.on('data', chunk => responseData += chunk) res.on('end', () => console.log('responseData', JSON.parse(responseData))) }).end() ``` - `request` メソッドは書き込みストリームである `http.ClientRequest` オブジェクトを返す - リクエストは `request()` メソッドコール時ではなく `http.ClientRequest` オブジェクトの `end()` メソッドコール時に送信される ## Web アプリケーションフレームワーク http モジュールは低レベルなので直接使うよりもふだんはフレームワーク経由で利用する 特によく使われるのは [Express](https://expressjs.com/ja/) で、http モジュールを使うよりもシンプルに可読性高く書ける ```javascript // http モジュールで対応メソッドを増やす場合、if 文の数が増える const server = http.createServer((req, res) => { if (req.url === '/api/todos') { if (req.method === 'GET') { // .. } if (req.method === 'POST') { // ... } // Express で対応メソッドを増やす場合、 Router の記述を増やす // routes.todos.js router.route('/') .get((req, res) => { // ... }) .post((req, res) => { // ... }) module.exports = router // app.js // /api/todos 以下のパスに対するリクエストのハンドリングを ./routes/todos モジュールに移譲 app.use('/api/todos', require('./routes/todos')) // ... ``` ```javascript // http モジュールでパスパラメータの id を解釈する場合、正規表現を使う const server = http.createServer((req, res) => { const match = req.url.match(/^\/api\/todos\/(\d+)\/?$/) if (match) { // 数字部分を取り出す const todoId = Number(match[1]) // ... } // Express でパスパラメータの id を解釈する場合、プレースホルダを利用できる app.get('/api/todos/:id(\\d+)', (req, res) => { const todoId = Number(req.params.id) // ... }) ``` ```javascript // http モジュールでクエリパラメータを取得する場合、URL API で URL をパースする // URL は第一引数に req.url、第二引数にベースの URL を指定する new URL('/api/todos?completed=true', 'http://localhost:3000') _.searchParams.get('completed') // => true const server = http.createServer((req, res) => { const url = new URL(req.url, `http://${req.headers.host}`) if (url.pathname === '/api/todos') { const completedFilter = url.searchParams.get('completed') // ... } // Express では特別な処理をしなくても req.quesry から取得できる app.get('/api/todos', (req, res) => { const completedFilter = req.query.completed // ... }) ``` ## メモ欄