« 2014年4月 | メイン | 2014年6月 »

2014年5月

2014年5月29日 (木)

Reveal.jsとRemotes.ioのコンボがいけてる話

ども、小西です。
社内勉強会向けにGrunt超入門というスライドを作りました。

http://shunjikonishi.github.io/slides/grunt-introduction/index.html

URLを見るとわかると思いますがスライドの公開先はGitHub Pagesです。
スライドの中身のGruntについては既に良記事もたくさんあって、今さら感もあるので割とどうでも良いんですが、今回紹介したいのはこれを作成するのに使ったReveal.jsというWebプレゼンライブラリについてです。

これ、「パワポなんか頼まれたって触ってやらねー!」というツワモノの同僚が見つけて作成したものをパクってマークダウンだけ書き換えて作ったんですが、これがなかなかいけてます。(^^v


★ Reveal.js

僕は知りませんでしたが割と有名なライブラリのようです。
日本語でも紹介記事がけっこう見つかりますが、やっぱり一番わかりやすいのは本家のWebサイトでしょう。

http://lab.hakim.se/reveal-js/#/

Reveal.jsの紹介自体がReveal.jsで作られたスライドで紹介されています。
これ見ればだいたい何ができるかはわかると思います。

個人的に良いと思うのは

  • マークダウンだけでスライドが作れる
  • HTMLやJavaScriptとの組み合わせも可能
  • スタイルやレイアウトはCSSで調整可能
  • jsオンリーなのでGitHub.ioで公開可能
  • ローカルではGruntで起動するnode.jsのサーバでライブリロードで動作確認できる
  • PC上のスライドをスマホで操作できる


というあたりです。
一番驚いたのは最後のスマホ連携で、これは最近すっかりおなじみのWebSocketですよ。

PCでスライドを表示すると画面右にちょっと邪魔な感じで電源マークみたいなのが表示されていると思いますが、ここをマウスオーバーするとQRコードが表示されそれをスマホで読みこむとリモートにあるWebSocketサーバを介してスマホとPCが接続され、スマホでのスワイプ操作によってスライドのページ送りができるという仕組みです。

なるほど。WebSocketにはこういう使い方もあるわけですね。今ならWebRTCでもできそうですけど、これはこれで面白い使い方だと思います。

で、それ以上に面白いと思ったのはこの機能がReveal.js単体で実現されているのではなく別サービスとの組み合わせで実現されているという点です。


★ Remotes.io

その組み合わせとして使われているサービスがRemotes.ioです。

http://remotes.io/

これはWebSocketで接続されているホストに対してスマホで発生したイベント(右スワイプとかタップとか)を通知するだけのサービスです。

Remotes.ioを利用するサイトはまずRemotes.ioが提供するJavaScriptをページにインクルードします。

そうすると先に見たようなQRコードが画面に組み込まれます。
そこにスマホが接続されるとRemotes.ioによってスマホ側で発生したイベントが通知されるので後はサイト側でそれらのイベントに対応するイベントハンドラを書けばOKです。(Reveal.jsではスワイプイベントをスライドのページ送りに対応させています。)
QRコードはスマホとの接続が確立されると画面から消えるので複数のスマホが1台のPCに接続されることはありません。


いやー、これは本当に驚いたアイデアです。
WebSocket、使い方次第で色々と面白いことができそう!ということを最近色々なところで主張しているんですがその可能性を示してくれる事例だと思います。(^^v

2014年5月23日 (金)

Heroku Meetup #12

行ってきました。
ていうか、LTしてきました。(^^;

今回は同僚の@i556の発表があったり、がちゃぴん先生(@kosaki55tea)の濃ゆいカーネルの話があったりで、非常に面白かったです。


★ WordPress On Heroku

http://www.slideshare.net/kokorojw/wordpress-on-heroku

いや思った以上に良く調べていたんでびっくりした。(^^;
普段結構とっぴょうしもないことを言ってることが多いので、どんな発表になるのかと思ってたけど、資料も過不足なくわかりやすくまとまっていて良い感じ。

そういえば前に社内で発表してたデザインの話にもとても感心したことを思い出し、実はできる子です。> @i556(^^;

WordPress使ったことがないのでHerokuでWordPressが動くことのインパクトがどれくらいあるのか正直よくわからないんだけど、やりたい人がたくさんいるのであればもうこの際、

WordPress込みでbuildpack作っちゃえば良いんじゃね?

とちょっと思いました。
ClearDBやCloudinaryの無償版をbuildpackで追加することはできるはずだし、DBのURLをファイルに転記することもbuildpack内でできます。(Heroku的にはPostgresを使って欲しいだろうけど。)

ID/Passwordはとりあえず環境変数に自動生成したものを書きだしておいて、設定ファイルでそれを見るようにするか、あるいは「.profile.d」にログインスクリプトを用意してDyno起動時に設定ファイルを書き換えるという方法でもできそうです。

WordPress本体はgitでバージョン管理するようなものではないだろうし、これができれば一度もgit pushしないでWordPressを動かすことができる気がするんだけどできないかな?(^^;

(。。。と書いてるそばから気が付いたけど一度もgit pushしないとSlugコンパイル自体が走らない気がする。PlatformAPIのSlug関連のAPIをbuildpackから叩くことができればなんとかなるかもしれないけど、ちょっと難しいかも。。。)

しかしまぁ少なくとも手順を大幅に簡略化することはできるはず。
気が向いたら試してみれ。 > @i556(^^;


★ Linux Container Update

http://www.slideshare.net/kosaki55tea/linux-container-update

正直Kernelの話とか完全に守備範囲外なわけですが、お話とても面白かったです。(というか、わかるように話をしてくれていたと思う。)

レイヤが違ってもやってることも考え方もあんまり変わらないですね。ただ考えなきゃいけないことの種類が全く違うだけで。

その中で一番興味を惹かれたのはやっぱりLive Migrationの話コンテナプロセスを丸ごとコピーして他のホストで復活させる。マジでか!!!

個人的には本当にちゃんと動くの?というところかなり懐疑的だけど理屈はわかります。

死にそうなプロセスをコピーしても、コピーした先でやっぱり死ぬだけなんじゃないの?と思って先生に質問したところそれはやっぱりその通り、とのこと。

使いどころとしては1ホスト内で複数のコンテナをあげる運用している事業者が、なんらかの閾値を決めてホストを監視して、閾値を越えて動作がやばそうになったコンテナがあったら他のまっとうなコンテナをLive Migrationする、みたいな感じになるんだと思う。

。。。それは凄いな!!!
この技術が浸透して当たり前のものになると、ハードの寿命を越えて永遠に動き続けるプロセスを作ることも不可能ではないってことか!(実際にはその間にOSのアップデートなんかがあって現実的ではないだろうけど。)

非常に先行きが楽しみな技術です。(^^


★ My LT: 一番簡単なWebSocketの試し方

http://www.slideshare.net/shunjikonishi/websocket-34998961

多くは語るまい。
6/9のSalesforce1 Develper Community MAXに話を聞きにきてくださいっちゅーことですよ。(^^;


2014年5月16日 (金)

APIサーバでCORSを使う

昨日はSODECに行ってきました。

最近はこうした展示会でこれはスゲー!という衝撃をうけることは少ないんですが、プロダクトを見る時に「どういう風に作ってるんだろうなぁ」ということを想像しながら眺めているせいか、ちょいちょい今まで考えもしなかったようなことを思いつきます。(毎回そうなんですが思いつく内容が特定のプロダクトからの直接の連想でないところが不思議なところです。ほとんどの場合帰り道や寝る直前などにふと降りてきます。(^^;)

今回の話はその思いつきの内容のメモです。

あ、あと知人の吉田さんに著書をいただきました。

帰りに1章だけ読みましたが、自分(開発者)とは違う視点からセキュリティを俯瞰している印象でなかなか興味深いです。

技術者だけでなく経営層の人も読んでみると良いかも。(ステマ)

 

★ CORSとは

Cross Orign Resource Sharingの略でその名の通り異なるドメインでリソースを共有するための仕組みです。

JavaScriptにはSame Origin Policyがあるので通常は異なるドメインのREST APIをAjaxでたたくことはできませんが、CORSを使うとそれが可能になります。

資料としてはこれがわかりやすいです。

https://developer.mozilla.org/ja/docs/HTTP_access_control

一番有名な実装例はおそらくAmazon S3で、CORSを利用することでブラウザからのファイルのS3への直接アップロードを可能にしています。

 

★ CORSのサーバサイド実装

上記リンク先を見ると実装に必要なことはだいたいわかるんですが、大まかにまとめると以下のようになります。

  • HttpResponseのAccess-Control-Allow-Originでアクセスを許可するOriginを指定する
  • 同じくAccess-Control-Allow-Methodsで許可するメソッドを指定する
  • プリフライトリクエストに備えてHTTPメソッドのOPTIONSに対応する
  • Cookieを使用する場合はAccess-Control-Allow-Credentials: true をつける

ほとんどHttpヘッダの制御だけなのでたいていのフレームワークではハンドル可能です。唯一OPTIONSへの対応だけが不安でしたが、とりあえずPlayframeworkではroutesに普通に「OPTIONS /hogehoge ...」を定義するだけでいけました。

クライアント側はブラウザがよしなにやってくれるので基本的には自分で実装すべきものはありません。(というかHttpClient自体を自前で用意するのであればそもそもSameOriginPolicyの制限を受けないので何もする必要がありません。)

唯一Cookieを利用する場合は自分でxhr#withCredentialsをtrueにする必要があります。jQueryだとやり方にすこし癖がありますがこの辺を見ればすぐにできると思います。

 

★CORSをAPIサーバで使うことを考える

さて、このCORS。僕はS3の事例でしか使ったことがなかったので静的に設定するモノという先入観があったんですが、よくよく考えるとHttpヘッダだけの対応なので動的に変更することが可能です。

例えばユーザ毎に固有のURLを用意するようなサービスの場合、URL毎にAccess-Control-Allow-Originを変更することは楽勝でできますし、認証後にすべてのユーザに対して同一のURLでAPIを提供するようなサービスであってもCookie併用で切り替えることができるはずです。

これで指定のドメイン以外からのリクエストをはじくことができますが、curlやChromeのコンソールなどからでも簡単に偽装リクエストを送ることはできるのでセキュリティ要件は別途考えなければなりません

あとは、ブラウザからの直接アクセスをはじくためにOriginヘッダのないリクエストははじくなどの対応を入れても良いでしょう。

これらができると、今まではSameOriginPolicy回避のためにサーバから実行するしかなかった外部APIの実行がブラウザ上から直接できるようになるので、アプリの可能性は大きく広がりそうな気がします。

 

★ 既存サービスの実装状況など

自分に思いつく程度のことだから既存のサービスではもう結構実装されてるんじゃないの?とおもってざっと検索してみましたが、あんまりそういうサービスない(追記)みたいですね。

Salesforceで検索してみたら3年も前によく見知った人が対応しろや!と声をあげていて流石と思いましたが、未だ対応されていないようです。残念。。。

その中で異彩をはなっているのは我らがHerokuです。

https://devcenter.heroku.com/articles/platform-api-reference#cors

え、「*」で許可してるってこと???

それは流石にやりすぎなんじゃ。。。(--

つーことは、つまりHeroku DashboardはGitHub.ioでも作れるってことですね。(^^;

(追記)

文中のよく見知った人(冨田さん)から、Google+ API, Facebook Graph API, GitHub APIがCORSに対応していることを教えていただきました。ありがとうございます。

これらも「*」で許可しているみたいですね。先行して認証があるAPIの場合はそれもアリな選択肢ということなんでしょう。

2014年5月14日 (水)

Heroku Connect来ました

Heroku ConnectがGAになったようです。

https://blog.heroku.com/archives/2014/5/13/introducing_heroku_connect

と言っても現状では使うためには直接コンタクトをとる必要があるようです。
以下、ざっとドキュメントを読んだまとめと感想です。節立てはドキュメントを踏襲していますが、個人的に引っ掛かったところを中心にまとめているだけで決して翻訳ではないので、ん?と思った方は原典をあたってください。(^^;


ちなみに1節読む毎に書いていたので先に書いた疑問が後で自己解決していることもあります。具体的にはINSERTに関する推測が最後に全部ひっくり返ります。直そうかとも思ったんですが、文章の流れもあるのでそのままにしました。


★ How Heroku Connect works

  • Heroku ConnectはSalesforceオブジェクトのレプリカをPostgreSQL上に作成するもの
  • テーブル名、列名はSalesforceと同じになる(ただし小文字)
  • Salesforce、DBの変更が双方向に同期する
  • SF -> DBの同期には「LastModifiedDate」列を利用する
  • DB -> SFの同期にはDatabaseのトリガーを利用する
  • DBの変更の反映はUserPermissionsとValidationRulesの影響を受ける
  • DBの変更は変更されたカラムのみが更新対象となる


これを読む限りSalesforce側になんらかのこのためのオブジェクトが仕込まれたということはなく、完全に外部からAPIの制御だけで作られているように見えます。

スキーマは常にSalesforceと完全に同じになるのかな?
そうだとすると以下の点が気になります。

  • 必要な列のみをDBに持たせることはできないのか?
  • スキーマの変更にどのように対応するのか?(最低限列の追加には対応してほしい)
  • ID列の扱いは?これがそのまま主キーになるの?


特に最後のID列の扱いは気になるところで、実はDB側ではUPDATEはできてもINSERTはできないんじゃないの?という気がします。(IDが生成できないので)
あと、DELETEの扱いはどうなるのかというのも気になるところです。

INSERTが扱えないんじゃないかと思う根拠はもう一個あって、以前にDB -> SFの片方向同期を自前で実装した時に参照(親子)関係の扱いがけっこう面倒だった記憶があるからです。

必ず親を先に同期するとか、外部キーが必要などいくつか注意すべき点があるんですが、その辺が考慮されているとはここからは読み取れません。


★ Synchronization frequency

  • デフォルトではSFの変更検知のポーリングは10分単位
  • ただしHeroku ConnectのUIを開いている間は3分になる
  • デフォルトではDBの変更検知のポーリングは5分単位
  • ただしHeroku ConnectのUIを開いている間は1分になる
  • Fast Synchモードにした場合更新頻度は30秒に一回になるがその分APIを消費する(Developer Editionの場合はほぼ間違いなくAPIを使いきる)


DBトリガーを使っているにも関わらずポーリングもあるということは、トリガーで行っているのは管理用の別テーブルへのINSERTだけでそれをまとめて更新しているってことでしょうね。5分の間に2回更新が発生したら2回のUPDATEが実行されるんですかね?(それで問題ないと思いますけど)


★ Heroku Connect and Salesforce API consumption

  • 同期オブジェクト1つにつき約500のAPIを消費する
  • リードオンリーモードの場合はAPIの消費はもう少し少ない
  • APIは更新ボリュームによってSOAPとBulkを自動的に使い分ける
  • DB -> SFで1度に更新できるのは200レコードなので10000レコードの更新は50APIを消費する
  • 初回の同期(全データコピー)は500万レコードで120分程度かかる(外部IDがない場合)


残念ながらAPIの優遇は一切ないようです。。。(--
1度に200件というのもSOAP APIの制限そのままですし。

初回120分というのはBulkを使ってもこれ位ってことですかね。(SOAPだとどう考えてももっと遅そうだし、Bulkを使わない理由がない)
スループットにすると694件/秒なのでそんなもんかとも思いますが、速いCPUのDBを使っていればもうちょっと速いんじゃないかとも思います。

外部IDの有無で性能が変わるというのはDB側でもForeign keyをちゃんと定義しているということでしょう。


★ How Salesforce objects map to Postgres database tables

  • セットアップ時にオブジェクトに対応するテーブルが自動で作られる
  • テーブル名、列名はSalesforceオブジェクトと同じ(ただし小文字)
  • DBの主キーはAuto Increment
  • SFのIDは「sfid」という列となりUnique Indexが付与される


先に書いたIDに対する疑問の回答が書かれていました。
これを読む限りやっぱりINSERTはできそうにない気がします。


★ Provisioning the add-on

  • Heroku Connectはアプリと同じリージョン(US or EU)に配置される
  • アドオンを追加したら最初にWebUIでセットアップをする
  • 設定項目は対象DB、使用するDBのスキーマ名、SF認証情報など
  • SF認証に使用するユーザーは十分な権限を持っている必要があるので連携専用のユーザーを用意することが望ましい。


ブログアナウンスでは使いたい人は直接連絡しろとありましたが、CLIのheroku addons:addで追加できるみたいですね。(試してませんが。)
日本でSalesforceを使う場合は当然日本に置くので、Herokuもとっとと日本リージョンを持ってこいっちゅー話です。

関係ないですけどCLIからアドオンの管理画面をコマンドで開けるということを初めて知りました。


heroku addons:open papertrail -a xxxx


地味に便利です。


★ Mapping objects

  • WebUIで同期するオブジェクトの設定ができる
  • SFユーザに「View All Data」と「API Enabled」の権限が必要
  • あとは一覧からポイント&クリックで設定できる
  • 同期の状態はWebUIで確認できる


どうやら同期する列も選択できるらしい。

WebUIがどの程度の情報をサポートしているのかは気になるところ。


★ Resolving read errors

認証に使用するSFユーザに十分な権限がなかったり、オブジェクトに読み取り制限がかかっている場合は同期が正しく実行できないという話


★ Accessing the synchronized tables

DB側でのデータへのアクセス方法。普通にログインしてSELECTするだけ。スキーマがデフォルトでは「salesforce」になっているのでpsqlを使う場合はsearch_pathにsalesforceを加えると良い。


★ Updating data in Salesforce

  • デフォルトはReadOnlyモードなのでDBの更新を反映するにはRead/Writeモードに変更する
  • DBの更新は<テーブル名>_trigger_logsというテーブルに記録され、それを元に更新が行われる
  • DB -> SFの更新結果は「SUCCESSFUL」「IGNORED」「FAILED」のいずれか
  • SUCCESSFULのデータはtrigger_logsから削除されるが、それ以外は残る


予想通り、DBトリガで別テーブルにINSERTしているだけのようです。
IGNOREDっていうのはどういう時に発生するんでしょうね?また、SUCCESSFUL以外のレコードは残り続けるとありますが、それがいつ消えるのかとかリトライはされないのかというのも気になります。


★ Inserting records to Salesforce

なんと!ここまでの予想を覆してDBへのInsertは可能だそうです。
どうやって実現しているかというと単純にsfid列はNULLABLEなのでそれは無視して、必要な列のみでINSERTするだけ。
あとは同期のタイミングでsfid列が自動で設定されるようです。
ちなみにINSERT後に同期が走る前にUPDATEやDELETEが行われた場合も、それらはちゃんとマージされる(DELETEの場合は何もなかったことになる)ようです。
master/detail関係のレコードも外部IDが設定されていればちゃんと反映されるとのこと。

先に書いた参照関係に対する懸念についても丁寧に説明されています。
ここに来てHeroku Connectへの評価は10段階位跳ね上がりました。(^^v


★ Using External IDs to manage relationships

AccountとContactを例に参照関係の解決のための外部IDの設定方法が書かれています。
要するにAccount側に外部ID列を追加しておけば、同期時にそれを利用することができるってことですね。

若干冗長ですが、これは仕方ないかな。。。


★ Resolving write errors

認証ユーザに十分な権限がなかったり、SFオブジェクトのValidation Rulesに引っ掛かった場合にはエラーとなる。
あるいは計算フィールドも同期可能だが、そこへの書き込みはエラーとなる。

これらのエラーはDBに記録されWebUIから確認できる。


★ Schema changes

スキーマ変更は問題なし。
列の追加はHeroku Connectの動作に何の影響もなく、その列を同期したいならマッピングを追加すれば良い。既存行の値はその時に同期される。

列の削除や変更では同期がエラーになるのでマッピングの変更が必要


★ Pausing Heroku Connect

WebUIからHeroku Connectの同期を一時的に停止できる。

オブジェクト単位ではできなくて、全体での停止しかできないらしい。


★ Import and export of configuration files

yaml形式でマッピングのインポート/エクスポートができる。
Sandboxで定義したマッピングを本番に移行する場合などに有用


★ Notifications

エラーや同期処理が60秒以内に終了しない場合などにはオーナーに対してメール通知が飛ぶ


★ Security

Heroku ConnectはSalesforceのOAuthトークンを保存している。
データ転送はすべてSSL。

ふと気になったけど、Heroku ConnectのIPは固定されるんですかね?
Salesforce側でAPIのIP制限がかかっている場合の対処方法があるのかどうかはちょっと気になるところです。


★ Using Heroku Connect and the demo Rails app

Heroku Connectを使ったRailsのサンプルアプリがあるのでまずは試してみるが良い。
このサンプルアプリではAccoutとContactを同期する
セキュリティについては特に考慮されてないので間違ってもこのデモアプリを本番環境のSalesforceで動かしてはいけない


★ Removing Heroku Connect from your Heroku app

WebUIからもCLIからも削除可能


★ FAQ
★ Support

省略。
なんかリトライする場合はtrigger_logsテーブルを直接UPDATEすれば良い、みたいなことが書かれていてなかなか衝撃的(^^;


□□□□
以上、同期処理自体はほとんど自力でやった時と同じようなものですが、WebUIで同期状況が継続的に監視できるのは良いですね。
どうでも良いけど料金体系がどこにも記載されてないような。。。

2014年5月 8日 (木)

GitHub pagesの運用を考える

なんか色々手を広げすぎてて自分の首を絞めている。。。(--
WebSocket関連だけでも書きたいことはわりといっぱいあるけど、なかなか手が回らない。。。
なのに今回も脱線ネタ(^^;

GitHub pages

ちょっとしたオープンソースのプロジェクトを作るのであれば、ソースはGitHub、ホームページはGitHub pagesで作成するのが一番お手軽だと思う。

GitHub pagesとはGitHub上で「gh-pages」というブランチを作成するとその内容をWebページとして公開できる機能のこと。機能自体の説明はかなり今更なので知らない人はググってくだされ。(^^;

さて、このGitHub pages。実体としては単なるGitブランチなので、素直に使おうとするとプロジェクトのルートで「git checkout gh-pages」としてブランチを切り替えながら管理することになる。

が、プロジェクトのソースとGitHub pagesで公開したいWebコンテンツはまるっきり異なるので、checkoutの度にディレクトリ構造がごっそりと入れ替わるのはできればさけたい。

個人的にはこのあたりの管理要件として

  • ソースとGitHub pagesのWebコンテンツを同一ディレクトリ内で管理したい
  • ソースとGitHub pagesの内容をcheckoutで切り替えたくない

という二つがあったのでこのあたりの記事を参考にしながら、それを可能とする方法を考えてみた。

手順

以下の手順はGitHub上で既にソースコードが管理されている前提からスタートしています。

1. GitHub上でgh-pagesをジェネる

gh-pagesのコンテンツはもちろん、自分で一から作っても良いのだが、久々にGitHubのジェネレーターを使ったらいい感じのテンプレートがかなりたくさん揃っていたので、よっぽどデザインにこだわりのある人以外はもうこれで良いんじゃないかと思う。(^^;
あとからカスタマイズするのにも特に不都合はないし。

これだけでgh-pagesブランチが作成されて、そこにコンテンツがコミットされます。

2. プロジェクトルートに「gh-pages」というディレクトリを作成し、そのディレクトリを.gitignoreに追加

gh-pagesの管理用のディレクトリを作成し、そのディレクトリを.gitignoreに追加して管理対象外にします。

3. gh-pagesディレクトリに移動してGitHubからgh-pagesブランチの内容を取得

具体的なコマンドは以下のような感じ

cd gh-pages
git init
git remote add origin git@github.com:xxxx/yyyy.git
git checkout -b gh-pages
git pull origin gh-pages
git branch -d master

ようするにgh-pagesディレクトリをgh-pagesブランチ専用のディレクトリにしちゃう。
masterブランチは特に必要ないので削除する。

以降はプロジェクトルートとgh-pagesを移動しながら、普段と同じ感覚のgit操作で両方を管理できる。
(「git config --global push.default matching」を設定しておけばブランチ名を省略できるのでますます便利)

他にもっと良い方法がありそうな気もするけど、本気でググると見たことないgitコマンドがいっぱいでてきて憂鬱になったので、手持ちの技だけで対処してみた。(^^;
とりあえずは満足(^^v

採用情報

株式会社フレクトでは、事業拡大のため、
Salesforce/Force.comのアプリケーション
開発
HerokuやAWSなどのクラウドプラッ
トフォーム上でのWebアプリケーション開発

エンジニア、マネージャーを募集中です。

未経験でも、これからクラウドをやってみた
い方、是非ご応募下さい。

フレクト採用ページへ

会社紹介

株式会社フレクトは、
認定コンサルタント
認定上級デベロッパー
認定デベロッパー
が在籍している、
セールスフォースパートナーです。
heroku partnersにも登録されています。
herokuパートナー
株式会社フレクトのSalesforce/Force.com
導入支援サービス
弊社の認定プロフェッショナルが支援致します。
・Visualforce/Apexによるアプリ開発
・Salesforceと連携するWebアプリ開発
も承っております。
セールスフォースご検討の際は、
お気軽にお問合せください。
Powered by Six Apart