継続的なアウトプットのために、ひとりAdvent Calendarを目論んだが、10/25でFinishした

一度は「ひとりAdvent Calendar」をやってみたいなーと思っていて、密かな目標として12月は25日まで毎日更新を目論んでいたのですが、半分にも満たない10日でFinishしました。
予定外に風邪をひいたりしてしまったことも多少は影響していますが、いやはや、完走できる人はすごいですね。

日付 記事
12/1 SOAP API(wsc)で地理位置情報型や時間型を扱う #salesforce
12/2 AWS CodeBuildでいつの間にかDependency Cacheがサポートされてた
12/4 ゼロからはじめるServerless Java Container (Qiita)
12/6 Salesforce開発デザインパターン集 #salesforce
12/9 AWS Lambdaでストリームベースのイベントソースのリトライ回数を有限にしてDLQを使う (Qiita)
12/12 $A.createComponentsでちょっと複雑なコンポーネントを動的に作成してみる #salesforce
12/13 Lightning Experience流のUIを作る4つの方法と比較 #salesforce
12/14 Lightning基本コンポーネントのライブエディタを作ってみる #salesforce
12/15 sfdxのカスタムプラグインを作ってみる #salesforce
12/20 古いDev Hub Trial Orgを消す #salesforce

Salesforceネタが7つ、AWSネタが3つでした。
本分はJava屋(だと思っている)のですが、今年はあまり新しい刺激が(業務においては)少なく、守りの年でした。

あと、ブログに感想は書けていませんが、12月は以下の勉強会に参加していました。

継続的なアウトプットのために

さて、日頃からインプットとアウトプットのバランスをとるように心がけているのですが、相変わらずアウトプット(ここでのアウトプットはブログを書くことに限定します)はムラが多すぎるなーと反省。。

目標の25日には届きませんでしたが、継続的にアウトプットをするための気構えとして感じたことを書き留めておきます。

1記事は大作でなくてもよい

Advent Calendarにエントリするのは今年で4回目でしたが、今までで一番リラックスして書くことができました。こう言ってしまうとなんですが、記事を書くための仕込みの時間も一番短かったと思います。

なんとなくAdvent Calendarというと、大作(=分量が多い・技術的に高度・ネタ色が強い)でないとダメ、みたいに感じていましたが、そんなことはないのかなと思うようになりました。テーマに沿っており、将来の自分や誰かの役に立つネタであれば、極論、一文・一行でもいいのかなと思います。これはAdvent Calendarにかぎらず、普段のブログでも同じでもいえると思います。
そもそも、短いほうが読者もすぐに読めますし…。

というわけで、もっと気軽に、自分の日頃の気づきをアウトプットする場として、来年はブログを使っていきたいです。

事前に準備しても熱は冷めてる

一応、ひとりAdvent Calendarということで、始まる前に20日分くらいのネタ(タイトルだけ)は考えたりしていました。
ただ、その当時は「××のことを調べてxxの記事に書くぞ!」と思っていても、時間が経ってしまうと、その熱が冷めていて、結局調べもせず、書きもしないことがあるんですよね…。

なので自分は、書くと決めたらその日に(多少粗くても)書き切って投稿する、というスタイルが合っているように思いました。

ちなみに、日の目を見なかったネタについては、来年どこかで記事にしたいとは思っていますが…。

アクセス数は気にしない

「反応があると嬉しい」ということは否定しませんが、アクセス数を稼ぐことが目的化してしまうと本末転倒であり、そもそもこのブログ、基本的に検索しかアクセスがないので、「自分のため」と割り切って書くのが長続きするコツかなと思います。
もうちょっと綺麗事を言うと、「将来の自分や誰かの役に立つネタ」を提供したい、というのがブログを書くモチベーションの源泉ですが…。

毎月xx回以上書く、毎週xx回以上書く、みたいな目標は決めない

定量的な回数を決めてしまうと、それ以上書こうという気が薄れるし、逆に目標が高すぎてもモチベーションが下がるし、目標を決めないほうが性にあっているような気がしました。これは人によるかもしれません。コンスタントに素晴らしい記事を書いている人も世の中にはたくさんいますし。

sfdxのカスタムプラグインを作ってみる #salesforce

sfdxにも少しずつ慣れていきたいなと思い、色々試してみようとしているところです。

手始めに

$ sfdx help

とヘルプコマンドを打ち、そこで初めて、sfdxはプラグイン機構を備えていることを知りました。

f:id:jappy:20171215004240p:plain

