メタデータAPIでDBスキーマ作成

by Shinichi Tomita on 9月 5, 2007 at 06:19 午後

Summer'07より(Developer Editionのみのプレビューリリースですが)メタデータAPIが公開されました。Salesforceをデータベースになぞらえるなら、今まではDML(データ操作言語)だけであったのが、今度からはDDL(データ定義言語)相当の機能も追加されるようになったと思っていただければよいかもしれません。これによって、Salesforceのデータベーススキーマ(カスタムオブジェクト、カスタム項目)を操作する独自プログラムを作成することが可能になります。

メタデータAPIにアクセスするには、まずAPIの仕様を定義したWSDLファイルをダウンロードします。Developer Editionの管理ユーザアカウントでSalesforceにログインし、「設定>統合>Apex API」から、「Metadata WSDL のダウンロード」のリンクからダウンロードができます。なお、同時にPartner WSDL についてもダウンロードしておいてください。

WSDLを利用するため、今回はJava言語を利用してクライアントを書いてみます。事前にApache Axis 1.4をダウンロードしておいて、関連ライブラリをローカルファイルに保存しておきましょう。

まずAxisのWSDL2Javaを利用して、先ほどダウンロードしたWSDLファイルから、APIにアクセスするためのスタブソースファイルを生成します。<CLASSPATH>にはAxisのライブラリへのパスを含めて指定します。

java -classpath <CLASSPATH> org.apache.axis.wsdl.WSDL2Java -a metadata.wsdl

同様にして、Partner WSDLについてもスタブソースファイルを生成します。

java -classpath <CLASSPATH> org.apache.axis.wsdl.WSDL2Java -a partner.wsdl

生成されたクラスを利用することで、JavaのクライアントプログラムからメタデータAPIにアクセスすることができます。もちろん、メタデータAPIにアクセスするにはまずSalesforceから認証を受けなければいけません。ログイン認証は従来のAPIにアクセスする場合と同様に行います。

SforceServiceLocator sforceServiceLocator = new SforceServiceLocator();
SoapBindingStub sforce = (SoapBindingStub) sforceServiceLocator.getSoap();

LoginResult loginResult = sforce.login(username, password);

ログインに成功すると、認証済みセッションIDが取得できますので、この値をメタデータAPIのスタブにヘッダ情報としてセットします。これでメタデータAPIのメソッドを利用できるようになります。

MetadataServiceLocator metaServiceLocator = new MetadataServiceLocator();
meta = (MetadataBindingStub) metaServiceLocator.getMetadata();

SessionHeader sessionHeader = new SessionHeader();
sessionHeader.setSessionId(loginResult.getSessionId());
meta.setHeader(metaServiceLocator.getServiceName().getNamespaceURI(), "SessionHeader", sessionHeader);

Salesforceではカスタムオブジェクトがデータベースにおけるテーブル(表)に相当し、カスタム項目はカラム(列)に相当します。まず最初にカスタムオブジェクトを作成してみましょう。カスタムオブジェクトを作成するには、WSDLから生成されたクラスに含まれる CustomObject クラスをインスタンス化することで行います。カスタムオブジェクトに必要なプロパティをインスタンスにセットした後、レコード名を表示するためのカスタム項目を1つ作成し、NameFieldとして設定します。

CustomObject customObj = new CustomObject();
customObj.setFullName("MyCustomObject__c"); // 作成するオブジェクトのAPI参照名(__cを末尾に付加)を指定
customObj.setLabel("私のカスタムオブジェクト"); // 表示ラベル
customObj.setPluralLabel("私のカスタムオブジェクト"); // 複数形の表示ラベル
customObj.setDescription("このオブジェクトはメタデータAPIから作成されました"); // オブジェクトの説明
customObj.setEnableReports(true); // レポートの許可
customObj.setEnableActivities(true); // 活動の許可
customObj.setEnableHistory(true); // 項目履歴管理
customObj.setDeploymentStatus(DeploymentStatus.Deployed); //リリース状況
customObj.setSharingModel(SharingModel.ReadWrite); // デフォルトの共有設定

// レコード名の項目
CustomField nameField = new CustomField();
nameField.setFullName("MyCustomObject__c.Name"); // オブジェクトのAPI参照名 . 項目のAPI参照名
nameField.setType(FieldType.AutoNumber); // 項目のデータ型(レコード名項目の場合はTextあるいはAutoNumberのみ)
nameField.setDisplayFormat("{00000}"); // データ型がAutoNumber(自動採番)の場合は必ず指定
nameField.setLabel("レコード番号"); // 表示ラベル
nameField.setDescription("この項目はメタデータAPIから作成されました"); // 項目の説明

