ReladomoにEclipse Collectionsをサポートする変更をコントリビュートしました
Reladomoとはゴールドマン・サックス社がOSSとして公開しているJavaのORMで、型安全なクエリ言語やバイテンポラルデータモデルのサポートなど、エンタープライズグレードの機能を持つORMフレームワークです。
詳しくはこれらのスライドをご覧ください。
www.slideshare.net
www.slideshare.net
さて、こちらのReladomoですが、データベースから取得したコレクションを、同じくゴールドマンサック社が同社初のOSSとして公開したGS Collectionsに変換する機能をもともとサポートしていました。
ReladomoでGS Collectionsを使用するコード例
// select * from PERSON PersonList people = PersonFinder.findMany(PersonFinder.all()) //苗字を取得 List<String> lastNames = people.asGscList().collect(Person::getLastName); //猫を飼っている飼い主を取得 List<Person> catOwner = people.asGscList().select(person -> person.hasPet(PetType.CAT)); //PetTypeごとに飼い主をグルーピング MutableListMultimap<PetType, Person> peopleByPetType = people.asGscList().groupByEach(person -> person.getPets().asGscList().collect(Pet::getPetType));
しかし、ご存知の方もいるかもしれませんが、GS Collections自体は現在開発が終了しており、Eclipse Foundationへ移管されたEclipse Collectionsという新たなプロダクトとして生まれ変わっております。
Eclipse Collections - Features you want with the collections you need. (日本語ページ)
こちらのEclipse Collections、GS Collectionsと上位互換の機能を持つのですが、パッケージの構成が異なり、実態としては異なるライブラリ依存になります。ですので、これまでReladomoがサポートしているGS CollectionsをそのままEclipse Collectionsとして使用することはできませんでした。
今回の変更では、新たにEclipse CollectionsをサポートするAPI asEcList()
を追加し、もともとのasGscList()
がdeprecated(非推奨)となりました。本機能はReladomoの17.0.0より使用することが可能です。
これにより、Reladomoそのものが持つ強力なDBクエリー言語に加えて、DBから取得したコレクションをインメモリーで操作することもEclipse Collectionsの機能で扱うことができ、データ操作の選択肢の幅が広がります!
ReladomoでEclipse Collectionsを使用するコード例
// select * from PERSON PersonList people = PersonFinder.findMany(PersonFinder.all()) //苗字を取得 List<String> lastNames = people.asEcList().collect(Person::getLastName); //猫を飼っている飼い主を取得 List<Person> catOwner = people.asEcList().select(person -> person.hasPet(PetType.CAT)); //PetTypeごとに飼い主をグルーピング MutableListMultimap<PetType, Person> peopleByPetType = people.asEcList().groupByEach(person -> person.getPets().asGscList().collect(Pet::getPetType));
その他変更の詳細はこちらのPRをご覧ください。
Reladomoでは、1年後の2019年3月にはGS Collectionsのサポートは終了する予定です。ですのでReladomoとGS Collectionsを併せて使用している方は早めの移行をおすすめします。
移行ガイドはこちら。
reladomo/GSC_TO_EC_MIGRATION_GUIDE.md at master · goldmansachs/reladomo · GitHub
ReladomoとEclipse Collections、最強の組み合わせでぜひ楽しい開発をしてみてください。Enjoy Happy Development!!
【第三回】経費申請のフローで学ぶCamundaの基本(BPMN実行編)
前回は、Camunda Modelerを用いて「経費申請」フローの簡単なBPMNモデリングをしてみました。第一回と第二回までの作業が終了したプロジェクトがGitHubにあがっているので、今回はここからスタートします。
今回は、前回作成したBPMNモデルをCamunda上で実行可能な状態にし、申請、承認、確認に必要なフォーム(html/JavaScript)とService Task
で実行するJavaコードを実装し、実際に使える業務アプリケーションとして動作させてみましょう。
まず先に業務要件をモデリングしてからアプリケーションの実装を行うということで、モデル駆動開発とも呼べる流れになっています。この方法論も楽しんでもらえればと思います。
- 【第一回】経費申請のフローで学ぶCamundaの基本(導入編)
- 【第二回】経費申請のフローで学ぶCamundaの基本(BPMNモデリング編)
- 【第三回】経費申請のフローで学ぶCamundaの基本(BPMN実行編)
- 【第四回】経費申請のフローで学ぶCamundaの基本(DMN編)
- 【第五回】経費申請のフローで学ぶCamundaの基本(CMMN編)
- 【第六回】経費申請のフローで学ぶCamundaの基本(テスト編)
- 【第七回】経費申請のフローで学ぶCamundaの基本(まとめ)
前回作成した経費申請のBPMNモデル
BPMNを実行可能にする
まずは前回作成したBPMNモデルをCamunda上で実行可能にする必要があります。「経費申請」プールを選択して、右側に現れるメニューからExecutable
という項目にチェックを入れれば実行可能になります。
また、Process Id
にはわかりやすい名前をつけておきましょう。今回はExpenseApplication
としました。
ちなみに、Camunda ModelerでつくるBPMNモデルは普通に考えてCamunda上で実行するんだから、デフォルトで実行可能にしてくれよ、っていうissueがあがっていて、どうやら次のバージョン(v1.12.0)にマージされているようです。リリースはよ。
フォームの作成
BPMNのワークフローを実行するにあたって、人間による作業や確認をするためのフォームが必要になってきます。
今回の経費申請フローでは、下記のようにそれぞれのイベント・ユーザータスクのBPMNコンポーネントに対応する、3つのフォームを作成します。
BPMNコンポーネント | フォーム名 | ファイル名 |
---|---|---|
経費申請フォーム | apply-form.html | |
経費承認フォーム | approve-form.html | |
経費確認フォーム | confirm-form.html |
src/main/resources/static/forms/apply-form.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>経費申請フォーム</title> </head> <body> <h2>経費申請フォーム</h2> <form role="form" name="expenseForm" class="form-horizontal"> <div class="form-group"> <label class="control-label col-md-4" for="expenseReceiptUpload">領収書のアップロード</label> <div class="col-md-8"> <input type="file" id="expenseReceiptUpload" cam-variable-name="expenseReceipt" cam-variable-type="File" cam-max-filesize="10000000" class="form-control"/> <div class="help-block">経費の領収書の画像を選択してください</div> </div> </div> <script cam-script type="text/form-script"> var fileUpload = $('#expenseReceiptUpload'); var fileUploadHelpBlock = $('.help-block', fileUpload.parent()); function flagFileUpload() { var hasFile = fileUpload.get(0).files.length > 0; fileUpload[hasFile ? 'removeClass' : 'addClass']('ng-invalid'); fileUploadHelpBlock[hasFile ? 'removeClass' : 'addClass']('error'); return hasFile; } fileUpload.on('change', function () { flagFileUpload(); }); camForm.on('submit', function(evt) { var hasFile = flagFileUpload(); // prevent submit if user has not provided a file evt.submitPrevented = !hasFile; }); </script> <div class="form-group"> <label class="control-label col-md-4" for="detail">内容</label> <div class="col-md-8"> <input cam-variable-name="detail" cam-variable-type="String" id="detail" class="form-control" type="text" required /> </div> </div> <div class="form-group"> <label class="control-label col-md-4" for="amount">金額(円)</label> <div class="col-md-8"> <input cam-variable-name="amount" cam-variable-type="Double" id="amount" name="amount" class="form-control" type="text" required /> </div> </div> <div class="form-group"> <label class="control-label col-md-4" for="expenseCategory">経費種別</label> <div class="col-md-8"> <select cam-variable-name="expenseCategory" cam-variable-type="String" id="expenseCategory" class="form-control"> <option value="TRAVEL" label="旅費交通費"/> <option value="MEAL" label="接待交際費"/> <option value="EXPENDABLES" label="消耗品"/> <option value="OTHER" label="その他"/> </select> </div> </div> </form> </body>
src/main/resources/static/forms/approve-form.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>経費申請フォーム</title> </head> <body> <h2>経費申請フォーム</h2> <form role="form" name="expenseForm" class="form-horizontal"> <p>経費情報を確認の上、承認してください</p> <div class="form-group"> <label class="control-label col-md-2">経費領収書</label> <div class="form-control-static col-md-10"> <input type="hidden" cam-variable-name="expenseReceipt"/> <img src="{{ camForm.variableManager.variable('expenseReceipt').contentUrl }}" class="img-responsive"> </div> </div> <div class="form-group"> <label class="control-label col-md-2">内容</label> <div class="col-md-10"> <input cam-variable-name="detail" type="text" name="detail" readonly="true" class="form-control" /> </div> </div> <div class="form-group"> <label class="control-label col-md-2">金額(円)</label> <div class="col-md-10"> <input cam-variable-name="amount" type="text" name="amount" readonly="true" class="form-control" /> </div> </div> <div class="form-group"> <label class="control-label col-md-2">経費種別</label> <div class="col-md-10"> <select cam-variable-name="expenseCategory" cam-variable-type="String" name="expenseCategory" disabled="true" class="form-control"> <option value="TRAVEL" label="旅費交通費"/> <option value="MEAL" label="接待交際費"/> <option value="EXPENDABLES" label="消耗品"/> <option value="OTHER" label="その他"/> </select> </div> </div> <div class="form-group"> <div class="col-md-10 col-md-offset-2"> <label> 承認しますか? <select cam-variable-name="approved" cam-variable-type="Boolean" id="approved" name="approved" class="form-control"> <option value="true" label="承認"/> <option value="false" label="却下"/> </select> </label> </div> </div> </form> </body>
src/main/resources/static/forms/confirm-form.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>経費申請フォーム</title> </head> <body> <h2>経費申請フォーム</h2> <form role="form" name="expenseForm" class="form-horizontal"> <p>経費情報を確認の上、タスクをCompleteにしてください</p> <div class="form-group"> <label class="control-label col-md-2">経費領収書</label> <div class="form-control-static col-md-10"> <input type="hidden" cam-variable-name="expenseReceipt"/> <img src="{{ camForm.variableManager.variable('expenseReceipt').contentUrl }}" class="img-responsive"> </div> </div> <div class="form-group"> <label class="control-label col-md-2">内容</label> <div class="col-md-10"> <input cam-variable-name="detail" type="text" name="detail" readonly="true" class="form-control" /> </div> </div> <div class="form-group"> <label class="control-label col-md-2">金額(円)</label> <div class="col-md-10"> <input cam-variable-name="amount" type="text" name="amount" readonly="true" class="form-control" /> </div> </div> <div class="form-group"> <label class="control-label col-md-2">経費種別</label> <div class="col-md-10"> <select cam-variable-name="expenseCategory" cam-variable-type="String" name="expenseCategory" disabled="true" class="form-control"> <option value="TRAVEL" label="旅費交通費"/> <option value="MEAL" label="接待交際費"/> <option value="EXPENDABLES" label="消耗品"/> <option value="OTHER" label="その他"/> </select> </div> </div> <div class="form-group"> <div class="col-md-10 col-md-offset-2"> <label> 承認情報: <select cam-variable-name="approved" cam-variable-type="Boolean" id="approved" name="approved" disabled="true" class="form-control"> <option value="true" label="承認"/> <option value="false" label="却下"/> </select> </label> </div> </div> </form> </body>
BPMNコンポーネントとフォームの紐付け
これらのフォームをそれぞれのイベント・タスクと結びつけるために、BPMNコンポーネントを選択肢、右メニューのForms
タブにあるForm Key
という項目に、対応するフォームのファイルパスをembedded:app:
に続けて入力します。
embedded:app:forms/apply-form.html
embedded:app:forms/approve-form.html
embedded:app:forms/confirm-form.html
以上でフォームに関する設定は完了です。
Exclusive Gatewayの分岐条件の設定
次に、「承認」タスクで承認者がタスクをCompleteした後に、ゲートウェイにおいて承認済みか否かの分岐判定が必要になります。
「承認済み?」のExclusive Gateway
から伸びている「いいえ」と「はい」のフローコンポーネントそれぞれに、「承認」タスクでフォームで入力されたapproved
変数を用いた条件式を設定します。
「いいえ」の分岐
「はい」の分岐
Service Taskの実装
「経費記録」のサービスタスクでは、今回は単純に経費情報をログに出力してみましょう。
Service Task
は実装方法を選択する必要があるので、「経費記録」のService Task
の詳細に、今回はJava Class
として下記のように入力しておきます。
Javaの実装としては、経費種別のラベリングのためのenum Category.java
と、サービスタスク ExpenseRecorder.java
を以下のように作成します。
src/main/java/sample/Category.java
package sample; public enum Category { TRAVEL { @Override public String toString() { return "旅費交通費"; } }, MEAL { @Override public String toString() { return "接待交際費"; } }, EXPENDABLES { @Override public String toString() { return "消耗品"; } }, OTHER { @Override public String toString() { return "その他"; } } }
src/main/java/sample/service/ExpenseRecorder.java
package sample.service; import org.camunda.bpm.engine.delegate.DelegateExecution; import org.camunda.bpm.engine.delegate.JavaDelegate; import sample.Category; import java.util.logging.Logger; public class ExpenseRecorder implements JavaDelegate { private final Logger LOGGER = Logger.getLogger(ExpenseRecorder.class.getName()); @Override public void execute(DelegateExecution execution) throws Exception { LOGGER.info("内容: " + execution.getVariable("detail")); LOGGER.info("金額(円): " + execution.getVariable("amount")); LOGGER.info("経費種別: " + Category.valueOf((String)execution.getVariable("expenseCategory"))); LOGGER.info("承認済み?: " + (((Boolean)execution.getVariable("approved")).booleanValue() ? "はい" : "いいえ")); } }
BPMNの配備とプロセス設定
作成したBPMNはsrc/main/resources
直下に配備しておきましょう。
src/main/resources/expense_application.bpmn
次に、src/main/resources/META-INF/processes.xml
を作成し、経費申請BPMN(expense_application.bpmn)をデフォルトで読み込むように設定します。
<process-application xmlns="http://www.camunda.org/schema/1.0/ProcessApplication" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <process-archive> <process-engine>default</process-engine> <resource>expense_application.bpmn</resource> <properties> <property name="isDeleteUponUndeploy">false</property> <property name="isScanForProcessDefinitions">false</property> </properties> </process-archive> </process-application>
ここまででBPMNはCamundaで自動的にロードされ実行可能な状態になりました。試しにSpring BootからCamundaを立ち上げてみましょう。
$ gradle bootRun
立ち上がったら http://localhost:8080/ にアクセスし、demo/demoでログインします。
Applications
項目にあるTasklist
をクリックします。
Tasklist
が開き、現状ではリストは空の状態です。
さぁ、ここから経費申請フローを動かしてみましょう。
経費申請フローのデモ
今回は、承認者が承認する条件分岐で経費申請フローを試してみます。また、簡単のために申請者
、承認者
、経理
の役割ははすべてdemo
ユーザーで実行します。
CamundaにデプロイされたBPMNのプロセスを起動するために、Tasklist
の右上のメニューにあるStart Process
をクリックします。
ポップアップで現れるメニューからExpenseApplication
をクリックすると、経費申請のプロセスが開始します。
この時点で「経費申請フォーム送信」イベントがトリガーされ、イベントに紐づけられたフォームが出現します。
適当な領収書の画像ファイルを選択し、必要な項目を入力し、Start
ボタンを押します。
ここで一旦Tasklist
のページをリロードすると、承認
タスクが現れます。
この承認
タスクをクリックしてみると、今度は承認
タスクに紐づけられたフォームが出現します。
demo
ユーザーにアサインするために、右上のClaim
ボタンを押すと、
承認
タスクがdemo
ユーザー担当になります。
これで承認できるようになったので、ドロップボックスを「承認」のままComplete
ボタンを押してみましょう。
すると、今度はTasklist
に経理担当の確認
タスクが出現します。
以上で経費申請のワークフローが経理の確認待ちという状態まで遷移しました。
(閑話休題)Cockpitでの実行中のプロセスの確認
デモの最中ですが、ここでいったんCockpitという機能を紹介したいと思います。
CamundaにはCockpitというダッシュボード機能がデフォルトで備わっており、デプロイされたBPMNや、現在実行中のプロセス、現在タスク完了待ちのユーザータスクなどが確認できます。
試しに上記のデモの途中の、経費を承認後(つまり、経理の確認待ちの状態)に、実行中のプロセスの状態をCockpitで確認してみましょう。
Cockpitにアクセスし、Running Process Instances
をクリックします。
http://localhost:8080/app/cockpit/default/#/dashboard
実行中のプロセスが表示されるので、ExpenseApplication
を選択します。
すると、下のように現在①プロセスが「確認」タスクのところにとどまっていることが可視化されます。
このように、Cockpitは実行中のプロセスをモニターするのに便利な機能を提供します。
確認タスクの実行により経費記録のサービスタスクをトリガー
さて、デモに戻ります。
上のCockpitでも確認できるように、最後に残るユーザータスクは経理の確認のみで、そのあとはサービスタスクである経費記録(ロギング)がトリガーされるはずです。
さっそく「確認」タスクを完了してみましょう。
Tasklist
に戻り、「承認」タスクの場合と同様に「確認」タスクをClaimし、demo
ユーザーにアサインします。
今回のタスクは確認するのみなので、特に何も編集する必要なしにComplete
ボタンを押します。
「確認」タスクをComplete
するとすぐに「経費記録」のサービスタスクが実行されるので、Spring Bootのログを確認してみましょう。
2018-01-16 15:41:35.131 INFO 16948 --- [io-8080-exec-10] sample.service.ExpenseRecorder : 内容: Scala関西の交通費 2018-01-16 15:41:35.131 INFO 16948 --- [io-8080-exec-10] sample.service.ExpenseRecorder : 金額(円): 28000.0 2018-01-16 15:41:35.140 INFO 16948 --- [io-8080-exec-10] sample.service.ExpenseRecorder : 経費種別: 旅費交通費 2018-01-16 15:41:35.140 INFO 16948 --- [io-8080-exec-10] sample.service.ExpenseRecorder : 承認済み?: はい
見事ログが出力されましたね!
今回は簡単なデモのためにログ出力としましたが、サービスタスクの実装はJavaのクラスで行われているので、外部のAPIを叩くもよし、データベースにストアするもよし、メッセージキューに送信するもよし、なんでもできます。
以上で今回の経費申請デモは完了です。今回の記事で実装したプロジェクトはこちらになります。
皆さんの手元でも、承認が「却下」になる分岐を実行してみたり、「却下」の場合の情報も出力するサービスタスクを追加して実行してみたり、遊んでみてください。
業務ワークフローをBPMNエンジンで実装するメリット
CamundaのようなBPMNワークフローエンジンを利用するメリットとして、私個人として一番大きいと思っているのは、ワークフローの「状態遷移」をバックエンドの実装と分離することができるところです。BPMNでモデル化できるような業務フローをバックエンドサービスとしてフロムスクラッチで実装しようとすると、どうしてもステートマシンのようなロジックを実装する必要がでてきます。手動で実装するステートマシンは結構楽しいんですが、業務要件に変更が加わった際に牙を向きます。ある状態から他の状態へ遷移する途中に新しい状態や分岐が必要となった場合、影響範囲の調査もなかなか厳しいですし、実装の変更工数はもとよりテストの工数もかさみます。さらにいうと本番環境でステートマシンから外れた例外的な状態遷移を実行する必要が出てきたときにニッチもサッチもいかなくなるという悲しみも抱えることになります(経験談)。
BPMNワークフローエンジンを用いて状態遷移をBPMNのモデルに集約するようにすると、状態遷移のロジックは分離され、バックエンドの実装は情報のストア(CRUD)やメッセージングに専念することができます。BPMNモデルそのものが業務要件を表現しているので、変更の影響範囲は可視化されます。新しい状態「遷移」を追加するにはBPMNのモデル側を変更すればよく、状態「情報」そのものはサービスタスクを用いてバックエンドにストアするような形になるでしょう。バックエンド側に遷移ロジックが入り込まないことで、変更に伴う状態の矛盾のようなバグが入り込む余地がなくなり、アジャイルで柔軟かつロバストなシステム開発を実現できるのではないかと考えています。
さらに、バックエンドの情報ストアに関しては、Reladomoのように履歴管理が容易なデータアクセスレイヤーを利用できると業務アプリケーションとしてはさらにいろいろ面白いことができるでしょう。この辺はいつか別のブログで書けると良いなと思います。
BPMNに変更を加える際のデプロイの方法論に関してはいろいろ検証が必要だと思いますが、デプロイ前にワークフローの中間状態のプロセスを排除(全て終了まで持っていくか、全てを始点に戻す等)するような施策が必要になってくるかなと思います。
第三回のまとめ
今回はBPMNでモデル化した経費申請フローを用いて、Camunda上で実行させるのに必要なフォーム実装とサービスタスク実装、BPMNの設定を行いました。第一回〜第三回までの準備・モデリング・実装プロセスだけで実際に動作する業務アプリケーションが完成しました。
いかがだったでしょうか。思いのほか簡単にモデリングできるし、アプリケーションの実装も非常にシンプルです(実装のほとんどはフォーム側で、バックエンドに相当するJavaのコードは今回はほとんど書いてませんw)。
ここまででCamundaの持つモデリング駆動の開発の可能性と、ワークフローエンジンとバックエンドストアの責務分離の強力さをなんとなく感じてもらえたら幸いです。
次回は、BPMN単独ではなかなかモデリングが難しい、意思決定のためのモデリング記法であるDMNを学んでみましょう。今回までに作成した経費申請ワークフローを拡張し、意思決定プロセスを導入してみます。お楽しみに!
【第二回】経費申請のフローで学ぶCamundaの基本(BPMNモデリング編)
前回はCamunda ModelerのインストールとCamunda Spring Boot StarterによるCamunda環境構築を行いました。
- 【第一回】経費申請のフローで学ぶCamundaの基本(導入編)
- 【第二回】経費申請のフローで学ぶCamundaの基本(BPMNモデリング編)
- 【第三回】経費申請のフローで学ぶCamundaの基本(BPMN実行編)
- 【第四回】経費申請のフローで学ぶCamundaの基本(DMN編)
- 【第五回】経費申請のフローで学ぶCamundaの基本(CMMN編)
- 【第六回】経費申請のフローで学ぶCamundaの基本(テスト編)
- 【第七回】経費申請のフローで学ぶCamundaの基本(まとめ)
今回は、前回インストールしたCamunda Modelerを使って、「経費申請」の基本的なワークフローをBPMNでモデリングしてみましょう。
BPMNとは
BPMNとはBusiness Process Modeling Notationの略で、ビジネスプロセスをモデリングするための記法です。
ビジネスプロセスとは業務の一連の流れのことで、BPMNは、タスクの担当者や担当範囲、フローの分岐条件、システムが担当するタスクやメッセージのやりとりなどが明確かつロジカルに定義できる、定型作業としてのワークフローをモデルとして表記することを得意とします。
百聞は一見にしかずなので、経費申請のワークフローを下記のような簡単な業務要件としてモデリングしてみましょう。
- ユーザーが経費申請のフォームを入力する
- 承認者が申請を承認する
上記のような要件をBPMNで表現してみると下のようなモデルが出来上がります。
GitHubにもあげてあるのでCamunda Modelerから開いて見ることも可能です。
ぱっと見ただけでも直感的に何を表現しているかわかりやすいかと思いますが、今回はこのBPMNのモデルを作成するのに必要ないくつかのコンポーネントの説明と、Camunda Modeler上でのコンポーネントの作成方法を解説していきます。
プール
ひとかたまりのビジネスプロセスや、あるプロセスを担当する一番高次な概念(たとえば部署等)を表現するためのコンポーネントを「プール」と呼びます。プールは、Camunda Modeler上の左下にあるこちらのボタンを押すと作成することができます。
プールの詳細についてはこちらのドキュメントに詳しく記載があります。
今回は「経費申請」のためのプロセスということで、こちらのボタンから経費申請のプールを作成すると下のようになります。
プールの名前(今回は「経費申請」)は、プールを選択した際に右側に表示されるProperties Panel
のName
欄に入力すればOKです。
プール以外の他のBPMNコンポーネントについても、名前に関してはすべて同じProperties Panel
のName
欄から記入することができます。
スイムレーン
スイムレーンとは、プール内で特定のタスクの担当範囲を分割するためのコンポーネントです。詳細についてはこちらのドキュメントを参照してください。
今回の経費申請ワークフローでは、申請者
、承認者
、経理
という担当範囲を定義しているので、経費申請プールにこのスイムレーンを作成してみましょう。
まずはプールを選択した際に右側に現れるDivide into two Lanes
というボタンを押してみましょう。
下のように、プールが2つのレーンに分割されます。
次に、どちらかのレーンを選択して右のメニューのAdd Lane above
もしくはAdd Lane below
どちらでも良いので押して見ましょう。
これでスイムレーンが3つ作成できたので、それぞれ申請者
、承認者
、経理
としましょう。
イベント
BPMNには「イベント」という概念があり、ワークフローのスタート起点となるイベントStart Event
であったり、プロセスの途中に発生するエラーハンドリングのようなイベントIntermidiate Event
、プロセスの終点となるイベントEnd Event
を表記することができます。
Camunda Modeler上では左のメニューにあるこれらの丸いコンポーネントがそれぞれStart Event
、Intermidiate Event
、End Event
となります。
さらに、それぞれのイベント内にもメッセージを起点とするイベントMessage Event
や時間発火するイベントTimer Event
、特定の条件で発動するイベントConditional Event
、Signal Event
等さまざまな種類のイベントが存在します。今回はこれらの紹介は省きますが、イベントの詳細はこちらのドキュメントを参照してみてください。
今回は起点イベントとして「経費申請フォーム送信」というStart Event
を作成してみましょう。左のメニューからStart Event
を選択して申請者
レーンに配置をし、「経費申請フォーム送信」という名前をつけるだけなので直感的にいけるかとおもいます。
タスク
さて、一旦起点イベントから「経費申請フォーム」が送信されたら、今度は承認者が申請フォームを承認する必要があります。BPMNでは、このような、人やシステムが何かしらのアクションを起こす概念を「タスク」と呼びます。
タスクにもさまざまな種類があり、こちらのドキュメントで詳細を確認することができます。
「経費申請」フローで使用するタスクは、人がアクションを起こすUser Task
と、システムがアクションを起こすService Task
の2種類で、今回の例ではUser Task
は「承認」と「確認」、Service Task
は「経費記録」のタスクをこなします。それぞれ下のようなBPMNコンポーネントとなります。
ここでは承認者が承認するユーザータスクUser Task
を作成してみましょう。
先ほど作成した「経費申請フォーム送信」イベントを選択すると、タスクを追加するためのAppend Task
というメニューが表示されるので、こちらをクリックします。
タスクを配置する場所をマウスで選択できるので、承認者
レーンでクリックします。
すると、下記のようなメニューが出現するので、レンチマークのChange Type
を選択し、
タスクのタイプとしてUser Task
を選択しましょう。
名前を承認
とすることで、下のようなモデルが出来上がります。
これでUser Task
を作成することができました。
ゲートウェイ
ビジネスプロセス上では、さまざまな状況に応じてプロセスが分岐したり、分岐したフローが合流したりといった事象を表現をしたいことがあります。BPMNでこの分岐や合流を表現するのがゲートウェイです。
今回は、経費申請が「承認されたかどうか」という判断と分岐するためにExclusive Gateway
というコンポーネントを使います。
Exclusive Gateway
は、ある条件(例えば今回は「承認済みかどうか」)に対して一つの分岐フローだけが選択されるゲートウェイになります。
承認
ユーザータスクから「承認済み」かどうかを判定し分岐するためのExlusive Gateway
を足します。
作成されたExclusive Gateway
に「承認済み?」という名前をつけ、「いいえ」の場合の分岐に「経費却下」というEnd Event
を作成しましょう。下のようにEnd Event
はAppend EndEvent
から作成することができます。
「承認済み?」の条件が「はい」だった場合の分岐には、経理
のレーンに「確認」のUser Task
を作成します。
仕上げ
ここまででプール
、スイムレーン
、イベント
、タスク
、ゲートウェイ
というBPMNの基本的なコンポーネントを使用して「経費申請」のワークフローのほぼ最終段階までモデリングしてきました。最後に、これまでと同様の流れで「経費記録」というService Task
と「経費受理」というEnd Event
を作成すれば今回のBPMN図は完成になります。
最初にも書きましたが今回作成したBPMNはこちらのGitHubにあげてあるので、Camunda Modelerから開くことができます。最終成果物を確かめてみたい方はこちらからどうぞ。
第二回まとめ
いかがでしたでしょうか。業務要件の定義さえしっかりされていれば、Camunda Modelerを用いたBPMNモデリングは非常に直感的かつ簡単にできることがおわかりいただけたのではないでしょうか?
今回は「経費申請」という単純なワークフローを題材に、下記のBPMNコンポーネント用いてビジネスプロセスをモデリングしてみました。
次回は、今回作成したBPMNモデルをCamundaの実行環境にデプロイし、いくつかのフォームとJava実装と連携することで、実際に動く経費申請アプリケーションを作成してみます。お楽しみに。
【第一回】経費申請のフローで学ぶCamundaの基本(導入編)
Camundaとはオープンソースのワークフロー&ビジネスプロセスマネジメントの総合プラットフォームで、BPMN2.0、CMMN1.1、DMN1.1に準拠したモデリングや実行環境を構築することができます。
JJUG CCC Fall 2017では世界的な証券会社であるゴールドマン・サックス社においてもCamundaを利用してビジネスプロセスの可視化や自動化を行なっているという講演がありました。
現状Camundaに関する日本語の情報はほとんど見当たらない状況で、アドカレの記事でも書いたように自社での導入検証もしていければとも考えているので、せっかくなのでオープンな形で技術検証をしていきたいと思います。
今回、下記のような連載形式で、「経費申請」という比較的簡単な業務フローをテーマに、Camundaを用いたモデリングとワークフロー実装の基本を学んでいきたいと思います。
実際に業務要件をモデリングするような流れでひとつのアプリケーションを作成していこうと考えているので、網羅的に機能を解説していくというよりは、「Camundaを使うと何ができるのか?」を全体を通してイメージできるようになることを目的としています。
(下の流れはあくまで予定)
- 【第一回】経費申請のフローで学ぶCamundaの基本(導入編)
- 【第二回】経費申請のフローで学ぶCamundaの基本(BPMNモデリング編)
- 【第三回】経費申請のフローで学ぶCamundaの基本(BPMN実行編)
- 【第四回】経費申請のフローで学ぶCamundaの基本(DMN編)
- 【第五回】経費申請のフローで学ぶCamundaの基本(CMMN編)
- 【第六回】経費申請のフローで学ぶCamundaの基本(テスト編)
- 【第七回】経費申請のフローで学ぶCamundaの基本(まとめ)
本エントリーでは導入編として、モデリングツールであるCamunda Modelerのインストールと、Camundaの実行環境をSpring Boot上に構築してみます。
連載を通して基本的に下記の環境にて動作確認をしていますが、他のOSやJavaバージョンでもほとんど差異はないものと思います(おそらく)。もし何か気付いた点などありましたらブログへのコメントやGitHubへのプルリクをいただけると幸いです。
Mac OS High Sierra: Version 10.13.2 Java 9 Gradle 4.2.1 Spring Boot 1.5.9-RELEASE Camunda 7.7 Camunda Spring Boot Starter 2.2
Camunda Modelerのインストール
Camunda ModelerはCamundaのワークフローエンジン上で実行することができるBPMN/CMMN/DMNのモデリングツールです。各モデリング記法の目的やモデリング、実行方法は第二回以降の記事で少しずつ学んでいければと思います。
Camunda Modelerのインストールに関しては非常に簡単で、こちらのダウンロードページから対応するOSのバイナリをダウンロードして、実行ファイルを実行するだけです。
https://camunda.com/download/modeler/
次のエントリーではBPMNのモデリングをしてみる予定なので、まずは「BPMN diagram」をクリックしてみましょう。
BPMNのモデリングの画面が立ち上がりました。Camunda Modelerに関してはこれで準備完了です!
Spring Boot上でCamundaの実行環境を構築
Spring Boot上のCamunda実行環境をGradleで構築します。CamundaのSpring Boot StarterはこちらのGitHubで提供されています。最小構成のbuild.gradle
はこんな感じ。
build.gradle
buildscript { ext { camundaVersion = '7.7.0' springBootVersion = '1.5.9.RELEASE' camundaSpringBootVersion = '2.2.0' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") classpath "io.spring.gradle:dependency-management-plugin:1.0.4.RELEASE" } } apply plugin: 'java' apply plugin: 'org.springframework.boot' apply plugin: "io.spring.dependency-management" group = 'sample' version = '0.0.1-SNAPSHOT' repositories { mavenCentral() } dependencyManagement { imports { mavenBom "org.camunda.bpm:camunda-bom:${camundaVersion}" mavenBom "org.springframework.boot:spring-boot-dependencies:${springBootVersion}" mavenBom "org.camunda.bpm.extension.springboot:camunda-bpm-spring-boot-starter-bom:${camundaSpringBootVersion}" } } dependencies { compile('org.camunda.bpm.extension.springboot:camunda-bpm-spring-boot-starter-webapp') compile('com.h2database:h2') testCompile('org.springframework.boot:spring-boot-starter-test') if(JavaVersion.current() == JavaVersion.VERSION_1_9) { runtime('javax.xml.bind:jaxb-api:2.3.0') } }
最小構成のアプリケーションコードはこんな感じ。 src/main/java/sample/CamundaExpenseProcessApplication.java
package sample; import org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @EnableProcessApplication public class CamundaExpenseProcessApplication { public static void main(String[] args) { SpringApplication.run(CamundaExpenseProcessApplication.class, args); } }
Camundaが使うデータベースをin-memoryのh2に設定するために、application.properties
を作成します。下記の設定ではメモリー上のh2データベースを使用するのでアプリケーションを立ち上げなおすたびに情報はリセットされます。もし情報を永続化しておきたい場合はspring.datasource.url=jdbc:h2:mem:camunda
をコメントアウトして代わりにspring.datasource.url=jdbc:h2:~/camunda;DB_CLOSE_ON_EXIT=false
を有効にしてください。
src/main/resources/application.properties
spring.datasource.url=jdbc:h2:mem:camunda #Use this config in case you want to persist camunda data into file system (comment the config above in that case) #spring.datasource.url=jdbc:h2:~/camunda;DB_CLOSE_ON_EXIT=false
デフォルトの設定として、デモユーザー(id/password = demo/demo)と、すべてのタスクを表示するfilterをapplication.yml
を追加しておきます。
src/main/resources/application.yml
camunda.bpm: admin-user: id: demo password: demo firstName: デモ lastName: ユーザー filter: create: All tasks
以上で最小限構成のCamundaアプリケーションが立ち上がる状態になりました。GitHubに動作確認済みのプロジェクトを上げてあるのでこちらからどうぞ。
https://github.com/itohiro73/camunda-expense-example/tree/1-intro
GradleでSpring Bootアプリケーションを立ち上げます。
$ gradle bootRun
立ち上がったら http://localhost:8080/ にアクセスすると、下のような ログイン画面が立ち上がります。
上記で設定したデフォルトのデモユーザー(demo/demo)を使用してログインしてみます。
これでCamundaが立ち上がりました。トップ画面からはCockpit、Taslklist、Adminの機能にアクセスすることができます。
この時点ではまだワークフローをデプロイしていないのでCockpit上では何もできませんが、TasklistやAdminの基本的な機能は動くのでポチポチ遊んでみてもいいかもしれません。
以上でCamundaの準備は完了です!
第一回まとめ
今回は導入編として
- Camunda Modelerのインストール
- Gradle/Camunda Spring Boot Starterを用いた環境構築とCamundaアプリケーションの起動
新卒から12年エンジニアとして勤めた某外資証券会社を辞めてスタートアップにジョインした話
本記事は退職者その2 Advent Calendar 2017の25日目の記事です。
僭越ながら最終日を担当させていただきます、@itohiro73 と申します、よろしくお願いします。
半年前ちょっと前にすでに入社エントリーは書いているので順序が大幅に逆になってしまっていますが、今回は退職者アドベントカレンダーということで、せっかくの機会なので新卒から12年も勤めた前の会社のことを振り返ってみようと思います。
前の会社について
世界的にもかなり大きい外資系証券会社でエンジニアとして新卒から入社して12年と1ヶ月半くらい勤めました。会社名に関しては、自分がかなり大々的にOSS活動等もしていたこともあり公開情報からちょっと調べればわかるところではありますし、本記事を読んで明らかにわかる部分もあります。とくに隠しているわけではないのですが、一応大人の事情としてあえて言及はしないといったところです。
なぜ外資系証券会社に「エンジニア」で新卒入社?
よく、新卒で外資系証券会社の「エンジニア」という選択肢をどうしてとったのかとよく聞かれます。
(余談ですが12年くらい前は「エンジニア」ってあんまり呼ばれていなかった気がしていて、同様の職種は日本だとプログラマー、海外(社内)だとdeveloperと呼ばれるのが普通でした。いつのころからかエンジニア/engineerが定着しましたね。ということで当時的にはdeveloperとしての新卒入社です。)
外資系証券会社というと、投資銀行部門のバンカーであったり、トレーダーやセールス、アナリスト等のいわゆる「フロントオフィス」の職種が花形であるとされています。
そんななかで、なんでエンジニア?っていうのが外資系証券会社をよく知る人たちにとっての疑問なのでしょう。逆にエンジニア系の人たちにとっては、(今でこそFinTechが盛り上がりを見せているものの)そもそも金融であったり証券に興味がない人が多いため、SIやWeb系企業ではなくなぜ外資証券?っていうのが疑問なんだと思います。
実際当時の私にとっては、外資証券に興味があったわけでは特になく、いわゆる就職活動によくある自己分析というやつで下のような3つの軸を求めてました。
- 自分で手をうごかしてものづくりをしたい
- 自分がつくったものを実際につかってくれるユーザーの側で働きたい(フィードバックを直に受けたい)
- インターナショナルで多様な環境で働いてみたい
この軸に沿っていてくれさえすればいいと思っていたものの、当時この3つすべてを満たすような会社は自分が調べた範囲内ではほとんど存在していませんでした。
そんな中バイト先で一緒に働いていた大学の同期が、〇〇っていう会社の選考を受けてきた〜っていう話を聞いたのがこの外資証券の会社でした。へぇーそんな会社があるんだと思ってリクナビから会社ホームページを調べたりいろんな口コミページ的なのをを眺めて見たら、「社内で使われているほとんどのトレーディングシステムや業務システムが内製化されている」「チームワークを重要視するカルチャー」「外資系企業で、多様性を重視、さまざまなバックグランドの人が働いている」というような文言がならんでいるじゃないですか。まさに上の3つの軸を満たしていそうで、「自分が求めているものにすごくしっくりくる、ここしかないんじゃないかー」、と思って早速エントリーしたのがすべての始まりです。
実際、就職活動中にお話を聞いた先輩社員の方達は皆ものすごく魅力的で、最高に輝いて見えました。その中には現在bitFlyerでCEOをされている加納さんもいました。こんなすごい人たちと一緒に働けるのであれば最高に楽しいだろうなと考え、全力で選考に挑んだところ、ご縁があって採用していただけました。
最初の「外資系証券会社でなぜエンジニア?」という疑問ですが、実際蓋を開けてみると、全世界で3万数千人いる社員のうちの約4分の1、つまり8~9000人のオーダーでエンジニアが働いています。この人員だけでITの大企業がつくれてしまう規模感です。それほどまでにエンジニアリングに力をいれている環境で、世界中の優秀なエンジニアと切磋琢磨しながら証券会社という複雑怪奇なビジネスをつくりあげていく。こんな最高に面白い環境は今考えても他になかったとおもいます。実際エンジニアとして最初の12年のキャリアを積むのにはこれ以上ない環境だったと感じていますし、今でもこの会社のことは大好きです。
前の会社のカルチャーと強み
ここの会社の一番好きなカルチャーは、スーパースター個人個人に頼るような仕事の進め方はせず、チームワークを最重要視するところ。チームとして最高のパフォーマンスを出すために皆がそれぞれ自分のチームの中での役割をしっかりこなす。"I did this"ではなく"We did this"と言うのが美徳とされる、そんなカルチャーでした。
このカルチャーはチームレベルではなく、会社全体の特性としても現れていました。よく「世界最強の投資銀行」などと評される会社でしたが、特定の部門が最強だったわけではありません。投資銀行のバンカー、トレーダー、セールス、アナリスト、コントローラーズ、オペレーションズ、コンプライアンス、テクノロジー、人事から総務や広報にいたるまで、すべての部門でそれぞれ最高のパフォーマンスを出し、部門間でのチームワークを発揮することで会社として世界最強の名をものにすることができていたのではないかと、自分は12年働いてきた中でそう感じています。
会社の外でよく聞く評判では、イカつい人が働いてそうとかガツガツしてそうとか怖そうとか言われるんですが、実際そんなことはなくて、一緒に働く人みなものすごく温和でやさしくて非常に人間味あふれる人たちでした。
さらにすごいのは、このカルチャーがグローバルで、しかも偉い人から新人まで一貫しているというところ。グローバルで数万人規模の大企業であるにも関わらず、日本で働いていてもニューヨークに出張してもインドに出張しても、香港やシンガポールの人たちとリモートで働いていても、常にアットホーム感あふれるものすごく暖かいチームワークを常に感じていました。
そしてもうひとつ一貫していたのが、皆一様にものすごく負けず嫌いであるというところ。「温和」と「負けず嫌い」って相反する概念じゃ無い?って思うかもしれませんが、これは本当に面白いんですが、このふたつの性質は同居できるんですね。フロントオフィスの人たちはわかりやすく勝ちにこだわって攻めて行くという意味での負けず嫌い。バックオフィスの人たちは絶対に負けない戦略をじっくり練って守って行くという意味での負けず嫌い。温和でやさしいけど皆さん芯がしっかりしてるんです。
マイノリティであり、多様性の一部であるということ
この会社に入って一番のカルチャーショックだったと言えるのは、「日本人であることがマイノリティである」ということに気付かされたことでした。
この会社の東京オフィスでは、なんと「日本人のためのコミュニティ」というものがが存在します。東京にいるのに日本人のためってどういうこと?と思うかもしれません。私の例をとってみれば、東京オフィスでのテクノロジー部の新卒同期は9人。そのうち日本人は4人だけです。グローバルでみるとテクノロジー部新卒同期全体で240人ほど、他のオフィスでの日本人採用はゼロだったので、同期の仲間たちでも日本人であるということは全体の2%にも満たない、相当なマイノリティです。
ではこの「日本人のためのコミュニティ」(Fuji Committee
という名で呼ばれていました)は何をするために存在するのでしょうか。日本人は、実は自分たちでは普段意識していないような日本人特有の性質を平均的に持っています。たとえば、コミュニケーションのとり方がハイコンテクストであったり、同調意識が働いて他の人と違う意見を自分の中にためこみがちだったりだとか、単純に英語が得意でないとか。あとは欧米人が比較的得意なアクティブな議論の場で発言するのが難しいと感じたりだとか、「自分の意見を表明しない」という認識を周りに持たれがちだったりとか、グローバルな環境で働いている日本人たちにとっては「あるある」的な話が山ほどあります。
このFuji Committee
では、こういった自分たちの性質に関する気づきを得たりだとか、それぞれが抱えている悩みを共有したり、その問題について議論したり、日本人がグローバルに活躍してキャリア形成を促進して行くにはどうしたらいいか、といった話をする場でした。また、シニアなメンバーが自分の培ってきたキャリアについて話す場を設けてくれて、彼らが抱えていた悩みやコンプレックス、それらをどうやって解決してきたかを赤裸々に語ってくれたりしました。自分がまだジュニアだった頃には、自分にとって凄い神のような存在である彼らが、自分と同じような悩みをかかえていて解消してきたんだという話は、とても親近感を持てますし、安心感と勇気を与えられました。
この経験からも、自分が比較的シニアになってからも、自分がかつて抱えていた仕事上の悩みやコンプレックスをジュニアなメンバーたちと正直かつ素直に共有するようになりました。
このようなある属性にフォーカスしたコミュニティは、日本人向けのものだけではなく、黒人やヒスパニック、LGBTやWIT(Women In Technology)などの様々なコミュニティが存在していました。それぞれのコミュニティで特有の悩み共有や問題解決をおこなって行くことで、それぞれの属性をもつグループが最高のパフォーマンスを出せるように改善していき、結果として会社全体への強みへと昇華して行く。このように多様性を重視して、それぞれの強みを最大限活用できるようにしていくカルチャーの大切さというのは、自分がマイノリティであることを認識してはじめて身にしみて感じました。この会社で働いて初めて、どんな人であっても多様性を構成する一部である、そんなあたりまえのことを大きな気づきとして得られた気がします。
前の会社のエンジニアリングについて
前の会社では、「コントローラーズ」と呼ばれる、トレーダーの損益を日々モニターしてリスク管理やレポーティングをする職種の人たちが日々使うアプリケーションの設計・開発・テスト・運用に関わりながら最初の10年間のキャリアを過ごしました。アナリストからアソシエイト、ヴァイスプレジデントと昇進を重ね、エンジニアとしてはジュニアデベロッパーからプロジェクトマネージャー、QAのグローバルコーディネーション、テクニカルアーキテクトと呼ばれるアプリケーションチームの技術面を支える仕事まで様々なレイヤーを経験しました。キャリアのほとんどをここのチームで過ごしたので、実はここで学んだことや経験したことが自分のエンジニアリング哲学を形成する大部分になるんですが、残念ながら会社の機密に関わる仕事が多かったため、ほとんど語ることができません(汗)
代わりに、最後の2年ほどを過ごしたプラットフォームチーム、そのなかでもOSS活動について語って見たいと思います。プラットフォームチームとは、アプリケーション開発とインフラのちょうど間くらいに位置するチームで、開発者たちが日々使うアプリケーション(たとえばSoftware Development Life Cycleのなかで重要な位置を閉めるコードレビューやビルド、リリースのプラットフォームであったり、各言語のライブラリやフレームワーク、テストのためのプラットフォーム、ジョブスケジューラー、ワークフローエンジン等々多岐に渡ります)を開発していました。ここは純粋に技術的なチームだったため、会社のビジネス業務に直接関わる機密的な話はほとんどなく、比較的表に出しやすい内容なのです。
Eclipse Collections秘話
その中でも、Eclipse Collectionsと呼ばれるJavaのコレクションフレームワークは私にものすごく関わりが深い技術で、かつOSSで公表されている技術なのでブログに記載することに何の問題もありませんw
コレクションフレームワーク誕生
このフレームワークは、もともと Donald Raab というエンジニアがコントローラーズのテクノロジーチームでCaramelという名で開発を始め、そのあと社内で広く使われるようになりました。上記の通り私もコントローラーズのチームで働いていたので、このフレームワークは当初からユーザーとして使用していました。Javaのコレクションフレームワークとしては相当使いやすい実装でかつパフォーマンスも良く、同僚たちとは「Javaに標準で入るのはいつだ」なんて冗談をよく言っていました。2012年に同社初のOSSとして公表された際は社内の皆でものすごく興奮した覚えがあります。
拡散と認知
2014年にはJavaOneで初めてDonald Raab がキーノートに登壇し、同フレームワークのセッション発表もされたことで、世界的に知られるようになりました。これを知った私はものすごく興奮し、日本でもこのフレームワークの認知を広げたいと考え、日本のJavaコミュニティへの進出の機会を探り始めました。JJUG CCC 2014 Fallに始めて参加した際に、JJUG会長の鈴木さんとお話ししたところ、驚いたことにJavaOneキーノートでの発表を見てくださっていたようで、日本支社の誰かとコンタクトを取りたいと思っていた、とのことでした。そこからはあれよあれよという間に話が進み、2015年Java Day Tokyoでの同社での初登壇が実現しました。その直後にはJJUG CCC 2015 Springでもセッションをすることができ、コミュニティによる情報拡散の手助けにより、このフレームワークは日本で一気に知名度を上げます。
この頃のコミュニティ活動は完全に社内での同フレームワークの1ユーザーとしてのボランティア活動であり、別に担当していた本業のかたわらやっていました。もともと日本では同フレームワークの開発チームはいなかったのですが、業務のかたわらちょいちょいコントリビュートを送るようにしたり、東京チームで興味のあるエンジニアにもコントリビュートを勧めたりしていました。
NYからの電話
そんな2015年夏のある日、ニューヨークから自分のデスクに一本の電話がかかってきました。
電話の主は上述の Donald Raab 、同フレームワークの創始者でありプロジェクトマネジャーです。ニューヨークのマネージングディレクターなのでめっちゃ偉い人。面識はあったものの、直属の上司ではなかったので電話で話したことはほとんどありません。いったい何の要件かと聞いてみると、
「今度同フレームワークをEclipse Foundationに移管するんだが、このプロジェクトリードをお前に任せたい。日本のお前のボスに相談しようと思うんだが、どう思う?」
と言うのです!ぶったまげましたが、同時に体の芯が震えるほど喜びました。同フレームワークはそれまで、会社内のsvnで開発・管理されているコードが社内でリリースされ、リリース時のコードのスナップショットのみがGitHubにアップロードされていると言う状態でした。つまり、GitHub上にソースコードは公開されているものの、外部からのコントリビュートを受け付けることができなかったのです。これでは完全なOSSとは言えません。Eclipse Foundationへの移管によりこの問題を解消し、完全に外部での開発ライフサイクルにのせてOSS化するというのが本プロジェクトのゴールです。
実はこの時点でUS以外での同フレームワークダウンロード数は日本がダントツ1位で、大きなユーザー数を占めていました。これはまさに上述した日本でのコミュニティ活動での成果であり、この功績を買われて白羽の矢がたったのです。
Eclipse Foundation移管と"Eclipse Collections"誕生
当然ふたつ返事でプロジェクトのリードを引き受けました。実際の移管プロジェクトでは、最新バージョンの機能開発はもちろん、同フレームワークのパッケージネーム変換から、Eclipse Foundationの開発フローに法ったワークフロー整備やツールの整備、法的書類のフォローアップなど、技術的・非技術的なチャレンジがさまざまありました。自分以外のプロジェクトメンバーは全員ニューヨーク在住、そしてEclipse Foundation本部はカナダのオタワにあるので時差の問題も大きく、電話ミーティングでのフォローアップも大変でした。2015年のJavaOneではDon Raabと共同で本件についての発表を行い(JJUG CCC 2015 Fallで発表された日本語版の同内容の資料はこちら)、2015年12月に無事移管リリースを迎えます。"Eclipse Collections"誕生の瞬間です。
当然OSSだけでは会社のビジネスに直結するわけではないので、移管リリース終了後は本業のプロジェクトも進めつつのOSS業をこなしていました。
紆余曲折を経てのEclipse Collections OSSリリースでしたが、今では開発者以外によるコミュニティでの発表も見られたりと、非常に裾野が広くなってきており、非常に喜ばしい限りです。
OSSとブランディング
こういったOSS活動は会社のブランディングとしての側面も大きいです。ここでいうブランディングは大きく2つの意味合いがあります。まず一つ目は、社外のエンジニア達に会社に興味を持ってもらうこと。つまり、会社がOSSを使うだけではなく、OSSを開発して発信することでコミュニティへの還元をしている、それくらい技術にコミットしているのだということを知ってもらうことです。もう一つは、社内のエンジニア達の士気をあげること。自分たちの会社はOSSにコミットするくらいエンジニアリングに対して本気なんだということを理解してもらい、そしてOSSという皆が平等に貢献できる土壌をつくることで、社内エンジニアリングの士気を上げて行く、空気感をつくることです。
こういった土壌はWeb系の企業だとわりと普通かつ自然に培われるものかもしれませんが、世界的な大企業であり金融機関、という環境でこのムーブメントをゼロから起こすのは相当な情熱(Passion)、忍耐力(Patience)とやり続けること(Persistence)が必要です。この3つのPは私が Eclipse Collectionsの創始者である Donald Raabから学んだ大切な哲学です。
彼は実際2004年からEclipse Collectionsのもととなるライブラリを作り始め、2012年に初めてソースコード公開、そして2015年についに会社外からもコントリビュータを受け入れられる完全なOSSとしてEclipse Foundationへの移管を完了しました(私が任されたEclipse Foundation移管のプロジェクトはこれです)。そしてDonは今現在もひきつづき同フレームワークをJavaの標準ライブラリとして取り込むための活動を続けています。これらの成果はまさに彼の情熱(Passion)、忍耐力(Patience)とやり続けること(Persistence)の賜物と言えると思います。
このOSS活動で学んだエンジニアブランディングにまつわる経験は現職でも生かされていて、現在の会社であるFOLIOで一緒にクリエーターブランディングに注力してくれているよこなさん @ihcomega のFOLIOアドカレ第一日目にもその辺の話題が書かれています。
退職直前のこと
この会社には社内SNSがあったんですが、私もそれなりに投稿してました。いわゆる社内ブログとか呟きみたいのの影響力(like数とか、コメントの数とか、投稿数とか)によってポイントランクみたいのがあって、退社直前についに8000人中の20位内に入り込みました。20位以内だとリストの一番最初のページにプロフィールが表示されるので、やめる直前でトップページに食い込めて喜んだ覚えがあります。この社内SNSのおかげで会ったことないけど私のこと知ってくれている人は世界の各オフィスに結構いて、それなりにグローバルにも影響力あったと思います。ただ、これに関しては完全に私のボスだった Donald Raab のおかげであり、彼がいなければOSSのプロジェクトリードをまかされることもなかったし、社内外に影響のあるプロジェクトを任されることもなかったと思います。彼は私のキャリアのなかで最も尊敬する人物のひとりで、技術的にもマインド的にも彼から学んだことの大きさは計り知れないです。
なぜ辞めたのか
めちゃめちゃ良さそうな会社なのに、なんで辞めたの?と疑問に思うかと思います。実際今でもこの会社のことはすごく好きですし、未だに動向はウォッチしています。
150年ほども歴史があるグローバルな会社で、仕事も楽しく待遇も良く福利厚生も良い、本当に至れり尽くせりのとても良い会社でした。とはいえ、これだけの長い歴史の中で、いろんな人たちが作り上げてきた会社、ということを肌で感じる中で、自分はただそのおこぼれを享受しているだけなのではないか、というのがここ数年のもやもや感でした。もし可能であれば、自分でもこんな素晴らしい会社・組織をつくってみたい、何よりもエンジニアが本気で活躍できるような組織や土壌というものを自分の手でつくりあげてみたい、ということをすごく考えるようになりました。
また、ここ数年日本の技術コミュニティに顔を出すようになり、日本のエンジニアたちの強さ、凄さを目の当たりにする機会が増えました。それとは裏腹に、グローバルと比較した際の日本のエンジニアたちの待遇の低さというのもよく嘆かれています。日本で埋もれているような優秀なエンジニアたちが思う存分実力を発揮して、その実力を適切な待遇で評価される、そして彼ら彼女らがグローバルで活躍できるような土壌をつくる、そんなエンジニア組織を自分でつくれたらいいなと考えていました。
そんな中FOLIOという面白い会社と出会いました。この会社と出会った当時はまだ創業1年そこそこで、社員数20数人ほどのできたてほやほやのスタートアップです。代表の甲斐もクリエーター(デザイナー+エンジニアをあわせてFOLIOではクリエーターと呼んでいます)を非常に大事にしており、次世代証券会社、そして今までの常識をくつがえすような金融プラットフォームをこのチームでつくりあげて行くんだという、そのマインドがものすごく気に入りました。エンジニアたちとも話がめちゃめちゃ盛り上がり、自分の次のキャリアを積むのはここしかないと考えました。なんせ、次世代証券会社をビジネス陣・クリエーター陣で協力してゼロからつくりあげてしまおうというスタートアップなので、私がやりたいと思っていたことにどハマりなのです。
このFOLIOが持っている多様性はFOLIO Advent Calendar 2017を見てもらえばわかると思います。現在ではクリエーター陣だけで30数人(全社では60人ほど)の組織になっていますが、その担当範囲やバックグラウンドは多種多様です。証券会社をつくるクリエーターとして、それぞれが自分のプロフェッショナリズムを発揮して仕事をしています。
そして、FOLIO Advent Calendar 2017では「株式会社FOLIOの次世代証券システムをひもとく」と題して本日の最終記事を担当したのですが、当記事でも述べたように次世代証券会社のシステムをつくりあげていくにはまだまだやることが山積みであり、とてもチャレンジングなさまざまなプロジェクトがわれわれを今後待ち受けています。
締め
以上、わりとエモい感じで前職の会社のお話と5月にジョインしたFOLIOのお話を書き連ねました。まだまだ書き足りないことはいっぱいあったんですが、さすがに長すぎると自粛しました。楽しんでいただけたら幸いです。
株式会社FOLIOの次世代証券システムをひもとく
これはFOLIO Advent Calendar 2017の25日目の記事です。昨日は弊社CTOである椎野(tshiino48)による「IOTA ~ポストスマートフォン時代のフィンテック~」でした。
本日はFOLIO Advent Calendar最終日、締めの記事を「株式会社FOLIOの次世代証券システムをひもとく」と題して、Head of Engineeringの伊藤(@itohiro73)が務めさせていただきます。
株式会社FOLIOでは、フォリオという、個別銘柄ではなくテーマを選んで投資するオンライン証券サービスを提供しています。
*図表やデータ等はあくまでもサンプルであり、将来の運用成果等を示唆あるいは保証するものではありません。また、サービスの詳細はこちら 。
本エントリーでは株式会社FOLIOを支える次世代証券システムについて、機能面とエンジニアリングの両方の視点から解き明かしていきたいと思います。
PORT + FOLIO
株式会社FOLIOには大きく分けて「FOLIO」というオンライン証券サービスと、「PORT」という証券業務システムの二つのプロダクトが存在します。
FOLIOとは
ここで解説する「FOLIO」はフォリオのサービスを構成するシステム群全体を表すプロダクト名です。まさにユーザーが直接接するプロダクトのことですね。株式会社FOLIOと同じ綴りですが、ここでいうFOLIOはプロダクトそのものの名称なのです。ちなみに、FOLIOの中にも厳密にはFOLIO-WebとFOLIO-Mobileが存在しており、FOLIO-MobileではiOS(Swift + Redux)とAndroid(Kotlin + Flux)での実装が現在開発進行中です。
今回はすでにβ版としてリリースされているFOLIO-Webに軸足をおいて説明します。 FOLIOのフロントエンドはBFF(backend for frontend) on Node + React/Redux というモダンなアーキテクチャーで構成されており、BFFの裏側はScalaのマイクロサービスで構成されたバックエンドシステムとThriftによる通信でコミュニケーションを行なっています。ユーザーの口座情報やポートフォリオなどのマイクロサービスが提供する情報をBFFが集約し、サーバーサイドレンダリングした上でユーザーに表示しています。
実際の取引には受発注処理のためのマイクロサービスの実装があり、株式会社FOLIOとお客様の相対取引を執行する店頭市場の実装、そして株式会社FOLIOが外部の証券ブローカーへFIXプロトコルを通じて取引を発注・約定するためロジックが実装されています。
現時点で20を超えるマイクロサービスが存在しており、Consulによるサービスディスカバリ、Fluentd/Kibanaによるサービスログ集約・可視化やSlackへのアラート通知、Zipkinによるサービス間トレース、Prometheus/Grafanaによるインフラ監視・ダッシュボードの整備等々、サービスの成長に従って増加していくマイクロサービスやそれを支えるインフラを管理していくにあたって重要なミドルウェア群も整備されています。
証券会社としてはかなりモダンでチャレンジングなアーキテクチャーを採用しており、エンジニアリングの側面からも非常に面白いシステム構成になっています。
FOLIOのチャレンジ
証券会社のサービスとはいえ、FOLIOの構成は一般的なWebサービスのマイクロサービスアーキテクチャーとも言えますし、ぱっと見普通のWebサービスのような開発が行われているのだろうと想像されがちです。しかし、証券の取引やお客様の資産を扱うという面において非常に繊細かつ高度な品質が要求され、通常のWebサービスと比べても機能面・技術面の両方において非常にチャレンジングな側面を持っています。
機能と技術両方において難しい一つの典型的な例をあげると、たとえばコーポレートアクションという機能。
コーポレートアクションとは、「企業の活動」という意味ですがその中でも特に有価証券の価値に影響を与える企業の意思決定のことをさし、具体的には株式分割、株式併合、株式移転・交換・合併などが含まれます。
このコーポレートアクション、機能的にはそもそも扱わなければいけない状態数が非常に多いために、UXデザイン、フロントエンドやバックエンドの要件定義と実装、テストを行うだけでも膨大なリソースと相当なエンジニアリング力が試されます。
技術的には、情報の更新タイミングを合わせるための難しさがあります。たとえば株式分割という事象では、お客様の資産において「保有株数」と「株価」というふたつの情報が違うタイミングでシステム上の変更が行われます。お客様が最終的に興味がある「保有資産評価額」はこのふたつの情報をもとに計算されます(「保有資産評価額」= 「保有株数」x「株価」)。このふたつの情報更新のタイミングの違いによりデータベース上でシステム反映時間とビジネス的な有効時間情報の両方を同時に表現する必要があり、これは技術的に非常に扱いが難しいものになります。
この問題の解決のために弊社ではreladomo-scalaというOSSを開発し、活用しています。本OSSはScala関西Summit 2017で公開され、当該セッション資料にもコーポレートアクションの例が記載されているため、興味がある方はのぞいてみてください。
www.slideshare.net
PORTとは
PORTは、顧客が日々直接接するWebサービスのプロダクトであるFOLIOとは違い、証券会社の内部業務を支えるミドル・バックオフィスシステムの総称で、株式会社FOLIO独自のプロダクト名です。実は、証券会社が業務を行う上では証券取引の性質上行わなければいけない業務や、さまざまな法律や規制に基づいて規定された必要業務が存在しており、それらを支えるPORTは株式会社FOLIOの心臓部とも言えるシステムなのです。
PORTという名前には、FOLIOというサービスプロダクトを毎日無事に出港させるための港(Port)でもあり、PORT+FOLIOが両方合わさって初めてPortfolioという概念が完成される大事なシステム、という想いが込められています。
では、PORTとは一体どういう機能を持つものなのでしょうか。すべてを事細かに紹介することはできませんが、おおよそ下記のような業務機能をサポートします(未実装の機能も含まれます)。
顧客口座管理
口座開設のための本人確認や、必要情報の整備、カスタマーサポートに必要な顧客情報等を管理します。
取引管理
約定や決済の情報管理やそれらの処理が正しく行われているかどうかの突合処理、コーポレートアクション情報の管理などが含まれます。
入出金管理
銀行とお客様のフォリオ口座の間の入出金処理や履歴を管理し、金額情報に誤りがないことを担保する重要な機能をサポートします。
報告書作成
お客様への取引報告書や法定帳票の作成をサポートします。
財務処理
自己資本規制比率を算出し、証券会社としての財務の健全性を保つための業務をサポートします。
リスクコントロール
自己勘定取引におけるポジションリスクの管理や、ヘッジのための機能を提供します。
コンプライアンス業務
コンプライアンスに関わる売買審査や社内コミュニケーション管理、監査情報の管理等をサポートします。
投資委員会業務
フォリオで提供するテーマの銘柄選定や入稿をするための業務をサポートします。
上記のように、PORTでサポートする機能は多岐にわたり、会社の業務を支える重要な要件や、法律や規定によって定められた複雑な要件を満たす必要があります。たとえば「本人確認」といったひとつの業務要件においても、顧客ユーザー、社内ユーザー、社外システム等のそれぞれのアクションによって異なる状態遷移を管理する必要があり、非常に複雑でかつ高品質を求められる業務システムになっています。
PORTのアーキテクチャとBPMN/ワークフローエンジンの活用
PORTに関しては、フロントエンドがRuby on Railsによって作られている以外はFOLIOとほとんど同じ構成で、バックエンドのマイクロサービスアーキテクチャはFOLIOと同様Scalaによるマイクロサービスが配置されており、Thriftによる通信でサービス間コミュニケーションをしています。サービス監視やインフラ構成もFOLIOと同様の構成です。
実はPORTの機能の多くはいわゆる業務システムであり、さまざまな状態遷移をもつワークフローに基づくものが多いです。このような性質をもつ業務の要件定義を行う際に、BPMN(Business Process Modeling Notation)と呼ばれるモデリング記法が非常に役立ちます。
BPMN自体は業務フローのモデル化のための記法にすぎませんが、BPMNと組み合わせてワークフローエンジンを活用することで、モデル化したフローをの大部分をコード生成し、実装コストを大幅に下げることが可能だと考えています。
FOLIOでは現在オープンソースのBPMNワークフローエンジンであるCamundaに着目しており、この技術でPORT開発と業務フローの改善ができないかの検証に着手しようとしています。
PORT+FOLIOの誕生秘話
このように、PORT+FOLIOは次世代証券会社を支えるシステム構成であり、それぞれが非常にモダンかつエキサイティングな技術によって支えられています。
しかし、PORTというプロダクト概念はまだ産まれてから半年強しかたっておらず、フロント以外のバックエンド構成はFOLIOと密に結合してしまっています。これは、FOLIOというベンチャー企業が誕生してから約一年半、これらの概念が認識される余裕もないまま一気にシステムが成長してきてしまったことに起因しています。
実は半年ちょっとくらい前まではPORTという名前も概念も存在せず、その機能の一部であり社内ユーザーが触れるRuby on Railsのフロントエンドの機能群が単に「管理画面」と呼ばれている状況で、その重要性やプロダクトとしての地位、エンジニアが機能開発にとりくむモチベーションさえもが非常に低い状況でした。これは由々しき問題です。
上記で述べたように、証券業務の機能は株式会社FOLIOの心臓部ともいえる非常に大事なシステムであり、重要な概念として認識される必要がありました。今年の春に証券会社出身のエンジニアが何名かジョインした後にこれらの概念が整理され分類されることで、顧客に提供するWebサービスと証券業務システムふたつの概念としてまずは認識されるべきだという提案が共有されました。
この概念整理に伴って証券業務システムにも愛着の持てるエキサイティングなプロダクト名が必要だろうということで社内で名前募集をした際にPORTという名前が提案されました。その提案がなされた際の、まさにその瞬間の社内Slackのキャプチャ。PORT爆誕の瞬間です。
われわれは会社全体としてビジネス陣、クリエーター陣(デザイナ+エンジニア)含めて、証券業務システムをサービスプロダクトであるFOLIOと同じくらい重要視しつつ育て上げていくというマインドを育んでいく必要があり、PORTという命名はその第一歩だったのです。
そして、最初に載せたこちらのイメージこそが、PORT + FOLIOの目指していく姿を共有するためのプロダクトビジョンでもあるのです。
PORT+FOLIOの未来
FOLIOとPORTというふたつのプロダクトの概念を説明しましたが、上でも述べたように、実はフタを開けてみるとこれらのシステムは互いに密に結合してしまっています。PORTは概念として誕生し、育まれてきましたが、まだ完全に独立したライフサイクルをもつシステム群としては存在できていません。
特にバックエンドに関してはポートフォリオ管理サービスや受発注処理サービス等、FOLIOを構成するマイクロサービス群の一部から直接のAPI提供も受けており、決済やポジション管理に必要なバッチ処理もFOLIO側のバックエンドサービスに直接接続するなど密な構成になってしまっているのです。
本来であればこれら二つのプロダクトは、全く違う対象ユーザー、システム要件、ライフサイクルの性質を持っており、それらをふまえた適切なアーキテクチャやPORT<=>FOLIO間のメッセージングの設計、また、それぞれのプロダクトに対応したエンジニアチームの編成もつくっていきたいところです。
理想としては、FOLIOは最高のUXと高品質なサービスを提供するためにアジャイルなリリースサイクルをまわしつつグロースハック施策をうつ、PORTは必要な業務要件を適切に吸い上げながら堅牢なシステム構成と高い品質を保った開発リリースをまわしつつコスト削減をめざす、といった形で全く違う開発ライフサイクルを持つべきであり、そのためにもアーキテクチャ分離がなされている必要があります。
しかし、現在のPORT+FOLIOアーキテクチャーでは適切なサービスモジュール分離と完全な開発リリースサークルの分離はまだ完全に達成されておらず、互いに密な設計、テスト、リリース計画を行う必要がある構成になってしまっています。
もちろんPORT用途のAPIサービスが一部分離されていたり、PORTフロントエンドのリリースサイクルの分離が推進されていたりと、改善してきている部分は多くあります!が、本来あるべき姿に到達するまでまだまだやるべきことは山積みです。
そして、PORTに関しては現在外部システムとの連携や外注システムの利用に頼っている部分があったり、まだ完全にシステム化されておらずマニュアル作業にたよっている機能も多く存在します。株式会社FOLIOの長期的な成長とコスト構造改善を推進していくためにも、これらのPORT機能を内製化していくことは非常に重要なアクションと言えるでしょう。
つまり何が言いたいのか...?
この記事を読んでいただいたことで、株式会社FOLIOの次世代証券システムのエンジニアリングに興味を持ったあなた、FOLIOの最高のUXづくりとアジャイルな開発プロセスを推進したいモダンな技術に興味があるエンジニアのあなた、PORTの堅牢な証券業務システムづくりに興味がある重厚感を持ったエンジニアのあなた、PORT+FOLIOのアーキテクチャ分離とリリースサイクル改善に興味をもった改善マインドを持ったエンジニアであるあなたと、われわれはぜひ一緒にこれらの施策を推進していきたいと考えております!
興味を持った方は、ぜひ下記の募集要項からご自身の興味のある職種にご応募ください。
本日は「株式会社FOLIOの次世代証券システムをひもとく」と題しまして、弊社の2大プロダクトであるFOLIOとPORTについて解説しました。これを持ちましてFOLIO Advent Calendar 2017の最終エントリーとなります。本エントリーでFOLIOに興味を持った方はアドベントカレンダーの他の記事ものぞいてみてください。多種多様な記事があって面白いですよ!
それではみなさん、メリークリスマス!素敵な年末をお過ごしください。
Scala関西 Summit 2017で瀬良さんと「Reladomo in Scala」という話をしてきました #scala_ks
9月9日に開催されたScala関西 Summit 2017に参加して来ました。私の所属する株式会社FOLIOもブロンズスポンサーとして協賛させていただきました。
Scala関西、やって参りました!へーしゃ株式会社FOLIOもスポンサーしております。求人も見てね。 https://t.co/LmO099kxep #scala_ks pic.twitter.com/b6HsZbwqpx
— Hiroshi (@itohiro73) 2017年9月9日
セッションでは、ScalikeJDBCやSkinny Frameworkでおなじみ瀬良さんと共同で「Reladomo in Scala」という話をしてきました。
www.slideshare.net
今年のカンファレンス参加はひたすらReladomoの話ばかりしている気がします…!今回はReladomoそのものの紹介と共に、ReladomoをScalaから使うためにFOLIOとグッドフロー・テクノロジーズ瀬良さんが共同で開発したラッパーライブラリreladomo-scalaの紹介をしました。詳しくはスライドをご覧ください。
今回のセッションの目玉はなんといってもreladomo-scalaをリアルタイムでGitHubに公開したことでしょうか!
FOLIOでの業務委託としてグッドフロー・テクノロジーズ瀬良さんに手伝っていただいている成果物をOSS公開するという、非常に珍しい形でのリリースかと思います。
お!!
— Yoshitaka Fujii (@yoshiyoshifujii) 2017年9月9日
きた!!
reladomo-scalaが公開されるぞ!!#scala_ks #scala_ks_main
いまからreladomo-scala 公開するぞー!!!!!!!!!!!!!!!!!!!! #scala_ks #scala_ks_main
— よこな (@ihcomega) 2017年9月9日
VSCode の公開のときみたいなやつだw #scala_ks #scala_ks_main
— むらみん (@Mura_Mi) 2017年9月9日
伊藤さん「今からここで・・・!(テンションマックス)」
— よこな (@ihcomega) 2017年9月9日
瀬良さん「あ、ちょっとまって」#scala_ks #scala_ks_main
https://t.co/95HGTKKwuC
— ねてる (@pajamaao) 2017年9月9日
reladomo-scala 公開なう#scala_ks #scala_ks_main
おお!!
— Yoshitaka Fujii (@yoshiyoshifujii) 2017年9月9日
公開されたぞ!!https://t.co/IaVG2PXZdm#scala_ks #scala_ks_main
reladomo-scala、セッション中にライブOSS公開。 🎉 https://t.co/XH0dMmO678 #scala_ks
— ザネリ🤔 (@so_zaneli) 2017年9月9日
reladomo-scala 公開したぞー!!!!!!!!!!!!!!!!!!!!https://t.co/PCmb7tGMCW#scala_ks #scala_ks_main
— よこな (@ihcomega) 2017年9月9日
giter8のテンプレートも同時に公開しているので皆さんぜひご活用ください。
こちらのコマンドで動かしてみてください。
# sbt new folio-sec/reladomo-first-example.g8
前にもどこかで言ったかもしれないですが、「バイテンポラルデータモデル」という名前とその概念が当たり前のように認識される世界が来るとエンジニアの世界はもう少し幸せになるかもしれないと思っています。金融に限らず履歴や有効期間の概念を扱わなければいけないシステム開発要件は山ほどあるはずなので。
Reladomoのキラーコンテンツであるバイテンポラルデータモデルを理解するにはJJUG CCCのこちらのスライドがおすすめ。
www.slideshare.net
Reladomo本体の基本的な使い方を理解するにはこちらのスライドがおすすめ。
www.slideshare.net
OSSはコミュニティの輪が広がってなんぼなので、皆さんぜひReladomoやreladomo-scalaを使って盛り上げていきましょう!
当日の様子はこちらから。
今回Scala関西 Summit初参加でしたが、Scala界隈の初めてお会いする方々といろいろ話すことができて、非常に有意義な時間を過ごせました。運営の皆さん、素晴らしいカンファレンスを開いていただきありがとうございました。本当にお疲れ様でした!!!
おまけ:懇親会2次会で食べたセル牡蠣、お刺身、ヒレ酒がうまかったぞ〜。
ヒレ酒~。めっちゃいい薫り!! pic.twitter.com/Ksl5GCcRvx
— Hiroshi (@itohiro73) 2017年9月9日