sfdxのカスタムプラグインはどんな感じに作られるのか、試してみました。

作ったもの

https://github.com/tq-jappy/sfdx-hello-plugin

$ sfdx hello:sayHello

と打つと、

Hello sfdx!

とコンソールに出力するだけのものです。

作り方を学ぶ

これまで、趣味と実益を兼ねて、RedmineやJenkinsのプラグインの作成(や改修)に関わってきましたが、プラグインの作り方を学ぶには、似たような動作をする既存のプラグインを真似するのが一番の近道だと思っています。

とはいえ、sfdxの場合、まだそんなに数が豊富にあるわけではないので、「そもそもどうやって作ればいいんだろう?」と思って検索したところ、例えば以下のようなリポジトリが見つかりました。

github.com

あと、作者の方の紹介記事もありました。

www.wadewegner.com

これらを踏まえて、作りました。

作り方まとめ

基本的には、sfdxのプラグインは普通のNode.js開発の要領で作っていけばよいみたいですね。

エントリポイント(メイン) である index.js の中で、 topics, namespace, commands をそれぞれ exports してあげるのがルールなようです。
ただ、コマンド体系が複雑でなければ、namespaceは省略してもよいみたいです。プラグインの作法としてはさておき、動作上は問題ありませんでした。

コマンドのロジックは以下のコードです。 context の中には認証情報なども含まれていそうなので、うまく駆使すれば色んな用途を満たすプラグインが作れるんじゃないかなと。

(function() {
  'use strict';

  module.exports = {
    topic: 'hello',
    command: 'sayHello',
    description: 'display hello message',
    run(context) {
      console.log('Hello sfdx!');
    }
  }
}());

JavaScriptで作成することから、jsforce(やその他のJS系のSalesforceライブラリ)と組み合わせるとよさそうですね。

今のプロジェクトでは、sfdxは実戦投入できていないものの、Jenkins, jsforce, gulp, jsforce-metadata-toolsを使ったCIを組んでおり、その中でApexのテスト結果をJenkinsのJunit/Coberturaレポート形式に出力する、みたいなこともしているのですが、そういう部分をsfdxのプラグイン化することで、sfdxへの移行も緩やかにしていけそうな気がしました。

Lightning基本コンポーネントのライブエディタを作ってみる #salesforce

昨日のエントリで検討した通り、シンプルな画面を新規開発する場合においては、Lightning基本コンポーネントを極力使って開発する、というのも、今後は一つの選択肢なんじゃないかなーという仮説を立ててみました。

Lightning基本コンポーネントについては、以下に書かれています。

developer.salesforce.com

しかし、実際に開発する場合、上記ドキュメント(リファレンス)を参照しながら、ということになると思うのですが、案外しんどいことに気づきます。
それはなぜかというと、ドキュメントがテキストのみであり、結果としてどういうUIになるか、仕上がりのプレビューがないことに起因します。

そのため、ちゃんとしたものを作ろうとすると、開発者ガイド、SLDSのドキュメント、場合によってはちょんプロを作ったりと、試行錯誤して作っていくことになりそうだなと予想しています。
(と、思っているんですけど、皆さんどうしているんでしょうね…?)

というわけで、本題です。

開発のハードルを下げるためには、「ライブエディタ(リアルタイムプレビュー)とかあったらいいんじゃない?」と思い立ち、空き時間を利用して作り始めてみました。
まぁ、車輪の再発明の気もしなくもないですが、練習も兼ねてなのでいいかなと。

作ってる画面の例として、lightning:layout の場合は、以下のようなものです*1

f:id:jappy:20171214005821p:plain

このライブエディタ自体も、極力Lightning基本コンポーネントを使って開発するように、意識しています。

動きとしては、コンポーネントの属性値 horizontalAlign などを入力パラメータとして受け取って、その度にコントローラ側で $A.createComponentsコンポーネントを再作成し、PreviewとCodeに反映しています。
実際に動かしてみると、 horizontalAlign 属性には center, space, spread, endの4つのオプションがあるわけですが、それぞれのオプションで配置にどのような違いがあるのかも一目瞭然で、なかなか便利そうな気がしています。

まだまだ作りが荒いですが、モチベを保つ意味で紹介してみました。 sfdxでCI/CDをやりつつ、年内くらいには形になるといいな…。

*1:PreviewとCodeで数字がズレているのはバグですね…

Lightning Experience流のUIを作る4つの方法と比較 #salesforce

アプリをSalesforce ClassicからLightning Experience(LEX)に移行するにあたり、検討・整理した事項についてのメモです。