// カスタムオブジェクトのレコード名項目に設定
customObj.setNameField(nameField);

カスタムオブジェクトの作成要求を発行するには、MetadataBiding#create() メソッドを利用します。引数には作成するメタデータオブジェクト(カスタムオブジェクト、項目)を配列で指定します。

AsyncResult[] results = meta.create(new Metadata[] { customObj });

サーバ側で完全にデータ定義が終了するまでには時間がかかる場合があるため、APIのレスポンスはリクエストの終了を待たずに一旦非同期で返されます。このレスポンスにはリクエストの進行状況を問い合わせるためのIDが含まれています。データ定義が完了したことを保証するためには、与えられたIDを元にしてクライアントプログラムからサーバに対して定期的な問い合わせ(ポーリング)を行う必要があります。

カスタムオブジェクトの定義が完了したら、そのカスタムオブジェクトに対してカスタム項目を追加定義することができます。カスタム項目のデータ型としては、テキストや数値、日付、選択リストなども指定できますし、他オブジェクトへのリレーション(参照関係、主従関係)なども指定することができます。

// テキスト項目の作成
CustomField textField = new CustomField();
textField.setFullName("MyCustomObject__c.TextField__c"); // オブジェクトのAPI参照名 . 項目のAPI参照名
textField.setType(FieldType.Text); // データ型(テキスト)
textField.setLabel("テキスト項目"); // 表示ラベル
textField.setDescription("この項目はメタデータAPIから作成されました"); // 項目の説明

// 選択リスト項目の作成
CustomField picklistField = new CustomField();
picklistField.setFullName("MyCustomObject__c.PicklistField__c"); // オブジェクトのAPI参照名 . 項目のAPI参照名
picklistField.setType(FieldType.Picklist); // データ型(選択リスト)
picklistField.setLabel("選択リスト項目"); // 表示ラベル
picklistField.setDescription("この項目はメタデータAPIから作成されました"); // 項目の説明

Picklist picklist = new Picklist();
PicklistValue[] picklistValues = new PicklistValue[]{ new PicklistValue(), new PicklistValue() };
picklistValues[0].setLabel("男");
picklistValues[0].setValue("M");
picklistValues[1].setLabel("女");
picklistValues[1].setValue("F");
picklist.setPicklistValues(picklistValues);
picklistField.setPicklist(picklist); // 選択リストとして設定

// 参照項目(リレーション)の作成
CustomField lookupField = new CustomField();
lookupField.setFullName("MyCustomObject__c.AccountLookupField__c"); // オブジェクトのAPI参照名 . 項目のAPI参照名
lookupField.setType(FieldType.Lookup); // データ型(参照関係)
lookupField.setLabel("参照項目(取引先)"); // 表示ラベル
lookupField.setDescription("この項目はメタデータAPIから作成されました"); // 項目の説明

lookupField.setReferenceTo("Account"); // 参照するオブジェクトのAPI参照名
lookupField.setRelationshipName("MyCustomObjects"); // 子リレーションの関連名(取引先=>カスタムオブジェクト)

// カスタム項目の作成
AsyncResult[] results = meta.create(new Metadata[] { textField, picklistField, lookupField });

なお、1リクエストに指定できるメタデータオブジェクトの数はAPI仕様上は最高200個までとされていますが、少なくとも現在のところ5つほどにしておいたほうがよいようです。また、配列として同時に指定できるメタデータオブジェクトは同じ種類のものである必要があります(1リクエストの中にカスタムオブジェクトとカスタム項目を混在することはできない)。

メタデータAPIが利用できるのは現在のところDeveloper Editionのみですが、作成したメタデータオブジェクトをパッケージングしてAppExchangeにアップロードすることで他の組織に自由にコピーすることができます。そのため、実質どのSalesforce組織、Editionにおいても自由にデータベース・スキーマを作成することができます。

Apex API 10.0 Manual
http://www.salesforce.com/us/developer/docs/api/index.htm

実践SOQLで学ぶSalesforceデータベース

by Shinichi Tomita on 7月 12, 2007 at 11:53 午前

