2021年8月1日日曜日

作成中のバックエンドフレームワークSaraswati

 今Golangで作っているSaraswatiの機能をまとめておく

SaraswatiはSedna XML Database をミドルウエアに使用したバックエンドフレームワークだ。

1.サーバの起動

 go run Saraswati

2.クエリの準備

Sarawsatiのあるフォルダ以下、

queryRoot/etc/qr/query.txtに以下を記述

queryRootはHTTPアクセスされた際の起点となるルートフォルダとなる。

開発時はlocalhost:5000で開発しているので、

フロントから、http:localhost:5000/etc/qrをアクセスした際に実行されるクエリとなる。


query.txtは見て分かる通りXQueryだ。

このXQueryはSaraswatiがミドルウェアに使っている Sedna XML Databaseで実行される。

クエリ内の(:PARAMS:)はXMLではタダのコメントだが、SaraswatiはフロントからJSONで渡ってきたデータをこの位置にバインドする。ちなみに、se:qrは今回C++でSedna XML Database用に作成したQRコード作成プラグインだ。このようにSedna XML DatabaseはXQueryに元から無い機能でも、後からプラグインとして実装が可能な仕組みを提供している。

 declare function se:qr($a as xs:string*) as xs:string external; (:PARAMS:) (: convert $text input to qr image :) (: size as pixel for image size :) let $a := doc('test1.db')//pages let $qr := se:qr($text) return <DATA TYPE='JSON' OUTPATH="//DATA/QRIMG"> <QRIMG>{$qr}</QRIMG> </DATA>

ちょっと引っかかるのは、let $aの部分だが、実はダミーとしてtest1.dbに対してクエリを実行するために書かれている。
クエリはどのDBに対して実行するのかという指定が必要なため、QRコードのような、本来DBアクセスをしないクエリであってもダミーとして実行対象のDBが必要となる。

ただし、現在のバージョンのSaraswatiでは、ダミーが無くとも適当なデータベースをダミーとして実行できるようになっている。

3.フロントの準備

クエリを呼び出して、結果を表示するためのフロントをJQueryで作成する

http:localhost:5000/etc/qrを呼び出し、

$(".qimg").attr('src','data:image/png;base64,' +myArray.QRIMG)で、戻ってきたJSONのQRIMGをイメージとして設定するだけだ。


4.実行

以下はSedna XML Database の吐き出したログ

Saraswatiは(:PARAMS:)の部分にフロントから渡されたJSONがXMLの変数として記述し、Sedna XML Databaseに渡して実行、その後、Sedna XML Databaseからの出力(XMLでなければならない)をJSONに変換してフロントへ戻す

ただし、このバインドの仕方は今後変わる予定で、強制的に全体を<VAL>で囲うことになると思う

let $text := <VALあいうえおかきくけこ</VAL>のようになる予定

---   declare function se:qr($a as xs:string*) as xs:string external;
---   let $text := 'あいうえおかきくけこ'
---   
---   (: convert $text input to qr image :)
---   (: size as pixel for image size :)
---   
---   let $a := doc('test1.db')//pages
---   let $qr := se:qr($text)
---   return
---   <DATA TYPE='JSON' OUTPATH="//DATA/QRIMG">
---     <QRIMG>{$qr}</QRIMG>
---   </DATA>
---   


5.出力結果

フロント側でレスポンスを見てみると、このようにQRIMGにはBase64エンコードされた画像が含まれている。

フロントはこれを使って表示すればよい


6.食品成分表からのデータ取得

クエリはqueryRoot/foodinfo/nutrients/query.txtに以下の内容で用意する

(:PARAMS:) let $a := doc('test2.db')//FOODS/FOOD[@FOOD_ID=$id] return <DATA TYPE='JSON' OUTPATH="//DATA/FOOD"> <FOOD> {$a/JP_DISP_NAME} {$a/ENERC_KCAL} {$a/PROT-} {$a/FAT-} {$a/CHOCDF-} {$a/VITA_RAE} {$a/TOCPHA} {$a/NE} </FOOD> </DATA>

FOOD_IDでしか検索しない場合のクエリだ

let $id :='2002'を入れて、実行するとSaraswatiは上記XMLの出力内容を以下のようなJSONに変換してフロントに戻す。

ちなみに2002は食品成分表の通し番号になっている。


{
    "FOOD": {
        "NE""1.1",
        "JP_DISP_NAME""さつまいも(塊根:皮なし:生)",
        "ENERC_KCAL""126",
        "PROT-""1.2",
        "FAT-""0.2",
        "CHOCDF-""31.9",
        "VITA_RAE""2",
        "TOCPHA""1.5"
    }
}


これを表示するフロントを作れば、一応の完成だ


7.さらなる改良

これだとIDでしか検索できないので、データ全件検索できるようにする。

IDだと結果は一件だったのだが、

名称などで検索すると、複数件の結果を返さなければならない。

以下のように複数件の検索結果を返すようにクエリを修正する。

