inuinu blog(開発用)

BOT @wagagun の開発ノウハウや、IT向け?の雑記ブログです。

なんちゃってRESTful APIについて(処理手順編)

この記事では、RESTful APIの実装方法を、一般的なWebフレームワークの規約に沿わないDB(AS400など)を例に解説します。

こちらは主にSQL等、DBの照会・更新の手法は知っているが、APIからWebアプリ開発を始めてみようとしている方を対象にしているため、初心者向けの情報もありますが、ご了承ください。

もしも、Web APIって何?というレベルでしたら、下記のコラムも目を通してください。
inuinu-tech.hatenablog.jp

Webアプリ初心者が、いきなりWeb APIを作成する場合は?

覚えること

こういったことを心がけると、比較的早くマスターできます。

  • データの取得は主にGETかPOSTパラメータだが、その違いを覚える
  • 最初はGETで、SELECTから
  • Webシステムでダンプコマンドがある場合は、どのようなパラメータが渡ってきているか?を見てみる
  • パラメータをもとにSQLを発行するが、その結果をダンプできるとなお良い
  • OKなら、それを構造体⇒JSON化してみる
    • 社内で先人が、SQL⇒構造体化する共通部品を作成している場合もあるので、それを利用するのが良い
    • 1件のみ(単票)、複数件OK(一覧)での、JSONの構造の違いも覚える
  • POSTは入力フォームを作って、そこから実行してみる

もし、社内に先人がいない場合は、荒野の一人旅になると思いますが、まず、予定されるWeb言語にこのような機能があるかを調査しましょう。

  • JSONへのデコード/エンコードがワンタッチでできるような言語であるか?

JSONからその言語で利用できる変数体系に変換、あるいはJSONに変換できないと意味がありません。
あなたはJSONのフォーマットに関する知識はゼロなのですから、JSON変換プログラムを一から作らないといけないような言語は、絶対に避けましょう。

AS400のケース

私の周りにはAS400の技術者が多いのですが、AS400のデータをAPIとして活用したいというニーズはあります。

AS400JSON変換ができないので、中間サーバーを作り、そこでJRubyJDBCなりでアクセスして、JSONはそっちで作成することをおすすめいたします。
(AS400はデータサーバーに特化します。)

さらに、AS400ActiveRecordを想定したファイルレイアウトではないため、JRubyベースで、SQL直接発行+SinatraAPI化…といった感じでしょうか。

AS400内に中間サーバーを作成する方法もあるとは思いますが、果たして、その技術者が確保できるかどうか?
Linux技術者が多いのでしたら、中間サーバーは外出ししたほうが楽かもしれません。

GET/POSTの違い

GET/POSTとは何でしょうか?

大雑把に以下の違いで覚えると楽です。

GET

詳細ページなどのリンクをクリックした際、ブラウザのURL欄の後ろに「?」から始まる文字列がありませんか?
それがGETパラメータです。

「?aaa=bbb&ccc=ddd」ですと…<

  • aaaというパラメータにbbbという値を送る
  • cccというパラメータにdddという値を送る

となります。

なんちゃってRESTful APIのGET型では、主に、以下の処理で利用されます。

  • 照会画面(SELECT文)
  • レコードの削除や取り消し(DELETE文やUPDATE文)
  • トグルスイッチの切替(UPDATE文)

以降はSELECTでの処理例です。

  1. GETパラメータをもとにSQL文を組み立てる。
  2. SQL文(SELECT文を実行する)
  3. 取得したデータを構造体(言語によってはハッシュ・連想配列)や配列に展開する
  4. 構造体(あるいはハッシュ・連想配列)をJSONに変換したものを、HTMLの代わりに返す
    (text/htmlではなくapplication/jsonで返す)

といった手順になります。

DELETEやUPDATEについては、

  1. GETパラメータをもとにSQL文を組み立てる。
  2. SQL文(DELETEまたはUPDATE文を実行する)
  3. 成功あるいは失敗を構造体にセットする
    (失敗した場合はメッセージも返す事が多い)
  4. 構造体(あるいはハッシュ・連想配列)をJSONに変換したものを、HTMLの代わりに返す
    (text/htmlではなくapplication/jsonで返す)