Salesforceのデータベースにアクセスするには、SOQLという独自のクエリ言語を用います。この文法自体はSQLによく似ているので、いままでデータベース開発に携わった方であればそれほど難しくないかもしれません。おそらく開発者の方が抱く一番の疑問点は「Salesforceのデータベースのどこにどんなデータが入るのか、そしてどのようにしてそのデータを引っ張ってくるのか」といったところではないかと思います。

Salesforceの標準データモデルについてはこちらのエントリでも簡単に解説しましたが、今回はもう少しユースケースを意識した実践的なデータクエリの例をいくつか挙げてみましょう。

ある取引先に所属している顧客名(取引先責任者)を一覧取得する

SELECT Id, FirstName, LastName, Account.Name
FROM Contact
WHERE Account.Name = '株式会社四川商会'

Contact(取引先責任者)オブジェクトに対しての比較的単純な問い合わせ文ですが、レコードの検索条件を親リレーション(Account)の属性値で指定してフィルタリングしているところが特徴です。

自分(ログインユーザ)が担当している商談と、その商談の更新履歴をすべて取得する

SELECT Id, Name, Account.Name, Amount,
       (SELECT Id, StageName, CreatedDate
        FROM OpportunityHistories
        ORDER BY CreatedDate DESC)
FROM Opportunity
WHERE OwnerId = '{現在ログインしているユーザのID}'

先ほどと同様に2つのオブジェクトにまたがった問い合わせです。前回が子供=>親への参照であったのに対して、今回は親=>子供のレコードへの関連を使用しています。

ここで、副問い合わせの形(括弧で囲まれたクエリ文)で指定されている中にある「OpportunityHistories」とは、Opportunity(商談)オブジェクトからOpportunityHistory (商談履歴) オブジェクトへのリレーションになっています。商談情報のレコードが更新されると自動的にこのオブジェクトに履歴が格納されるようになっています。履歴が時系列に並ぶように子オブジェクトに対してもORDER BY句でレコードを整列させしていることに注目してください。

Opportunity(商談)の問い合わせ条件として、OwnerId(所有者ID)がログインユーザIDと一致しているものに絞り込んでいます。現在ログインしているユーザのIDは getUserInfo APIメソッドで取得することができます。なお、Salesforceにおいてはレコードの所有ユーザは「担当者」という意味でよく使われます。

30日以上活動していない商談を一括取得する

SELECT Id, Name, Account.Name, Amount, LastActivityDate
FROM Opportunity
WHERE LastActivityDate = null
OR LastActivityDate < LAST_N_DAYS:30

検索条件として、LastActivityDate(最終活動日)という項目に対して「過去n日以内」という意味を表す LAST_N_DAYS:n というリテラル記述を使用しています。これにより問い合わせを実行した時点から30日以内に活動がない商談をすべて取得します。

SOQLではこの他にも日付を表すリテラル表記を定義しています。詳細についてはマニュアルをご参照ください

LastActivityDateは、そのレコードに関連付けられているToDoや行動といった活動情報のうち、最後に発生した活動の日時を表示する項目です。これは活動の記録をサポートしているオブジェクトに存在する標準項目です。Opportunity(商談)のほかにもAccount(取引先)やCase(ケース)オブジェクトに存在しています。

ある取引先に対して行った活動履歴、および活動予定を日時が近い順に5件ずつ取得

SELECT Id, Name,
       (SELECT Id, Subject, ActivityDate
        FROM ActivityHistories
        ORDER BY ActivityDate DESC, LastModifiedDate DESC
        LIMIT 5),
       (SELECT Id, Subject, ActivityDate
        FROM OpenActivities
        ORDER BY ActivityDate ASC, LastModifiedDate DESC
        LIMIT 5)
FROM Account
WHERE Id = '{取引先のID}';

Salesforceの標準オブジェクトの中でも「Event(行動)」および「Task(ToDo)」というオブジェクトは、少々特殊な扱いになっています。これらはまとめて「Activity(活動)」という単位で扱われ、さまざまなSalesforceオブジェクトに関連付けて用いることができます。上記のクエリでわかるように、関連付けられた活動情報は、もうすでに完了したものについては「ActivityHistories(活動履歴)」、および未だ完了していないものについては「OpenActivities」という関連をたどることで参照することができます。