Salesforceの新しいUIであるLEXですが、開発する画面のLook & FeelをLEXに合わせるためには、私が知る範囲では少なくとも以下の4つの方法があります。

まぁ、どれも根底のスタイル装飾はSLDSなわけですが、作り方・扱い方の違いという観点で、4つを分けました。

主観でメリット、デメリットを整理するとこんな感じです(デメリットと言ってよいものか迷うものもありますが…)。

方式 メリット デメリット
SLDSスクラッチ 開発方式や表現力の自由度が高い マークアップが辛い。
SLDSのバージョンアップに追随する場合、コストをかけて対応が必要。
Reactライブラリ 充実したフロントエンド開発のエコシステムに乗っかって開発ができる SLDSのバージョンアップにライブラリが対応していく必要がある(利用については自己責任)
Visualforce ある程度成熟したVFの成果物を大きく変えずにLEX化できる 完璧ではないので、SLDSを使って細かい調整が必要になることもある。
Visualforceなので、表示領域の高さを固定にしておく必要がある(必要なコンテンツの高さだけ確保する、というのが難しい)
Lightning基本コンポーネント 継続的なアップデートと後方互換性が見込まれる(多分) 利用可能なコンポーネントには限りがある

汎用性(適用範囲)と生産性の高さのバランスがよいので、総合的には2番目がベターかなー、という気がしています。
もう少し掘り下げると、それぞれの方式が適切な用途というのは、それぞれ次のような時かなと思っています。

方式 あてはまるケース
SLDSスクラッチ 慣れているSPA方式で開発したい場合(React以外のライブラリ利用時。Vue.jsとか…?)
Reactライブラリ 慣れているSPA方式で開発したい場合(React利用時)
Visualforce Visualforceの標準コンポーネントを駆使して作られた既存のVisualforceページをLEX対応したい場合
Lightning基本コンポーネント 新規で作る画面であり、比較的シンプルな場合

また、4つの方式は排他的なものではなく、複数を組み合わせて開発することもあるかもしれません。
分かりにくい表ですが、その場合の組み合わせ方は下表のような手段を利用することになりそうです*1。 1番目と2番めは、成果物はSPA方式の静的リソースであるという観点では一緒なので、同一のものと見なしています。

取り込む側\取り込まれる側 SLDSスクラッチ
Reactライブラリ
(=HTML,JS,CSS)
Visualforce Lightningコンポーネント
SLDSスクラッチ
Reactライブラリ
BrowserifyやWebpack等、普通のJSエコシステムの世界 無理(?) Lightning Out
Visualforce 静的リソースとして読み込み VFコンポーネント化すればVF in VFは可。もしくはiframe Lightning Out
Lightningコンポーネント lightning:container, ltng:require iframe <lightning:コンポーネント名>, <c:コンポーネント名>

*1:実際に試していないものもあるため、かなり雑なまとめです…。

$A.createComponentsでちょっと複雑なコンポーネントを動的に作成してみる #salesforce

Lightningコンポーネントには $A.createComponent$A.createComponents メソッドを利用して、コントローラ(or ヘルパー)側で動的にコンポーネントを作成する手段が用意されています。

developer.salesforce.com

2つのメソッドの違いは単一のコンポーネントを作るか、複数の(特にネストした)コンポーネントを作るか、にあります。
$A.createComponents メソッドを使って、どれくらい複雑なコンポーネントが作れるものなのか確認してみました。

試したサンプルは、lightning:layoutにあるものです(↓)。

<aura:component>
    <div class="c-container">
        <lightning:layout horizontalAlign="space">
            <lightning:layoutItem flexibility="auto" padding="around-small">
                1
            </lightning:layoutItem>
            <lightning:layoutItem flexibility="auto" padding="around-small">
                2
            </lightning:layoutItem>
            <lightning:layoutItem flexibility="auto" padding="around-small">
                3
            </lightning:layoutItem>
            <lightning:layoutItem flexibility="auto" padding="around-small">
                4
            </lightning:layoutItem>
        </lightning:layout>
    </div>
</aura:component>

そこまで複雑というわけではないですね。。
ただ、公式の例( ui:messageui:outputText )と比べ、複数の子要素( lightning:layoutItem )を含んでいたりするので、これくらいのものができれば、後は応用が効きそうだということで選定しました。

コンポーネント : DynamicNestedComponents.cmp

Lightningコンポーネントはこんな感じになりました。