といった手順になります。

昔はXMLというものを返していましたようですが、廃れてきていますね。
JSONのほうがわかりやすいので、そちらで統一しましょう。

POST

登録・変更画面の入力フォームのソースを覗くと、

<formmethod="POST">
  <input type="~" name="hoge" value="初期値">
</form>

となっていませんか?

上記の例ですと、ボタンをクリックした後の次のページでは、以下の値が取得できます。

  • hogeというパラメータに「初期値」という値
    (フォームに入力された場合は、入力値に差し替えられる)

登録・変更画面のに使用されるPOSTの場合は、GETとは異なり「?~」で受け渡しをせず、表に見えない形で、次のページに受け渡しを行います。

たまに、入力フォームかつmethod属性がGETの場合もありますが、その場合は「?~」で渡された場合と同様になります。
ただし、文字列長に制限があったり、エンコード・デコードのわずらわしさから、登録画面はほぼPOSTだと思います。

登録・変更画面のに使用されるPOSTの場合は、GETとは処理手順が多少異なります。

  1. 入力パラメータについての簡単なエラーチェックを行う
    • 日付チェックや数値チェック、桁数チェック程度
    • (DBのカラムの型や長さに沿ったチェックを行う)
  2. API送信元のシステムが、エラーチェックを満足に行えない場合
    • 不足しているエラーチェックを行う
  3. エラーがあった場合は、
    • 更新できない旨や、エラーメッセージをJSON化して返す
  4. NSERTまたはUPDATE文を発行する
  5. 成功あるいは失敗を構造体にセットする
    (失敗した場合はメッセージも返す事が多い)
  6. 構造体(あるいはハッシュ・連想配列)をJSONに変換したものを、HTMLの代わりに返す
    (text/htmlではなくapplication/jsonで返す)

フィスコンピュータの入力画面に近いですね。

多くの場合、入力先のシステムにてエラーチェックを行うのですが、サーバーサイド側の方でないと、より詳細なエラーチェックを行いケースがあります。
その場合は、エラーチェックを行い、エラーだった場合は、更新を行わずに、エラーメッセージを返します。

エラーチェックのみで、更新はまた別のAPIで…というケースもありますが、セキュリティホールになりやすいので、エラーチェック+更新は、一体化させたほうが良いでしょう。

POSTの場合はフォームデータで統一させる

まれに、POSTで、フォームデータではなくJSONで欲しいというリクエストがありますが、そうすると、添付ファイルが渡せないので、私はフォームデータで統一しています。

Web言語によっては、クライアント側で「multipart/form-data」と明示しないと、API側が落ちる場合もありますので、注意しましょう。

submitで画面遷移させない

APIJavascript側から実行されることも多々あり、その場合はsubmitではなく、Javascript側で戻り値を受け取り、処理を実行させます。

ただし、APIのみのテストであれば、POSTの場合は仮のフォームを作成しsubmitさせたり、GETの場合はURLを組み立てて直接実行させることが多いです。

JSONとは

JSONの定義

JSONとは、ざっくり説明すると、Javascriptの構造体から発展した書式です(ほぼ業界標準)。
定義不要で、見た目がシンプルなことから、Web APIではXMLではなくJSONを使うことが主流になっています。

構造体はAS400でいうとDS、Rubyでいうとハッシュ(に近いもの)かなと。
構造体の中に配列を入れたり、さらにその配列の中に構造体を入れたりできます。

JSONで使用する記号等

  • {~} は構造体を表します
  • [~] は配列を表します
  • , 項目をつなぎます
  • “~” 文字列の場合に囲みます
  • {“key” : “value”}といった形で受け渡しします
    (keyの箇所はJSONキーともいいます。構造体(連想配列)のキーになります。)
  • “arrayname” : で配列名となります

値にはNULLやtrue/falseも使えますが、言語によってはサポートしない、あるいは別値に変わってしまうものもあります。
その場合は…といいますか、最初からこれで統一してもらっています。

  • NULLは””あるいは0に、あるいはデータが有る無いのフラグを渡す
  • そのNULLが配列の場合はで
  • ture/falaseは1/0で

といった代替値で受け渡すように依頼することが多いです。

SONの具体例(SELECT、一覧タイプ)

https://~/get_foo?bar=1

を実行したとして、その実行結果が複数レコード存在する場合は…

{
  "error" : 0,
  "message" : [], 
  "record" : [
    {"id" : 10, "code" : "abc", "name" : "あいうえお", "price" : 123},
    {"id" : 25, "code" : "def", "name" : "かきくけこ", "price" : 456} 
  ],
  "record_count" : 2
}

となります。
これは…

  • エラーもなく、SELECTは成功しましたよ
  • 「bar=1」の条件に一致した、fooのレコード件数は2件ですよ
  • レコード毎にcodeとnameとpriceをお渡ししますよ

を意味します。
そのSQLは、

SELECT id, code, name, price
FROM foo
WHERE bar='1'

といった感じでしょうか。

エラーの場合は…

{
  "error" : 1,
  "message" : ["barの値が正しくありません。"], 
  "record" : [],
  "record_count" : 0
}

レコード0件の場合は…

{
  "error" : 0,
  "message" : [], 
  "record" : [],
  "record_count" : 0
}

となります。
(ちょっと寂しい気もしますが…)<

JSONの具体例(SELECT、単票タイプ)

https://~/get_foo?id=10

のように、その実行結果が必ず1件の場合は、配列にせずに、

{
  "error" : 0,
  "message" : [], 
  "id" : 10,
  "code" : "abc",
  "name" : "あいうえお",
  "price" : 123 
}

と、クライアント側から要求されることがあります。
ただ、レコードが取得できなかった場合、

{
  "error" : 0,
  "message" : [], 
}

で渡した場合、クライアント側が、変数未定義で落ちてしまう可能性が高いです

接続テストあるあるですね。

それでは、変数未定義を回避するために、文字列の場合は空、数値は0とした場合、

{
  "error" : 0,
  "message" : [], 
  "id" : 0,
  "code" : "",
  "name" : "",
  "price" : 0 
}

idが0というレコードがあるのか無いのかが、後に不具合に発展するケースもありますので、一覧と同様、

{
  "error" : 0,
  "message" : [], 
  "record" : [
    {"id" : 10, "code" : "abc", "name" : "あいうえお", "price" : 123}
  ],
  "record_count" : 1
}

一覧型のJOSNにするか、あるいは、

{
  "error" : 0,
  "message" : [], 
  "record_count" : 0,
  "id" : 0,
  "code" : "",
  "name" : "",
  "price" : 0 
}

単票スタイルのままrecord_countを設け、0の場合はデータなし、1の場合は1件取得できた…と判断したほうが、双方にとって幸せかもしれません。

JSONの具体例(SELECT以外)

エラーの場合は…

{
  "error" : 1,
  "warning" : 0,
  "message" : ["名称は80文字以内にしてください。","単価は1円以上の値を入力してください。"]
}

正常にINSERT(またはUPDATE)した場合は…

{
  "error" : 0,
  "warning" : 0,
  "message" : []
}

随分と味気ない感じではありますね。

warningは、更新はしたが、ちょっと問題があるかもしれない場合を想定したものですが、そういったものがなければ、除去して構いません。

warningは使用せずに、errorが0でmessageに値が入る場合は警告にするといった、仕様にする手もあります。要は「決め」を双方でしっかりとしておきましょう。
(errorは1が警告、2が入力値エラー、3は重大なエラー(更新時に発生したエラー※)といった決めもありました。errorというJSONキーが目立つのであれば、statusなどでもいいでしょう。)

※ 「更新時に発生したエラー」とは、DBサーバーが接続できないといったものや(500エラーにするケースもある)、更新後にメールやSlackにメッセージを投げるはずがサービス障害が発生した(重大エラーではなく警告で済ますケースもある)…等が考えられそうです。

最後に

SELECTを中心にJSONの出力例を解説しましたが、そこまで難しくはありません。
がんばってくださいね。

さらに、実践的な記事も用意しています。

inuinu-tech.hatenablog.jp

それでは、また。