なおTask(ToDo)オブジェクトに関してですが、日本語のラベル名は「ToDo」という名前になっているので、このオブジェクトが過去の活動記録の用途にも使われるというのはちょっと混乱してしまうかもしれません。実際の話Salesforceを使う場合に、ToDoを本当にToDoとして用いることはそれほど多くなく、むしろ送信したメールや電話の内容など「すでに行った活動のログ」を保存しておくのにToDoオブジェクトが用いられることのほうが多いくらいです。これは、日本語ラベルではなく英語名の「Task」という名前を考えていただければ、本来このオブジェクトが未来の活動予定のみでなく使われるのは納得いただけるでしょう。

ある部署の今月の売上げを営業マン別に集計して取得

SELECT Amount, Owner.Id, Owner.Name
FROM Opportunity
WHERE IsWon = true
AND CloseDate = THIS_MONTH
AND Owner.UserRole.Name = '営業2課'

これは、よく営業成績のレポートに用いられるタイプのデータクエリだと思います。IsWon(成立フラグ)が商談がまとまったことを示しているため、金額(Amount)を売り上げとしてみなすことができます。検索条件の中のOwnerに指定されているUserRoleという項目は、商談を所有している営業が所属している部署をあらわしています。UserRoleUserオブジェクトに対して1つのみとなり、その意味では「役割」というよりもむしろ「組織階層」を表現するの用いられるので、ちょっと注意が必要かもしれません。

SQLと異なり、現在SOQLでは集計などの集合関数が用意されていないため、そのままではランキングのような形式で利用することができません。そのため、このような場合はクエリを送信したプログラム側で集計作業を行う必要があります。以下にJavaScriptで書いた場合の集計の例を記載します。

var soql = "SELECT Amount, Owner.Id, Owner.Name "+
           " FROM Opportunity "+
           " WHERE IsWon = true "+
           " AND CloseDate = THIS_MONTH "+
           " AND Owner.UserRole.Name = '営業2課'";

var qr = sforce.connection.query(soql);

var tmp = {}; // 集計を行うための一時Hashオブジェクト
var iter = new sforce.QueryResultIterator(qr);
while (iter.hasNext()) {
  var opp = iter.next();
  if (!tmp[opp.Owner.Id]) {
    tmp[opp.Owner.Id] = {
      Name : opp.Owner.Name,
      TotalAmount : opp.Amount ? opp.getInt("Amount") : 0
    };
  } else {
    tmp[opp.Owner.Id].TotalAmount += opp.Amount ? opp.getInt("Amount") : 0;
  }
}
// 配列に変換
var ranking = [];
for (var id in tmp) ranking.push(tmp[id]);
// TotalAmountの降順でソート
ranking = ranking.sort(function(r1, r2) {
  return r1.TotalAmount < r2.TotalAmount ? 1 : -1;
});

なお、これらのクエリを試していただくにしても、Salesforceにデータが入っていなくてはあまり話になりません。実際に運用しているデータがあればベストでしょうが、すべての開発者の方がそのような環境にあるわけではないと思います。そのような方には、テスト用のデータをロードするアプリケーションをこちらから配布しています。ぜひ有効活用してください。

今回はSalesforceの標準オブジェクトのみにフォーカスしてSOQL文の例を挙げました。SOQLは独自に定義したテーブル(カスタムオブジェクト)に対してももちろん使えますが、こちらについては別の機会に詳しく説明できればと思います。

翔泳社DBマガジンでSalesforceプラットフォームが紹介されます

by Shinichi Tomita on 6月 20, 2007 at 06:42 午後

データベースの技術情報誌として有名なDBマガジン2007/08月号(翔泳社)で、Salesforceプラットフォームが紹介されます(6月23日発売予定)。本記事では、Salesforceを単なるSFAやCRMアプリケーションサービスとしてだけではなく、基盤サービスの面から切り込んで紹介しており、技術的な濃度の濃い内容になっています。

具体的にはSalesforceのプラットフォームアーキテクチャについて紹介した後、AJAX Toolkitを使ったAPIの利用方法から、簡単なアプリケーションのチュートリアルまで記載しています。

同号ではAjaxとデータベースのアプリケーション開発についての特集が別途組まれていますが、SalesforceのようにWeb経由でデータベースサービスを提供できるプラットフォームが、既存のインストールベースのデータベース開発に対してどれだけインパクトを与えることができるかについて考えてみるのもよいかもしれません。

DBマガジン2007/08月号

特集:JavaScriptのデバッグやJava連携も簡単になった Ajaxによる最新型DBアプリ開発
商品番号:810708
添付:CD-ROM 1点
サイズ:A4変
ページ:228
販売価格:\1,380(本体\1,314 消費税5%)
出版社:株式会社翔泳社


(追記)

なお以下は誌面のチュートリアルで利用した掲示板アプリケーションの完成版のURLです。開発用アカウントでアプリケーションをインストールして、Sコントロールのソースコードを参照してみてください。

https://www.salesforce.com/jp/appexchange/detail_overview.jsp?id=a03300000035ra1AAA

Salesforceのアプリケーション・データモデル

by Shinichi Tomita on 4月 25, 2007 at 12:54 午後

SalesforceのApexプラットフォームは汎用のデータベースサービスとしても利用できるのですが、Salesforceが標準で提供しているCRM/SFAアプリケーションと連動するアプリケーション開発を行うことによって、より強力なアプリケーションサービスを構築することも可能です。

ただ、このようなアプリケーションの開発のためには、標準で提供されるCRM/SFAアプリケーションが保有しているデータモデルについてある程度前提となる知識が必要になります。そのため、ここでは、Salesforceの営業支援アプリケーションが用意している標準オブジェクトのデータモデルについて、どのようなものがあるかについてご紹介します。

(※カッコ内はAPI参照名)

ユーザ(User)

Salesforce組織にアクセスするユーザの情報を管理しています。
一人一人にユーザ名とパスワードが与えられ、Salesforceにログインすることができます。
営業支援の用途としてSalesforceを使う場合は、自社の営業マンがこれに相当します。また管理ユーザの情報もこの中に登録されます。

SELECT Id, FirstName, LastName, Alias, Username FROM User

取引先(Account)

顧客企業/事業者の情報を格納するオブジェクトです。
取引先名、電話番号、住所(請求先/発送先)、従業員数などの企業情報を格納します。

SELECT Id, Name, Phone, NumberOfEmployees,
       BillingState, BillingCity, BillingStreet
FROM Account

取引先責任者(Contact)

取引先責任者は取引先に所属している個人をあらわします。
氏名、役職、電話番号、Eメールなどの個人情報を格納します。
取引先責任者には、所属している取引先の情報が親として関連付けられます。

子(Contact)から親(Account)への参照
SELECT LastName, FirstName, Email, 
       Account.Name, Account.Phone
FROM Contact
親(Account)から子(Contact)への参照
SELECT Name, Phone, 
       (SELECT Email FROM Contacts)
FROM Account

商談(Opportunity)

商談は現在進行中の(あるいは完了した)案件情報を格納するためのオブジェクトです。
現在のフェーズや商談全体の金額などの情報を指定できます。
商談に関連している取引先の情報も参照関係という形で指定できます。

子(Opportunity)から親(Account)への参照
SELECT Id, Name, StageName, 
       Account.Name, Account.Phone
FROM Opportunity
親(Account)から子(Opportunity)への参照
SELECT Id, Name, 
       (SELECT Name, StageName FROM Opportunities)
FROM Account

行動(Event)/ ToDo(Task)

行動(Event)は、カレンダー上のスケジュールをあらわすオブジェクトです。
またToDo(Task)は、今後遂行すべき業務内容(ToDo)の情報を格納するオブジェクトですが、そのほかにもすでに終了した活動情報を履歴として格納することがあります。

行動およびToDoといった活動情報には、その活動に関係する取引先責任者/リード(Who)の情報や、商談/取引先(What)の情報を関連づけておくことができます。

SELECT Id, Subject, ActivityDateTime, DurationInMinutes,
       Who.Id, Who.Type, Who.Name,
       What.Id, What.Type, What.Name
FROM Event

なお、上記すべてのオブジェクトについて、レコードの所有者(Owner)および作成者(CreatedBy)の情報としてユーザオブジェクトが自動的に関連付けられます。

SELECT Id, Name, CreatedBy.Name, Owner.Name FROM Account

その他にも、マーケティング用途としてリード(Lead)、キャンペーン(Campaign)、サービス&サポート用途としてケース(Case)およびソリューション(Solution)などのオブジェクトが定義されています。以下の図はSalesforceの標準オブジェクトの関係を示したER図です

これらのデータモデルの詳細については、Apex APIマニュアルを参照するか、あるいはdescribeSObject APIメソッドを利用することで詳細情報を確認できます。