はじめに
Curlの開発・提供を行っているSCSKでは、VBマイグレーションというサービスを提供しています。これはVisual Basicで作成されたソースをCurlにコンバージョンするサービスですが、Visual Basicに限らず他の言語からでもCurlへ焼き直しすることはできるのか考えました。
そこで今回は、ERPシステム等で使われることが多いOracle社の「Developer」で作成された画面をCurlで作成し、その過程を紹介します。
Oracle社のDeveloperとは、Webに対応したOracle独自の4GLアプリケーション開発・実行環境です。利用するデータベースはOracle DBのみですが、Oracle DBとの高い親和性・レスポンスが売りのツールで、PL/SQL(Oracle Database用にデータベース言語SQLを独自拡張したプログラミング言語)で業務ロジックを作成していくものです。
Curlは次世代Webプラットフォームとして標榜し、その生い立ちは、米国マサチューセッツ工科大学(MIT)の研究室からになります。利用するサーバサービスには制約がありません。詳しくはCurl公式サイトを参照ください。
必要な環境
CurlとDB(AP)サーバとの通信手段には「Curl ORB for java」(以下、ORB)を用います。ORBとは、CurlとJavaをシームレスに結合し、高速なデータ通信を実現するためのツールです。詳しくは下記のサイトをご覧ください。
画面の仕様
実装するにあたり、画面の仕様を決めておきます。今回は以下の仕様とします。
「検索ボタン押下時、テーブル[HINMK_TBL]より全件取得し、取得結果を明細行に設定する。」
とても簡単な一覧検索画面です。
Developerの内部仕様
前ページの画面仕様をDeveloperにて実現する場合、次のような作業が必要となります。
- ウィンドウ、キャンバスを定義
- データブロックを定義(ヘッダ部、明細部)
- 明細部のデータブロックに対して、テーブル[HINMK_TBL]を結びつける
- 明細部に項目(HINMK_CD、HINMK_NM)を定義
- ヘッダ部のデータブロックに項目(検索ボタン)を定義
- 検索ボタン押下時に、明細部へ遷移するように定義(WHEN-BUTTON-PRESSEDトリガーに go_block('明細部')と書く)
- 明細部に遷移時、execute_queryを実行するように定義(WHEN-NEW-BLOCK-INSTANCEトリガーに execute_queryと書く)
ロジックと呼べるものはほとんどなく、プロパティの設定で実装可能なのが特徴です。
Curl+ORBで実装する場合
Curl+ORBで実装する場合は、サーバ側とクライアント側の各々での定義が必要です。データアクセスをサーバ側(Java)で、画面コントロールをクライアント側(Curl)で実装します。Developerはデータアクセスロジックと画面コントロールロジックが融合して実装されていることが多いため、移行する場合はまず「データアクセスロジック」と「画面コントロールロジック」に分割する必要があります。
サーバ側
サーバ側はJavaで実装します。検索ボタン押下時の処理として、サービスクラスに以下のロジックを定義します。
- JDBCを用いて、Oracle DBに対してSQLを実行
- 実行した結果をデータクラスに設定
クライアント側
クライアント側はCurlで実装します。
- RecordGrid(名細部)とボタン(検索部)を定義
- 検索ボタンに対して、ボタン押下時にサーバ側のサービスクラスを実行するように定義
- サーバ側から返却された結果をRecordGridに定義
Curl Developer Centerに掲載されている「Curl ORB for Java」の関連記事も併せて参照ください。
サーバ側の実装
まず、サーバ側の実装を見て行きましょう。実際に定義するプログラムは「HinmkLst.java」「Hinmk.java」の2つです。
HinmkLst.java
サービスクラスです。品目一覧画面をコントロールするための[HinmkLst]クラスを定義し、検索ボタン押下時の処理として、[kensaku]メソッドを定義します。JDBCを用いて、Oracle DBに対してSQLを実行し、結果をデータクラス(Hinmk)に設定します。クライアントにCurlを使っていることを意識させない、一般的なJavaでのDBアクセスロジックと同様です。
package sample; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import com.curlap.orb.security.RemoteService; /** * HttpSession sample */ @RemoteService public class HinmkLst { public ArrayList<Hinmk> kensaku(String pi_cd) throws Exception{ //クライアントには、配列(ArrayList)型で返却する。 ArrayList<Hinmk> hinmklist = new ArrayList<Hinmk>(); try { /* 初期処理 */ // Oracle JDBC Driverのロード Class.forName("oracle.jdbc.driver.OracleDriver"); // Oracle に接続 Connection conn = DriverManager.getConnection ("jdbc:oracle:thin:@dbhosts:1521:instancename", "user", "user"); // ステートメントを作成 Statement stmt = conn.createStatement(); /* 主処理 */ /////SQLを実行する ResultSet rs = stmt.executeQuery("SELECT hinmk_cd,hinmk_nm FROM hinmk_tbl"); //取得結果をセット rs.next(); while(rs.next()) { Hinmk hinmk = new Hinmk(); //データクラスの生成 hinmk.setHinmk_cd(rs.getString("hinmk_cd")); //HINMK_CDの値を設定 hinmk.setHinmk_nm(rs.getString("hinmk_nm")); //HINMK_NMの値を設定 hinmklist.add(hinmk); //1行分のデータとして配列へ設定する。 } /* 終了処理 */ rs.close(); stmt.close(); ///////////////////////////////////////////////////////////////////////////////////////////// conn.close(); }catch(Exception wl_excp) { throw new Exception(wl_excp) ; } return hinmklist; } public Object echo(Object v) { return v; } }
Hinmk.java
テーブル[HINMK_TBL]を格納するためのデータクラスです。
package sample; /** * データクラス(HINMK_TBLの結果を格納する) */ public class Hinmk implements java.io.Serializable { private static final long serialVersionUID = -5592465168071966675L; /* フィールド */ private String hinmk_cd; //HINMK_CD private String hinmk_nm; //HINMK_NM /* ゲッター・セッター */ public String getHinmk_cd() { return hinmk_cd; } public void setHinmk_cd(String hinmk_cd) { this.hinmk_cd = hinmk_cd; } public String getHinmk_nm() { return hinmk_nm; } public void setHinmk_nm(String hinmk_nm) { this.hinmk_nm = hinmk_nm; } }
サーバサイドで作成するロジックは以上です。
クライアント側の実装
続いて、クライアント側の実装を説明します。実際に定義するプログラムは「sample1.curl」「Hinmk.curl」の2つです。
sample1.curl
画面プログラムです。最も魂をこめて作成する部分ですが、ご覧のようにとてもシンプルです。一般的なWebシステムの場合、サーバサイドから返却されたデータを取得する部分が煩雑になりがちですが、Curl+ORBで実現するととてもシンプルな構成となります。
{curl 6.0, 7.0, 8.0 applet} {curl-file-attributes character-encoding = "utf8"} {applet manifest = "manifest.mcurl", {compiler-directives careful? = true}} {import * from SAMPLE} ||-- {let staff:RecordSet = {RecordSet {RecordFields {RecordField "HINMK_CD", domain = String}, {RecordField "HINMK_NM", domain = String} } } } {let rv:RecordView = {RecordView staff}} ||--明細部(RecordGrid) {let rg:RecordGrid = {RecordGrid record-source = rv, height = 10cm, width = 7.5cm, display-navigation-panel? = false } } ||--検索部(検索ボタン) {def cb1 = {CommandButton label = "検索", width = 7.2cm, ||--ボタン押下時のイベント {on Action do ||--サーバにアクセスする def search-service = {HinmkLst server-url = {url "http://curl-svr:8080/curl-orb-server/"}} ||--Javaで作成したサービスクラスを呼び出す。(result-listに結果が返却される) def result-list:#{Array-of any} = {search-service.kensaku ""} ||--サービスクラスからの結果をRecordGridに設定します。 {if-non-null result-list then {rg.records.delete-all} ||--サーバから返却されたresult-listの件数分Recordに設定します。 {for item in result-list do {type-switch item case item:Hinmk do def r = {rv.new-record} set r["HINMK_CD"] = item.hinmk_cd set r["HINMK_NM"] = item.hinmk_nm {rg.records.append r} } } {rg.records.commit} } {search-service.destroy-instance} } } } {value {spaced-vbox cb1, rg}}
Hinmk.curl
サーバ側で定義したデータクラス(Hinmk)と同じ定義を行います。以下のソースコードは、ORBのツールにて自動生成できるのでコーディングは不要です。
package sample; /** * データクラス */ public class Hinmk implements java.io.Serializable { private static final long serialVersionUID = -5592465168071966675L; private String hinmk_cd; private String hinmk_nm; public String getHinmk_cd() { return hinmk_cd; } public void setHinmk_cd(String hinmk_cd) { this.hinmk_cd = hinmk_cd; } public String getHinmk_nm() { return hinmk_nm; } public void setHinmk_nm(String hinmk_nm) { this.hinmk_nm = hinmk_nm; } }
HinmkLst.curl
サーバ側で定義したサービスクラス(HinmkLst)と同じ定義を行います。こちらのソースコードもHinmk.curlと同様、ORBのツールにて自動生成できるのでコーディングは不要です。
||| ||| Curl ORB for java (version 0.8) ||| This code was generated by the Curl code generator automatically. ||| package name : SAMPLE ||| generated date : 2011-10-31 10:31:36.836000 ||| {import * from COM.CURLAP.ORB} {define-class public HinmkLst {inherits HttpSessionClient} {constructor public {default server-url:#Url = null} {construct-super.HttpSessionClient "sample.HinmkLst", server-url = server-url} } {method public {echo v0:any}:any {return {self.invoke "echo", arguments = {FastArray v0}}} } {method public {kensaku v0:String}:#Array {return {self.invoke "kensaku", arguments = {FastArray v0}} asa #Array} } }
サーバ(Java側)で作成されたデータクラスをクライアント(Curl側)で受け取って表示しています。サーバ側とクライアント側のデータクラスを同じ定義とすることで、JavaとCurlで異なる言語にも関わらず、それを意識する必要がありません。
Hinmk.curl、HinmkLst.curlの自動生成の詳細は、Curl Developer Centerのコード生成ツール・マニュアルを参照ください。
Developer/Curl+ORBでの実装時の違い
それでは、Developerでの実装時と、Curl+ORBでの実装時の差異を確認していきましょう。
Developerでの特徴
- サーバサイド、クライアントサイドを意識することが少ない
- 単一言語(PL/SQL)にて実装ができる
- DBアクセスレスポンスが良い
- 今回のサンプル程度であれば、項目定義、プロパティの設定のみで実装が可能である
Curl+ORBでの特徴
- サーバサイド、クライアントサイドを分割して実装できる。データクラスを定義していれば並行開発が可能
- サーバサイドは、一般的なJava+DBアクセスロジックにて実現でき、クライアントにCurl/ORBを使っていることを意識する必要がない
- Curl側のデータクラスはツールにて自動生成できるため、負荷が少ない
- 煩雑なサーバ/クライアント間のデータ通信部分のロジックを省略でき、Curl側へのデータ描画、Java側のデータ抽出作成 部分に注力できる
- ORBはオープンソースで公開されているため、自由なカスタマイズが可能
DBアクセスレスポンスについて
両者のアーキテクチャーが大きく違うため単純な比較はできませんが、DBアクセスレスポンスを比較しました。テーブル[HINMK_TBL]に1000件のデータを格納し、それを画面に表示するという速度検証を行いました。
結果は、Curl/Developer共に1秒以下で表示されます。体感はDeveloperが早いですが、1000件程度の表示であれば両者の速度に大差は感じられませんでした。
作業性について
今回の画面作成の作業性を比較してみます。Developerは、1人で作業し、1時間で作成できました。クライアント/サーバを意識する必要がない反面、分担して作業することができません。Curl+ORBは、サーバサイド/クライアントサイドで担当を分け、2人で作業しました。サーバサイド:30分、クライアントサイド:30分で作成できました。DeveloperもCurl+ORBも トータルでかかる作業時間は大差ありませんでした。Curl+ORBでは自動生成で作成する部分もあるので、”コーディング”する部分は局所的です。
APサーバの設定については、経験がないと苦戦するという点では、Developer/Curlも同じです。両者共に丁寧な解説サイトがありますので、それに従えば構築できます。Curlは、利用するサーバサービスに依存していないため、特段Curlに関する知識がなくても、一般的なWeb/APサーバを構築するための知識さえあればスムーズにサーバを構築できます。
開発ツールについて
Developerの開発は、Forms Builderという開発ツールを用います。項目を作成し、プロパティを設定していき、必要に応じてPL/SQLでロジックをコーディングしていきます。画面のエディタも配備しております。
Curlの開発は、CDE(Curl Development Tools for Eclipse)という開発ツールを用います。Curlのソースコードは、テキストベースですので、前述のコーディングレベルであれば、テキストエディタでも作成可能ですが、開発ツールを用いるとコード補完などコーディングする際に有用な機能が多数あり生産性が向上します。
また、Ver.8.0.0では便利な機能も多数盛り込まれています。詳細は、こちらをご覧ください。
バージョンアップについて
開発ツール/実行環境の宿命上、バージョンが存在します。旧バージョンの開発ツールで作成したプログラムを新バージョンにコンバージョンさせるシーンに良く出会うと思います。
Developerでは、古いバージョンのソースを新しいバージョンのソースにコンバージョンできます。やり方はいくつかありますが、基本的には古いバージョンで作成したソースを新しいバージョンのツールで開き、コンパイルして保存する、というやり方です。(もちろん、バージョン間で変更されたAPIに対する対応は必要です)
Curlの場合は、ヘラルドと呼ばれる部分に新しいバージョン番号を書きます。今回の例では、ヘラルドに{curl 6.0, 7.0, 8.0 applet}と記載しています。これは、Ver.6、7、8のCurl RTEで稼動することを意味しています。
例えば、このプログラムをVer.9に対応させたい(記載日時点でVer.9は存在しません)場合は、ヘラルドに
{curl 6.0, 7.0, 8.0, 9.0 applet}
と書きます。変更後のテストは必要ですが、基本的にはこれでVer.9のCurl RTEでも稼動します(Developerと同様に、バージョン間で変更されたAPIに対する対応は必要です)。
Developerと大きく違うのは、Curlの場合、{curl 6.0, 7.0, 8.0, 9.0 applet}と記載していれば、単一のソースプログラムでCurl RTE Ver.6.0/7.0/8.0/9.0で稼動させることができることです。個々のバージョンで稼動することを確認する必要がありますが、バージョンアップしたからといって、新バージョンでしか稼動しないということはなく、旧バージョンのCurl RTEで稼動させることも可能です。
本格的にCurlに移行したい場合
では、前ページまでで説明した、画面の作り替えだけでなく、本格的にDeveloperからCurlへと移行したい場合は、どのようにすればよいのでしょうか。
残念ながら、Developerで作成されたプログラムをCurlに移行するための専用のツールはありません。しかし、ちょっとした工夫で比較的簡単に移行できます。
Developer側のロジック
Developerのソースコードは、PL/SQLで作られているため、それらの業務ロジックをOracleのストアドプロシージャに移行させ、Javaにて移行したストアドプロシージャを呼び出すサービスクラスを作成し、Curlより作成したサービスクラスを呼び出す構成にします。こうすれば、スムーズに移行でき、既存資産も活かせます。
-
テキスト項目にコード入力確定時にコードに紐づく名称を表示(テキスト項目のWHEN-VALIDATE-ITEMにて商品マスタから名称を取得するSQL文を実行し、結果を名称項目に設定)
BEGIN //商品マスタから名称を取得する。 SELECT 商品名 INTO :検索条件.商品名 FROM 商品マスタ WHERE 商品コード = :検索条件.商品コード END;
-
検索ボタン押下時に在庫情報を取得し、表示(検索ボタンのWHEN-BUTTON-PRESSEDにてDBから各種情報を取得するためのSQL文(カーソル)を実行し取得結果を明細に設定)
DECLARE CURSOR C_SQL IS SELECT 倉庫コード,….. FROM 倉庫マスタ,…… WHERE 品目コード = …... BEGIN first_record; FOR r_SQL in C_SQL 受注 LOOP //明細行に設定 create_record; :明細.倉庫コード := r_SQL.倉庫コード; ………………………. END LOOP; END;
Curl+ORB側のロジック
-
Curl
APサーバ上のサービスクラスを呼び出し、その結果を該当の画面項目に設定
-
Java(APサーバ)
DBにてテキスト項目のWHEN-VALIDATE-ITEM、検索ボタンのWHEN-BUTTON-PRESSEDを実装したストアドプロシジャを作成し、Java(APサーバ)より実行、その戻り値をJavaのデータクラスに設定するようなサービスクラスを作成
-
DBサーバ
テキスト項目、ボタンに定義しているトリガーのロジックをストアドプロシジャ(※注1)に移行
DECLARE CURSOR C_SQL IS SELECT 倉庫コード,….. FROM 倉庫マスタ,…… WHERE 品目コード = …... BEGIN first_record; FOR r_SQL in C_SQL 受注 LOOP //明細行に設定 create_record; :明細.倉庫コード := r_SQL.倉庫コード; ………………………. END LOOP; END;
BEGIN //商品マスタから名称を取得する。 SELECT 商品名 INTO :検索条件.商品名 FROM 商品マスタ WHERE 商品コード = :検索条件.商品コード END;
※注1 ストアドプロシジャの定義・テキスト項目のWHEN-VALIDATE-ITEM
IN:商品コード
OUT:商品名
・検索ボタンのWHEN-BUTTON-PRESSED
IN:商品コード
OUT:明細行配列
を、NESTED TABLEにて定義します。
業務ロジック・データアクセス部分は全てDB層にて定義しており、APサーバ(Java)層は、ロジックを呼び出すだけのラップサービスです。3層構造ですが、APサーバの役割が小さくてすみます。
そのほかの注意点
Developerは元々C/S環境を源流とする開発ツールで、Curlは、当初より「エンタープライズRIA」に取り組んでいる言語です。C/Sシステムから本格的なWebシステムへ移行するにあたり、いくつか考慮すべきことがあります。
排他制御について
C/S環境のシステムでは、悲観的排他制御を行うケースが見受けられますが、一般的なWebシステムでは、楽観的排他制御を用いるのが常道です。
セッション管理について
C/S環境のシステムにおけるDBとのセッションは、1システム利用者=DBセッションとなることがほとんどかと思いますが、Webシステムにおけるセッションは、以下の2つが存在し、1Webクライアント≠DBセッションです。
- クライアント・Webサーバ間のセッション
- Web(AP)サーバとDBサーバとのセッション
C/S環境のシステムにて一時領域にデータを書き込む際にクライアントのキーとしてDBセッションを用いている場合などは、WebのセッションIDを書き込むように変更するなどの考慮が必要です。
参考情報
マニュアル(ヘルプ)
Curlヘルプドキュメントは、Curl言語の記述方法やAPIの解説方法のほか、各種ツールも紹介しています。このヘルプドキュメント自体も、CurlのAPIで構築されています。
特筆すべきは、Curlのソースコードをその場で書いて確認できる機能です。ヘルプに記載されているサンプルコードのほとんどは、その場で実行することができます。また、ヘルプドキュメント自体がCurlのエディタとなっており、その場でコードのカスタマイズを行うことも可能です。
-
Curlの開発者ガイドHTML版(上記の機能はありません)
Curl開発環境に付属しているCurlの開発者ガイドなどをhtmlに変換したページ
マニュアルは、体験版の開発ツールに同梱されています。ダウンロード先はこちらです。
困った際のトラブルシューティング
両者の言語ともに、サポートサービスは提供しているものの、Web上には一般的な知識として、Javaほどの情報量は出回っていません。Curlには、コミュニティサイトがあり、困った際はこちらに投稿することもできます。Curlを開発しているエンジニアが回答してくれることもあるため、非常に心強いです。
そのほかの公開サイト
-
Curl Apps Gallery
Curlで作られたサンプルデモプログラムをWeb上にて公開。Curlの技術を使用したアプリケーションの投稿や、投稿されたアプリケーションの検索/実行/ダウンロードが可能
-
Developer Center
Curl言語技術者向けサイト。Curlの逆引きリファレンス、Curlに関するよくある質問(FAQ)などの有用な情報を多数掲載
セミナー・トレーニング
-
Curl WBT(Web Based Training)
E-learningツールを使用してオンラインでトレーニングを公開。時間や場所に制限されず、利用者の都合に合わせてトレーニングを進めることが可能
-
各種セミナー・トレーニング情報
Curl導入検討から開発、運用に到る様々なフェーズをサポートする「Curl プロフェッショナルサービス」の情報はこちら