コントローラ(後述)で作成した lightning:layout, lightning:layoutItem{! v.contents} のところで出力させるようにします。

<aura:component  implements="flexipage:availableForAllPageTypes" access="global">
    <aura:attribute name="contents" type="String" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <div class="c-container">
    {! v.contents }
    </div>
</aura:component>

なお、 implements="flexipage:availableForAllPageTypes" access="global" の部分に関しては、動作確認を適当なLightningページでするためのものなので、今回の本筋ではないです。

コントローラ : DynamicNestedComponentsController.js

コンポーネントを作成する肝心のコントローラの doInit は次のようになりました。

({
    doInit : function(component, event, helper) {        
        $A.createComponents([
            ["lightning:layout", {
                "horizontalAlign" : "space"
            }],
            ["lightning:layoutItem", {
                "flexibility" : "auto",
                "padding" : "around-small"
            }],
            ["ui:outputText",{
                "value" : "1"
            }],
            ["lightning:layoutItem", {
                "flexibility" : "auto",
                "padding" : "around-small"
            }],
            ["ui:outputText",{
                "value" : "2"
            }],
            ["lightning:layoutItem", {
                "flexibility" : "auto",
                "padding" : "around-small"
            }],
            ["ui:outputText",{
                "value" : "3"
            }],
            ["lightning:layoutItem", {
                "flexibility" : "auto",
                "padding" : "around-small"
            }],
            ["ui:outputText",{
                "value" : "4"
            }],
        ],
            function(components, status, errorMessage){
                if (status === "SUCCESS") { 
                    var layout = components[0];
                    var layoutItems = [
                        components[1],
                        components[3],
                        components[5],
                        components[7] ];
                    var outputTexts = [
                        components[2],
                        components[4],
                        components[6],
                        components[8] ];
                    
                    // それぞれの lightning:layoutItem に値をセット
                    for (var i=0; i<4; i++) {
                        layoutItems[i].set("v.body", outputTexts[i]);
                    }
                    
                    // lightning:layout の下に lightning:layoutItem をセット
                    var layoutBody = layout.get("v.body");
                    for (var i =0; i<4; i++) {
                        layoutBody.push(layoutItems[i]);
                    }
                    layout.set("v.body", layoutBody);
                    
                    component.set("v.contents", layout);
                }
                else if (status === "INCOMPLETE") {
                    console.log("No response from server or client is offline.")
                    // Show offline error
                }
                else if (status === "ERROR") {
                    console.log("Error: " + errorMessage);
                    // Show error message
                }
            }
        );
    }
})

長いですね…。
子要素が4つあるサンプルのため冗長であることをを抜きにしても、結構長いです。

注意する点として、 set("v.body", ...) で中身をセットしたい場合、セットする値もコンポーネントである必要があります。
なので、lightning:layoutItem の中身は表示したい文字列ではなく、 ui:outputText コンポーネントになっています *1
もし、

layoutItems[i].set("v.body", "" + i);

のようにしてしまうと、

Uncaught Assertion Failed!: Descriptor for Config required for registration : undefined

といったエラーが起きます。

というわけで、複数の子要素を含んだLightningコンポーネントでも一応は動的に作成可能であることが確認できました。

*1:実はこのせいで、出力されるHTMLはオリジナルのものとは微妙に異なります。ui:outputText はspanタグを作ってしまうので。

Salesforce開発デザインパターン集 #salesforce

Salesforce Platformの上でApexクラス・トリガやLightningコンポーネントを開発していく上でのデザインパターンについてのまとめ。
他にもあるかもしれないです(知っていたら教えてください)

先人の知恵、大事。

トリガ

Trigger Frameworks and Apex Trigger Best Practices - developer.force.com

適用していないプロジェクトの方が少ないかもしれないくらい定番かもしれないですね。
複数のオブジェクトを操作するような混み入ったDMLを扱う時に、 bypass, clearBypass が役立ちます。

スケジュールバッチ

tyoshikawa1106.hatenablog.com

Lightning Component

広範囲のパターン集(全部はまだ追えていません…)

Salesforce Lightning Component Design Patterns and Development Best Practices - Mike Topalovich | Chicago Salesforce Architect and CTO

ApexCallだとこちら。

qiita.com

早速今年のAdvent Calendarからですね。 なるほどー!と思いました(小並感)

番外編(?)HTTP コールアウト

github.com

パターンというよりもガッツリしたライブラリですが。使い所は選びそう。。


以上。
あとは、Apex言語そのものの進化にも期待したいところです。