<DATA TYPE='JSON' OUTPATH="//DATA/LIST" FORCEARRAY="//LIST/FOOD">
(: 複数のFOODを返す場合には、その上位にラッパーを作り、OUTPATHに指定をする :)
(: FORCEARRAYはのラッパーの下位のFOODが1件の場合でもJSON配列で返すために指定する :)
<LIST>
{
(:PARAMS:)

for $food in doc('test2.db')//FOODS/FOOD
where contains($food/SEARCH_NAME,$id) or contains($food/ADDITIONAL,$id) or $food/@FOOD_ID=$id
order by $food/@FOOD_ID
return
  <FOOD>
    {$food/JP_DISP_NAME}
    {$food/ENERC_KCAL}
    {$food/PROT-}
    {$food/FAT-}
    {$food/CHOCDF-}
    {$food/VITA_RAE}
    {$food/TOCPHA}
    {$food/NE}
  </FOOD>
  }
</LIST>
</DATA>

この時、適当な検索文字列(さつまいも)で検索した結果は以下の様になる。

{
    "LIST": {
        "FOOD": [
            {
                "JP_DISP_NAME""さつまいも(塊根:皮なし:生)",
                "ENERC_KCAL""126",
                "PROT-""1.2",
                "FAT-""0.2",
                "CHOCDF-""31.9",
                "VITA_RAE""2",
                "TOCPHA""1.5",
                "NE""1.1"
            },
            {
                "JP_DISP_NAME""さつまいも(塊根:皮なし:蒸し)",
                "ENERC_KCAL""131",
                "PROT-""1.2",
                "FAT-""0.2",
                "CHOCDF-""31.9",
                "VITA_RAE""2",
                "TOCPHA""1.5",
                "NE""1.1"
            },
            {
                "NE""1.3",
                "JP_DISP_NAME""さつまいも(塊根:皮なし:焼き)",
                "ENERC_KCAL""151",
                "PROT-""1.4",
                "FAT-""0.2",
                "CHOCDF-""39.0",
                "VITA_RAE""1",
                "TOCPHA""1.3"
            },
            {
                "FAT-""0.6",
                "CHOCDF-""71.9",
                "VITA_RAE""(0)",
                "TOCPHA""1.3",
                "NE""2.4",
                "JP_DISP_NAME""さつまいも(蒸し切干)",
                "ENERC_KCAL""277",
                "PROT-""3.1"
            },
            {
                "JP_DISP_NAME""さつまいもでん粉",
                "ENERC_KCAL""340",
                "PROT-""0.1",
                "FAT-""0.2",
                "CHOCDF-""82.0",
                "VITA_RAE""0",
                "TOCPHA""-",
                "NE""Tr"
            },
            {
                "VITA_RAE""(0)",
                "TOCPHA""(0)",
                "NE""0",
                "JP_DISP_NAME""はるさめ(普通はるさめ:乾)",
                "ENERC_KCAL""346",
                "PROT-""0",
                "FAT-""0.2",
                "CHOCDF-""86.6"
            },
            {
                "PROT-""0.9",
                "FAT-""0.5",
                "CHOCDF-""33.1",
                "VITA_RAE""3",
                "TOCPHA""1.0",
                "NE""0.8",
                "JP_DISP_NAME""さつまいも(塊根:皮つき:生)",
                "ENERC_KCAL""127"
            },
            {
                "NE""0.9",
                "JP_DISP_NAME""さつまいも(塊根:皮つき:蒸し)",
                "ENERC_KCAL""129",
                "PROT-""0.9",
                "FAT-""0.2",
                "CHOCDF-""33.7",
                "VITA_RAE""4",
                "TOCPHA""1.4"
            },
            {
                "PROT-""1.4",
                "FAT-""6.8",
                "CHOCDF-""38.4",
                "VITA_RAE""5",
                "TOCPHA""2.6",
                "NE""1.0",
                "JP_DISP_NAME""さつまいも(塊根:皮つき:天ぷら)",
                "ENERC_KCAL""205"
            }
        ]
    }
}

あとはこれを表示するフロントを作ればよい

こんな感じに表示すればよいだろうか?

もちろん、成分表をDIVやCANVASを使って棒グラフにしてもよい



8.まとめ

このようにSaraswatiを使った開発では、フロント開発者が、自分の都合のいいようにクエリを編集できる。

SaraswatiはJSONで渡ってきたデータをXML化し、XQueryにバインドし、結果XMLをJSONに戻す仕組みを提供する。

この際、Saraswatiそのものは停止する必要が無く、コンパイルし直したりデプロイし直したりする必要もなく、動かしっぱなしでクエリを編集すればいい。

ところで、Saraswatiにはドメイン層が無い。

無いは言い過ぎかもしれない。

取得したデータの計算などはXQueryで行う仕組みだ。

既存DBでもPL/SQLなどで実現していることと同じことをXQueryで行えばいい。


また、Sednaには連続してデータの更新をするような機能はないが、Saraswatiはその機能を提供する。

今回、連続したDBの更新など、データの更新についてはここで記述しなかったが、Saraswatiは<DATA>タグ以外に<SEDNA><PROCS><PROC>というエンベロープで包むことにより、連続データの更新が出来るようになっている。

//SEDNA/PROCS/PROCに複数のデータ更新系クエリを記述できる。

PROCS/PROCが全部正常に実行されればコミットされ、

途中で失敗したらロールバックという仕組みになっている。

つまり、データ更新に於いてもクエリを編集すれば良い


0 件のコメント: