GraphQL DataLoader の実践的な使い方

DataLoader とは GraphQL におけるデータ取得において内部で N+1 処理になるのを回避するためのツールです。例えば、あるリソース(A)一覧とそれに関連付けられた 1:1 のリソース(B)を一気に要求すること場合、最初のリソース(A)リストをクエリした後に、各レコードに対してリソース(B)を毎回クエリすると遅くなります。リソース(B)に対してそのキーをスタックしていき、まとめてクエリすることで N+1 回を 1+1 回に減らすことができます。この機能をまとめたものが DataLoader です。 実践的な使い方 条件検索について DataLoader はあくまでも Key-value キャッシュ付きストアと考えた方がいいです。GraphQL の検索 Query で条件指定する場合は DataLoader を経由せずに直接行います。但し結果はその後の下階層で再度利用する可能性があるので prime メソッドでキャッシュしておきます。 キーによるレコード検索について マスターリソースに対する場合は、単純に主キーで取得およびキャッシュします。 リレーションリソースに対する場合は、複合キーが cacheKeyFn の処理によってユニークな文字列になるように変換します。サロゲートキーは使いません。 要求したユーザーによって結果が変わる場合 この場合は、リクエストコンテキストに応じて DataLoader インスタンスを生成し付与します。コンテキストを生成するミドルウエア内で、DataLoader インスタンス自体をセットするか、それらインスタンスを生成できるメソッドを提供するといいでしょう。 どのレイヤーのデータを DataLoader で扱うべきか GraphQL の Type とデータベースのレコードスキーマが完全に一致せずに、データの変換が必要となる場合の問題です。基本的にデータベースレイヤーに DataLoader を適用して、GraphQL Type には都度変換する方がいいと考えます。この理由としてリゾルバ周りの変更の影響を受けないことや、 N+1 はそもそもデータベース(ストレージ)への負荷であるからです。 コンテキストから DataLoader をどうモデルに渡すか コンテキストはリゾルバのパラメータとして受け取りますが、DataLoader インスタンスは下位レイヤーのモデル(ロジック層やストレージ層)で使いたいので、そのままストレートにモデルのコンストラクタなどで渡すのが一番だと思います。これに引っ張られて Active-Record パターンは JavaScript ベースでは使いずらいと思います。Ruby on Rails 等だとスレッドに情報をセットして、透過的に扱うことも可能ですが。

2019年6月23日 · Toshimitsu Takahashi

DynamoDB DataMapper For JavaScript でインデックス付きテーブルを作成するには

Amazon DynamoDB DataMapper For JavaScript という TypeScript にも標準対応している DynamoDB の JavaScript 向けデータマッパーライブラリがあります。 このライブラリはテーブル作成・削除の機能もあるので、テスト用テーブル作成もこちらを使ってスクリプト化しようと考えました。 ただドキュメントが少なく、グローバルセカンダリインデックスを付与する方法を探すのに手間取ったため、ここにメモしておきます。 スキーマ定義とテーブル作成 プライマリキー(通常のHASHキー)は user_id です。 外部キー(GSIのHASHキー)は email です。 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 import { attribute, hashKey, table } from '@aws/dynamodb-data-mapper-annotations' @table('users') class User { @hashKey({ attributeName: 'user_id' }) userId!: string @attribute({ indexKeyConfigurations: { 'emailIndex': 'HASH' } }) email!: string } async function createTable() { await mapper.ensureTableExists(User, { readCapacityUnits: 1, writeCapacityUnits: 1, indexOptions: { 'emailIndex': { type: 'global', projection: 'keys', readCapacityUnits: 1, writeCapacityUnits: 1, } } }) } createTable() .then(() => { console.info('Created DynamoDB table users') }) .catch(err => { console.error(err) }) 結果確認 $ aws dynamodb describe-table --table-name users --endpoint-url http://localhost:4569 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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 { "Table": { "TableArn": "arn:aws:dynamodb:us-east-1:000000000000:table/users", "AttributeDefinitions": [ { "AttributeName": "user_id", "AttributeType": "S" }, { "AttributeName": "email", "AttributeType": "S" } ], "GlobalSecondaryIndexes": [ { "IndexSizeBytes": 0, "IndexName": "emailIndex", "Projection": { "ProjectionType": "KEYS_ONLY" }, "ProvisionedThroughput": { "WriteCapacityUnits": 1, "ReadCapacityUnits": 1 }, "IndexStatus": "ACTIVE", "KeySchema": [ { "KeyType": "HASH", "AttributeName": "email" } ], "IndexArn": "arn:aws:dynamodb:ddblocal:000000000000:table/users/index/emailIndex", "ItemCount": 0 } ], "ProvisionedThroughput": { "NumberOfDecreasesToday": 0, "WriteCapacityUnits": 1, "LastIncreaseDateTime": 0.0, "ReadCapacityUnits": 1, "LastDecreaseDateTime": 0.0 }, "TableSizeBytes": 0, "TableName": "users", "TableStatus": "ACTIVE", "KeySchema": [ { "KeyType": "HASH", "AttributeName": "user_id" } ], "ItemCount": 0, "CreationDateTime": 1560437300.665 } }

2019年6月13日 · Toshimitsu Takahashi

JavaScript で HTML の参照文字をアンエスケープするには

HTML 文字列のアンエスケープ(unescape)とはタグに使われる文字をエスケープした文字実体参照などを実際の文字などに戻すことです。例えば &lt;b&gt;tilfin&apos;s note&lt;b&gt; を <b>tilfin's note<b> と変換します。RSS Feed などに HTML が埋め込まれるときに必要になったりします。 最近のブラウザでは、 DOMParser という HTML/XML/SVG パーサーが実装されているのでこれを使います。 サポート状況 https://caniuse.com/#search=DOMParser parseFromString メソッドの第2引数に MIME タイプを指定します。 1 2 3 4 function unescapeHTML(escapedHtml) { const doc = new DOMParser().parseFromString(escapedHtml, 'text/html'); return doc.documentElement.textContent; } 上記のようにユーティリティ関数を定義して試します。 > unescapeHTML("&lt;b&gt;tilfin&apos;s note&lt;b&gt;") "<b>tilfin's note<b>"

2019年3月7日 · Toshimitsu Takahashi

背景色に応じて文字色を白と黒で切り替える

ユーザーが背景色を指定できる要素の文字色をその背景色に応じて切り替えたいときの方法です。固定で白や黒にすると、背景色によっては見辛くなってしまうためです。そのためには背景色の輝度を算出します。RGBに対して R × 0.299 + G × 0.587 + B × 0.114 で算出できます。下記の例では背景色が暗ければ白を、明るければ黒となるように計算しています。 1 2 3 4 5 6 7 8 9 10 11 12 13 const backColor = '#0033ff'; let r = backColor.substr(1, 2), g = backColor.substr(3, 2), b = backColor.substr(5, 2); r = parseInt(r, 16); g = parseInt(g, 16); b = parseInt(b, 16); const y = 0.299 * r + 0.587 * g + 0.114 * b; // 輝度 const foreColor = v < 128 ? 'white' : 'black';

2019年1月15日 · Toshimitsu Takahashi

Chrome Extension の createHTMLNotification メソッドでデータを渡してフレキシブルに内容を構成するには

Chrome拡張でデスクトップ通知の実装時に、HTMLを使うときデータを簡単に渡す方法について。 Desktop Notifications chrome.notifications - Google Chrome を見ると、通常のタイトル、イメージ、テキストの三つを渡して表示する通知と、HTMLで自由に組める通知がある。 1 2 3 4 5 6 // Create a simple text notification: var notification = webkitNotifications.createNotification( '48.png', // icon url - can be relative 'Hello!', // notification title 'Lorem ipsum...' // notification body text ); このように前者の通知は引数でデータを渡せるが、後者では HTML ファイル名を指定するだけになっている。 1 2 3 4 // Or create an HTML notification: var notification = webkitNotifications.createHTMLNotification( 'notification.html' // html url - can be relative ); これで内容を可変にしたいときは、例として ...

2013年2月20日 · Toshimitsu Takahashi

JavaScript の Array で forEach したときに break するには

jQuery.each メソッドだと、return true で continue, return false で break できます。 しかし、node で普通に array.forEach で同じようにやろうとしたらできない。 node の実装つまり ECMAScript 5 においては、array.forEach に break 処理はない。代わりに some メソッドを使うと意図したことができることがわかった。 1 2 3 4 5 6 7 8 9 array.some(function(item){ if (item.isEmpty) { return false; // continue } if (item.isLast) { return true; // break } }); some メソッドは、要素に対して評価(コールバック)関数を実行し、true になるものが一つでもあれば true を返します。これが本来の用途です。

2012年11月14日 · Toshimitsu Takahashi

Mongoose で MongoDB の Embedded Documents の扱いで嵌まったこと

node(.js)と相性が良いということで、MongoDB とそのJavaScript O/R マッパーライクなモデリングライブラリの Mongoose を使い始めました。 MongoDBと言えばドキュメント指向モデルでそれを特徴づける Embedded Documents が有名ですが、この機能を Mongoose から利用する上で躓いたので、メモしておきます。 Embbed Document の Array について var Users = new Schema({ name : String }); var Comments = new Schema({ title : String , body : String , date : Date }); var BlogPost = new Schema({ author : { type: ObjectId, ref: ‘User’ } , title : String , body : String , date : Date , comments : [Comments] , meta : { votes : Number , favs : Number } }); mongoose.model(‘User’, Users); mongoose.model(‘BlogPost’, BlogPost); ...

2011年10月30日 · Toshimitsu Takahashi

jQuery を用いて大きな画像などを Google Map ライクにスクロール表示する UI

下記のサンプルは jQuery プラグインとして公開しています。(2009/05/20 追記) jquery-scrollview - This jQuery plugin applies grab-and-drag scroll view to block elements. - Google Project Hosting 壁紙などの大きなサイズの画像を表示するのに Google マップのようなインターフェイスを提供したい。 Google マップのような、 マウスドラッグでスクロール ダブルクリックした所にセンタリング するような処理である。ただ、ズームに関しては実装しない。 ということで実装したサンプルは ScrollViewer Sample である。 ScrollViewer jQuery を必要とする。 function ScrollViewer(){ this.initialize.apply(this, arguments); } ScrollViewer.prototype = { initialize: function(container){ // // Dragging Cursor Set. // // http://docs.jquery.com/Utilities/jQuery.browser // jQuery.browser Deprecated in jQuery 1.3 // if (navigator.userAgent.indexOf(“Gecko/”) != -1) { this.grab = “-moz-grab”; this.grabbing = “-moz-grabbing”; } else { this.grab = “default”; this.grabbing = “move”; } // Get container and image. this.m = $(container); this.i = this.m.children().css(“cursor”, this.grab); this.isgrabbing = false; // Set mouse events. var self = this; this.i.mousedown(function(e){ self.startgrab(); this.xp = e.pageX; this.yp = e.pageY; return false; }).mousemove(function(e){ if (!self.isgrabbing) return true; self.scrollTo(this.xp - e.pageX, this.yp - e.pageY); this.xp = e.pageX; this.yp = e.pageY; return false; }) .mouseout(function(){ self.stopgrab() }) .mouseup(function(){ self.stopgrab() }) .dblclick(function(){ var _m = self.m; var off = _m.offset(); var dx = this.xp - off.left - _m.width() / 2; if (dx < 0) { dx = “+=” + dx + “px”; } else { dx = “-=” + -dx + “px”; } var dy = this.yp - off.top - _m.height() / 2; if (dy < 0) { dy = “+=” + dy + “px”; } else { dy = “-=” + -dy + “px”; } _m.animate({ scrollLeft: dx, scrollTop: dy }, “normal”, “swing”); }); this.centering(); }, centering: function(){ var _m = this.m; var w = this.i.width() - _m.width(); var h = this.i.height() - _m.height(); _m.scrollLeft(w / 2).scrollTop(h / 2); }, startgrab: function(){ this.isgrabbing = true; this.i.css(“cursor”, this.grabbing); }, stopgrab: function(){ this.isgrabbing = false; this.i.css(“cursor”, this.grab); }, scrollTo: function(dx, dy){ var _m = this.m; var x = _m.scrollLeft() + dx; var y = _m.scrollTop() + dy; _m.scrollLeft(x).scrollTop(y); } } ...

2009年1月22日 · Toshimitsu Takahashi

JavaScript でオブジェクトのメンバであるメソッドをイベントハンドラにしたときに非 DOM の this を取りたい

ちょっとエントリのタイトルが微妙だが、まず次の HTML + JavaScript コードをご覧いただきたい。(前後の HTML コードをカットしている) s1 ボタン 上記を実行して「s1 ボタン」をクリックすると、「This is s1ButtonName」とアラートが表示される。 これはイベントでは this がイベント送出の DOM オブジェクトとなるからだ。button 要素の name 属性が this.name となる。s1.button_click を呼び出していても、this.name が “s1” とはならない。 こういうコーディングをしたい場合、今まで SampleA に当たるものがシングルトンだった。そのため、this の代わりに SampleA.name とベタに定義してしまっても問題がなかった。 ということで、複数インスタンスを作りたい場合どうすれば良いかというのが本題である。 あるオブジェクトのメソッドの中でイベントを無名関数で定義するならば、その中でその外のスコープの this を参照 したいときは with やローカル変数に一旦代入することで拾えるようになる。 例えば、このようなな感じで書くだろう。 var sample = { name: “名前”, display: function(){ var that = this; $(“s1button”).click(function(){ alert(that.name); }); } }; ...

2009年1月10日 · Toshimitsu Takahashi

JSONP ならぬ HTMLP を Amazon XSLT で試してみた

Amazon Web Services では Style 引数に XSL ファイルの URL を指定することでレスポンスの XML を Amazon のサーバ側でパースできる。これを使って、JSONP 形式に返すサンプルが色々あった。でも単純に表示するだけなら innerHTML に HTML を流し込むだけの方が楽だろう。 ということで JSON with Padding ならぬ HTML with Padding(で合ってる??)を試しに作ってみることにした。 function awsResult(html){ document.getElementById(‘content’).innerHTML = html; } と定義しておいて、Script タグの Padding によって、 awsResult(’ ・・・ ’); 最終的に上記のように返ってくれば、 ・・・ の部分を content に表示できる。 awshtmlp.xsl AWS に渡す XSL ファイルの内容は次のとおり。 ポイントは Callback 引数を拡張定義することで aws:OperationRequest/aws:Arguments/aws:Argument[@Name=‘Callback’]/@Value から引っ張って出力していること。これでコールバック関数名(前述の awsResult)は固定せずに済む。 また HTML 出力としているが結果は JavaScript であるので、インデントはさせない(改行はさせない)で、「’」で囲う。そして escape-apos でタイトルとラベルに含まれている場合はエスケープするようにしている。 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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:aws="http://webservices.amazon.com/AWSECommerceService/2005-10-05" exclude-result-prefixes="aws"> <xsl:output method="html" indent="no" encoding="UTF-8"/> <xsl:template match="/aws:ItemSearchResponse"> <xsl:value-of select="aws:OperationRequest/aws:Arguments/aws:Argument\[@Name='Callback'\]/@Value"/> <xsl:text>('</xsl:text> <xsl:apply-templates select="aws:Items"/> <xsl:text>')</xsl:text> </xsl:template> <xsl:template match="aws:Items"> <ul class="amazonList"> <xsl:apply-templates select="aws:Item"/> </ul> </xsl:template> <xsl:template match="aws:Item"> <li> <a> <xsl:attribute name="href"><xsl:value-of select="aws:DetailPageURL"/></xsl:attribute> <xsl:call-template name="escape-apos"> <xsl:with-param name="target" select="aws:ItemAttributes/aws:Title"/> </xsl:call-template> </a> <xsl:apply-templates select="aws:SmallImage"/> <span class="label"> <xsl:call-template name="escape-apos"> <xsl:with-param name="target" select="aws:ItemAttributes/aws:Label"/> </xsl:call-template> </span> <span class="price"> <xsl:value-of select="aws:ItemAttributes/aws:ListPrice/aws:FormattedPrice"/> </span> </li> </xsl:template> <xsl:template match="aws:SmallImage"> <img> <xsl:attribute name="src"><xsl:value-of select="aws:URL"/></xsl:attribute> <xsl:attribute name="width"><xsl:value-of select="aws:Width"/></xsl:attribute> <xsl:attribute name="height"><xsl:value-of select="aws:Height"/></xsl:attribute> </img> </xsl:template> <xsl:template name="escape-apos"> <xsl:param name="target"/> <xsl:variable name="m"><xsl:text>&apos;</xsl:text></xsl:variable> <xsl:variable name="r"><xsl:text>&amp;apos;</xsl:text></xsl:variable> <xsl:choose> <xsl:when test="contains($target, $m)"> <xsl:value-of select="substring-before($target, $m)"/> <xsl:value-of select="$r"/> <xsl:call-template name="escape-apos"> <xsl:with-param name="target" select="substring-after($target, $m)"/> </xsl:call-template> </xsl:when> <xsl:otherwise><xsl:value-of select="$target"/></xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> サンプル HTML 下記は検索ボックスの内容を HTML with Padding(?) で投げて、結果を表示するサンプルである。 ...

2008年5月26日 · Toshimitsu Takahashi