Hello world! コンテンツ・メディア第1事業部のjyukutyoこと阪田です。

弊社フリューはJJUG CCC 2016 fallのスポンサーになりました!

http://www.java-users.jp/ccc2016fall/

JJUG CCCは毎年2回、春と秋に開催する日本最大のJavaコミュニティイベントです。Java関連の技術や事例に関する良質なセッションが行われ、また異なる分野で活躍するJava技術者が一堂に会する場ともなっています。

スポンサーにはダイヤモンド、プラチナ、ゴールド、ブースと種類があり、フリューはゴールドです。

CCCでは弊社から2セッション!

弊社では2セッションでスピーカーをします。

  • 13:30-14:20
  • ルームE
  • 10年運用している画像サービスでのJavaの活用と今後の展望

こちらは、盛岡が弊社のサービス開発について話します。私がCCCの2016 Springと2015 Springで話した弊社のサービスについて、別の観点から話します。盛岡はこのサービスを初期(10年前!)から担当しており、ユーザの拡大につれて変遷してきたアーキテクチャや運用、チーム構成などさまざなまリアルが聞けます!

  • 18:30
  • ルームI
  • バイトコードが君のトモダチになりたがっている

こちらは私のセッションです。Javaにおけるバイトコードって何?というところからそこを変更するライブラリの紹介、実例としてJava Agentでのバイトコード操作といったことを話します。

ぜひ私たちのセッションに足をお運びください!

エンジニア募集中

弊社では他にもScalaMatsuri、Scala関西 Summit、try! Swiftなどのスポンサーをしています。それもこれも最終的にはエンジニアの採用のためですね。京都での開発業務(Webアプリ、iOS/Androidネイティブアプリ)に興味のある方はぜひご連絡ください。

http://mobile-career.furyu.jp/

京都以外に渋谷にもオフィスがあり、開発をしています。関東の方もぜひご連絡ください。


Posted bySatoKeita


こんにちはフリューのジョンです。普段はJMockitを使ってテストコードを書いているのですが、今回は、そのJMockitを使ってテストを書いていて、困った2点を書きたいと思います。

  1. すでに@Injectableで書いてあるクラスをモックしたい
  2. FileOutputStreamをJMockitでMockUpを使ってコンストラクタをモックする

この2つです。例えば以下のようなメソッドをテストした場合はどうすればよいのかを書きます。

環境

JDK 1.8.0_65
JMockit 1.18
JUnit 4.12

解説

1. すでに@Injectableで書いてあるクラスをモックしたい

これは、テスト対象のクラスに@Resourceがあるため、@Injectableを書いているため起きます。
@Mockedを引数に書いてつかうこと、MockUpを使ってやることもできません。(すでにモックされています、というエラーが出ます)

対応としては、ソースコードの①にある通り、Expectationsにクラスを渡すことで、Fileのモックを対象のExpectations内で書くことができます。
今回はコンストラクタをモックしたかったので、返り値となるFileのモックオブジェクトを@Injectableで定義してExpectations内で使用しています。

2. FileOutputStreamのコンストラクタをモックする

これはFileOutputStreamに@Mockedをつけてモックすることができないために起きます。(@Mockedは使えません、代わりに@Injectableや部分モックを使ってくださいというエラーが出ます)

対応しては、ソースコードの②にある通り、MockUpで記述します。
closeメソッドもモックしているのはtry-with-resource文にて自動的にcloseメソッドが呼ばれるためです。(もちろん無いとテストが落ちます)

ここで気をつけることが、一点あります。
実際のコードでは、ファイルを引数としたコンストラクタを使用していますが、テストコードでは異なるコンストラクタをモックします。理由としては以下になります。

To mock the target “FileOutputStream(File)” constructor, JMockit must replace the internal call to another constructor (“this(file, false);”) with an innocuous “super(…)” call. This is because, inside a constructor, the JVM forbids extra calls before a “super(…)” or “this(…)” call is complete. So, in this case calling “proceed()” from $init has no effect since there is no remaining code in the original constructor.

https://groups.google.com/forum/#!topic/jmockit-users/EvTuJKTecDk

意訳すると、以下のようになります。

JMockitでFileOutputStream(File)をモックするためには、内部でsuperによってFileOutputStream(File, false)を呼び出しているのでそちらをモックしてください。 理由としては、JVMによってsuper(…)またはthis(…)の余分な呼び出しが解消されます。だから、MockUpのproceed()で呼ばれる$initは存在しないコンストラクタを参照してしまうのです。

まとめ

JMockitは複数の書き方ができますが、それ故にわからなくなることも多いと思います。
みんな細かい部分で詰まっていると思いますので、ソースコードを共有していければ解消できるかもしれないですね(切実)



みなさん、こんにちは。コンテンツ・メディア第1事業部インフラ担当の藤本佳世です。
今回もOpenStackの続き、【Open vSwitchのVRRP(L3HA)で高可用性を実現する!】についてお話しします。
前回の記事はこちら

VRRPとは

VRRP(Virtual Router Redundancy Protocol)とは、ルータの冗長化をサポートするプロトコルです。
フリューでは、OpenStackで立ち上げた仮想インスタンスに接続する際に、このVRRP機能を採用しています。
仮想インスタンスへはFloatingIPでssh接続しています。
その通信が、Neutron(networknode)サーバでプライベートIPアドレスにNAT変換され、仮想インスタンスに接続されます。この処理を行うNeutron(networknode)サーバ2台をVRRP構成にすることで、master側のサーバに障害が発生した場合、すぐにstandby側のもう1台のサーバに自動的に処理を切り替えることができます。

<イメージ図>

openstack_vrrp

構築について

公式ドキュメントを参考に構築を進めました。
ネットワークの知識が必要なので、慣れないうちは苦労するかもしれません。

サーバ構成について

コンポーネント ノード
サーバ1 Keystone/Horizon/Nova/Cinder/Glance/Neutron
※1 MySQL/Memcached/RabbitMQ
※2 Heartbeat
controllernode
サーバ2 Keystone/Horizon/Nova/Cinder/Glance/Neutron
※1 MySQL/Memcached/RabbitMQ
※2 Heartbeat
controllernode
サーバ3 Nova/Cinder/Neutron computenode/storagenode
サーバ4 Nova/Cinder/Neutron computenode/storagenode
サーバ5 Nova/Cinder/Neutron computenode/storagenode
サーバ6 Nova/Cinder/Neutron computenode/storagenode
サーバ7 Neutron networknode
サーバ8 Neutron networknode

ネットワーク作成について

ネットワーク名 ext-net80 vxlan-net80
プロジェクト admin service
ネットワーク種別 flat vxlan
外部ネットワーク external1
割り当てサブネット ext-subnet80
10.0.80.0/24
vxlan-subnet80
192.168.80.0/24
Gateway 10.0.80.1 192.168.80.1

※1ネットワークセグメントのみの情報です。実際には同じような構成で3パターン作成しました。

設定内容

各ノードに対して設定を行います。ポイントとなる部分を書き出してみました。

用語の説明

NAT変換 IPアドレスを変換する技術のこと
VXLAN L3ネットワーク上に論理的なL2ネットワークを構築するトンネリングプロトコルのこと
ML2プラグイン OpenStack Networking で、複雑な実世界のデータセンターで使われている様々な L2 ネットワーク技術を同時に利用できるようにするフレームワークのこと
GRE ML2 がサポートするネットワークセグメントタイプの一種
Flat ML2 がサポートするネットワークセグメントタイプの一種
DHCP IPアドレスなどを自動的に割り当てる仕組みのこと

controllernode

共通のオプションを設定します。

ML2プラグインを設定します。

networknode

Open vSwitchエージェントを設定します。

L3エージェントの設定します。

DHCPエージェントを設定します。

メタデータエージェントの設定します。

computenode

Open vSwitchエージェントを設定します。

ネットワークの作成

次は、ネットワークの作成の手順をご紹介します。
フリューでは、3つのネットワークセグメントが存在し、それぞれ仮想ルータを3台構築しました。

下記の通り、flat外部ネットワークとプロジェクトネットワーク(vxlan)を作成しました。

ネットワーク名 ext-net80 vxlan-net80
プロジェクト admin service
ネットワーク種別 flat vxlan
外部ネットワーク external1
割り当てサブネット ext-subnet80
10.0.80.0/24
vxlan-subnet80
192.168.80.0/24
Gateway 10.0.80.1 192.168.80.1

※1ネットワークセグメントのみの情報です。実際には同じような構成で3パターン作成しました。

flat外部ネットワーク作成

  1. 外部ネットワークを作成 ※下記の例では「ext-net80」というネットワーク名で作成しました。
  2. 外部ネットワークのサブネットを作成

プロジェクトネットワーク(vxlan)作成

  1. serviceプロジェクトのidを取得
    ※ここではすでにserviceプロジェクトが存在することを前提としています。
  2. ネットワーク作成
    ※先ほどとは異なり、network_typeにvxlanを指定します。
  3. サブネットを作成
  4. 仮想ルータの作成
  5. サブネットを仮想ルータにインターフェースとして接続
  6. ルータに外部ネットワークへのゲートウェイを追加

HA確認

  1. 下記のコマンドでVRRP(active,standby)構成になっていることが確認できます。
  2. networknodeで、qrouterとqdhcp名前空間の作成を検証します。
    ※両方のqrouter名前空間で同じUUIDが使われているはずです。

  3. networknodeでHAの動作確認をします。
    ※qrouter 名前空間には ha, qr, qg インターフェースがあります。バックアップノードはqrとqgインターフェースはIPアドレスを持ちません。

    ha インターフェース 169.254.192.0/18 の範囲の一意な IP アドレスを持つ
    qr インターフェース プロジェクトネットワークのゲートウェイ IP アドレスを持つ
    qg インターフェース 外部ネットワーク上のプロジェクトルーターの IP アドレスを持つ

  4. Networknodeで、マスターノードの HA インターフェースの IP アドレスから VRRP 通信が行われていることを確認します。

冗長化することは、どんなシステムでも重要視される部分ですね。

最後に

今回初めてVRRPという単語を聞いた私にとっては、かなり苦戦する部分ではありましたが、何とか構築することができました。ネットワーク知識が必要となりますが、少しでも構築の参考になれば嬉しいです。


Posted bySatoKeita

2016年10月28日

SatoKeita

Vue.jsを使ってみよう

カテゴリ
JavaScript

こんばんは、フリューのジョンです。普段はサイト開発をしており、最近Mithril.jsからVue.jsに移行しています。

今回はVue.jsのお話です。
JavaScriptのフレームワークっていっぱい聞くけどどういうところが嬉しいんだろう、というレベルの方にお伝えしたいと思います。

概要

Vue.jsはReact.jsやAngular.jsと同じようにMV*系のフレームワークです。
近年はJavaScriptのフレームワークは飽和していますが(React.js、Angular.js、Riot.js、Aurora.jsなどなど)
それではなぜそのようなフレームワークを使うのでしょうか?jQueryじゃダメなのでしょうか?

 

いえ、jQueryでもできます。しかし、既存のフレームワークのほうが楽をすることができる可能性が高いです。

  • view周りをフレームワークに任せることができるので、logicに集中できる。
    • jQueryのみで大きなページを作成すると、logicとview部分が複雑になる。
  • 要素をJavaScriptで作成している複雑なページは、仮想DOMを使用しているフレームワークを使うと、描画速度が早くなる

一つ目の利点について考えてみます。
例えば以下のようにselectタグの値によってコンテンツの表示切り替えを行いたい場合、どのようにjQueryを使えば良いのでしょうか?

おそらく、コード量を抑えるとしたら以下のように書くと思われます。他を消してから、選択されたものを表示するということになると思います。

では、少し意地悪な変更が加えられたとしましょう。現状idを使って選択していますが、このidは一般的すぎて変更したいですね。
その場合、JavaScript側のセレクタも変更しなければいけません。それに加え、optionタグのvalueを使って切り替えられると思っていたlogicの前提が崩れてしまいます。*1

フレームワークを使うと、このような画面切り替えなどもフレームワーク側が対応してくれるのです。(後にこの問題をVue.jsではどう記述するかを説明します)

そこでVue.jsです

では他のフレームワークではなく、Vue.jsを使う利点はなんでしょうか?私としては、以下だと考えられます

  • viewをhtmlに書くことができるので、viewを分業しているプロダクトではデザイナさんが変更しやすい

Angular.jsでも同じようなことができるのでは?となりますがVue.jsではよりデザイナさんが理解しやすいhtmlの書き方になっています。また、軽量な画面に関しては、少量のコード量で済みます。
そして、仮想DOMを用いているため描画スピードも早いです。

Vue.jsを使ってみよう

先ほどのselectタグの値によってコンテンツの表示切り替えを行いたい場合を題材にします。

各コンテンツにidを書かないようになりました。その代わりにv-showという表示、非表示を切り替える際に使う「ディレクティブ」をつけています。
selectタグにもidを書いていません。その代わりにv-modelという双方向のデータバインディングを行うディレクティブをつけています。

JavaScript側では、Vueに、rootとなるタグのセレクタと他のデータを渡しています。これだけです。

  1. Vueのコンストラクタに渡したオブジェクトにあるdataにはcontentが1で入っています。これがselectタグに渡り、どのoptionタグを表示するかを決めています。
  2. selectタグのonchangeイベントが走るとoptionタグのvalueがcontentに入ります。
  3. contentの値が変化すると、各コンテンツに書いてあるv-showの評価が行われます。その評価値がtrueになった場合、そのコンテンツが表示されるという仕組みになっています。

どうでしょうか?
idの変更やデザインの変更がある場合もこれならば影響はありません。

まとめ

jQueryだけでもできるページは少なくありませんし、広く一般的になっていると思われます。しかし、少し複雑度が増してくると、途端にコード量が増えていきます。

これまでの例からもVue.jsはかんたんに導入できることがわかりましたので、何か面倒だなと感じたときには、ぜひライブラリやフレームワークを使ってみてください。

次はVue.jsを使った少し複雑な例をお送りしたいと思います。

おまけ

今回のソースコードについてはjsfiddleにあげておりますのでご確認ください。
https://jsfiddle.net/satojohn/x9fjajuL/

参考

Vue.jsのドキュメント : https://vuejs.org/

 

*1 もちろんdata属性を使うなど回避策はありますが、ここではわかりやすいようにしています。



Hello world! コンテンツ・メディア第1事業部のjyukutyoこと阪田です。

前回はコレクションのセッションについてのレポートでした。今回はクラスローダーのセッションについてレポートします。

Join the War on ClassLoader Leaks

スライドはこちらにあります。

https://static.rainfocus.com/oracle/oow16/sess/1461358415846001E0TZ/ppt/Classloader%20leaks%20public.pptx

内容

java.lang.OutOfMemoryError(OOME)はみな出会ったことがあるだろう。Java 8になってPermGen spaceとは出なくなったが、代わりにMetaspaceが出るようになった。ローカルでの開発時にも、継続的デプロイでもOOMEは出る。このセッションのアジェンダは次のようなものだ。

  • OOMEは何を意味するのか?
  • OOMEはなぜ起こるのか?
  • どこでリークするのか?
  • どのように避けるのか?
  • OSSの例
  • トレーニング

OOMEは何を意味するのか?

JVMのメモリはヒープとPermGen/Metaspaceとスタックである。スタックはスレッドごと、ローカル変数やメソッドのパラメータを持つ。ヒープはオブジェクトのインスタンスを持つ。PermGen/Metaspaceはjava.lang.Classインスタンスなどを持つ。PermGenという名前はJava EEとクラスのアンロードができる前に名付けられた。Java 8でMetaspaceへ置き換えられた。OOME PermGen space/Metaspaceは、あまりに多くのクラスをロードしたときに起こる。

OOMEはなぜ起こるのか?

OOME PermGen space/Metaspaceが起こる原因は次の2つが考えられる。

  1. アプリケーションが大きすぎる。Java 7までは-XX:MaxPermSize=256Mなどと指定すればよい。Java 8ではMetaspaceは自動的に増えていく。
  2. java.lang.Classインスタンスが再デプロイ後にガベージコレクトされない

参照の型は次の4つがある。

  • 強い参照:到達可能なときは決してGCされない
  • ソフト参照:OOMEになる前にGCされる
  • 弱い参照(WeakHashMap):強い参照とソフト参照がないときにGCされる
  • ファントム参照:GCは妨げない

GCの到達可能性は以下の図のようなことである。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-05-17-40-30

再デプロイでは、新しいWARファイルをデプロイすれば前のクラスローダはGCされる。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-05-17-43-30

クラスローダーの参照は次の図のようになる。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-05-17-44-55

どのようにリークが起きるのか。GCルートからクラスローダへ到達可能だと、クラスローダをGCすることができない。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-05-17-45-38

どこでリークするのか?

Tomcatなどアプリケーションサーバのバグだ。他にCommons LoggingやLog4Jなどロギングフレームワークのバグ、Bean Validation APIやUnified Expression Languageのバグといったこともある。

GCルートでも起こる。まず、システムクラスローダによってロードされるクラス。JDKのクラスにおけるstaticフィールドで起こる。ライブスレッドではローカル変数、メソッドのパラメータのスタック、java.lang.Threadインスタンスで起こる。

システムクラスでは、java.sql.DriverManagerやBean introspectionのキャッシュ、シャットダウンフック、カスタムでデフォルトにしたAuthenticator、カスタムのセキュリティでのProvider、カスタムMBean、カスタムのThreadGroup、カスタムのプロパティエディタ…最初の呼び出し元でのcontextClassLoaderへの参照などで起こる。

DriverManagerをもう少し詳しく見てみよう。たとえばMySQLのJDBCドライバを使うときはこうするだろう。

com.mysql.jdbc.Driverクラスにはstaticイニシャライザがある。

registerDriver()メソッドは以下のようになっている。

このregisterDriversという変数は、以下のものだ。

図で表すと以下のようになる。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-06-16-55-03

そのため、コンテキストのシャットダウンで以下のように書かなければ、リークしてしまう。

スレッドもここで止めておかなければならない。次のようにスレッドを実装するのは悪いアイデアだ。

止められるようには以下のようにする。

このことは、次の記事に紹介されている。

Heinz Kabutz / Java Specialists’ – The Law of the Blind Spot

次にThreadLocalについて考えてみよう。次のように実装してはならない。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-06-17-09-13

ThreadLocalのJavaDocには以下のようにある。

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; …

各スレッドは、スレッドが生存していてThreadLocalインスタンスがアクセス可能である間は、スレッド・ローカル変数のコピーへの暗黙的な参照を保持します。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-06-17-13-40

プールされたスレッドではスレッドはクラスローダより長生きする。ThreadLocalはThreadGlobalになる!

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-06-17-17-35

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-06-17-18-20

ThreadLocalMapのJavaDocには以下のようにある。

However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space

(私訳)しかしながらリファレンスキューが使われないので、テーブルが容量不足になり始めたときだけ失効したエントリが削除されることが保証されます。

参照を含んだカスタムの値は、staticなThreadLocalであればリークし、そうでなければ予測されていないGCとなる。カスタムのThreadLocalであればリークしない。

ThreadLocalはクリアするようにしよう。

実際には、多くのOSSライブラリが違反している。

どのように避けるのか?

リーク防止のライブラリがある。アプリケーションサーバ非依存、Tomcatよりも多くをカバーしており、ログで警告を出力、ライセンスはApache 2ライセンスだ。実行時の依存は0で、設定は必要ない。Mavenであれば以下のように依存ライブラリを追加する。

これはServlet 3.0以降用だが、2系のものもある。

OSSの例

Bean Validationの1.0.0.GAでは以下のようになっていた。

(私訳)List<ValidationProvider>はキーであるクラスローダに強い参照を持っている。JPAのCachingPersistenceProviderResolverと同じモデルを使うようにして。

jyukutyo調査

Bean Validationの1.1.0.GAではこのクラスは次のようになっています。

providersPerClassloader.put( classLoader, new SoftReference<List<ValidationProvider<?>>>( providers ) );とあるので、ソフト参照を使うようになったようです。

JPAのCachingPersistenceProviderResolverクラスも調べてみました。

その他のOSS

javax.el.BeanELResolverやorg.apache.cxf.transport.http.CXFAuthenticator、com.sun.star.lib.util.AsynchronousFinalizerも該当する。

トレーニング

Eclipse Memory Analyzer(MAT)を使おう。

http://www.eclipse.org/mat/

次のオプションをつけてアプリケーションを実行しよう。

感想

最後に出てきた、MATについてはこのブログで私が記事を書いています。

ヒープダンプをEclipse Memory Analyzerで解析しよう!

セッションとしては非常におもしろかったです。とくにOSSの実例を挙げながらリークの原因を解説する箇所ではとても興奮しました。こういう内容のセッションはJavaOneだと他にもいろいろとあります。なかなか普段の勉強会でこの分野を扱っていることは少ないと思いますので、興味がある人にはJavaOneはとても楽しめるカンファレンスです!



Hello world! コンテンツ・メディア第1事業部のjyukutyoこと阪田です。

前回はAngular 2とGWTのチュートリアルセッションをレポートしました。今日はJDKの開発者であるStuart Marksさんのセッション”Collections Refueled”をレポートします。

Collections Refueled

スライドはこちらにあります。
https://stuartmarks.files.wordpress.com/2016/09/collectionsrefueled-final.pdf
タイトルの通り、Javaのコレクションフレームワークについての話です。その歴史とJava 8での拡張、Java 9での拡張と将来の作業についてでした。

内容

JDK 1.0はレガシーコレクション、VectorやHashtableなどである。1.2はコレクションフレームワークが導入された。インタフェースとしてCollection、List、Set、Map、Iteratorが、具象クラスとしてArrayList、HashSet、HashMap、TreeSet、TreeMapがある。5.0ではジェネリクス、java.util.concurrentパッケージが追加された。

Java 8

Java 8ではラムダとストリームが追加され、インタフェースにデフォルトメソッドとstaticメソッドが定義できるようになった。15年経って初めての変更だ。

 


CollectionはIterableのサブインタフェースなので、これはすべてのコレクションで動作する。
Iteratorインタフェースでは、ほとんどのイテレータは削除をサポートしていない。そのためこう書く必要があった。



remove()のデフォルトメソッドがまさにこれだ。削除できないイテレータを作るときは、単にremove()を省くだけでよい。削除できるものを書くときは、remove()メソッドをオーバーライドするだけだ。

Collectionインタフェースでは、removeIfでバルク更新ができるようになった。


もしコレクションがArrayListなら、7の場合の計算量はO(n^2)であり、8の場合はO(n)となる。

List

ListインタフェースのreplaceAllもバルク処理だ。


ただし、要素の型を変更することはできない。そうしたいときはStreamを使う。

List.sortはCollections.sortよりなぜよいのか?Collections.sortは3つのステップを使う。

  • 一時的な配列にコピーする
  • 配列をソートする
  • リストにコピーして戻す

List.sortはデフォルトでは上記と同じだが、ArrayList.sortはオーバーライドしてin-placeでソートするため、コピーはしない。Collections.sortは現在単にList.sortを呼び出すだけだ。

Map

MapインタフェースにもforEachがある。



replaceAllもある。



Java 7までMulti-mapはとても扱いづらかった。Multi-mapはキーに対して複数の値があるマップだ。

Java 8ではこうなる。

Comparator

Comparatorを書いて楽しい人はいる?Comparatorは多くの条件とコードの繰り返しだ。Java 8はComparatorにstaticとデフォルトメソッドを追加した。名字とnullがある名前でソートし、nullを最初に持ってくる2レベルのソートのサンプルを見てみよう。まずはJava 7から。

Java 8ではこうできる。

Java 9

JEP 269: Convenience Factory Methods for Collectionsだ。こういったAPIが追加される。

設計と実装の課題としては以下のものがあった。

  •  Immutability
  •  Iteration Order
  •  Nulls Disallowed
  •  Duplicate Handling
  •  Space Efficiency
  •  Serializability

新しいstaticファクトリメソッドが返すコレクションはイミュータブルとなる。これは従来の不変性であり、不変の永続性ではない(addなどはUnsupportedOperationExceptionをスローする)。不変性はよいものである。一般的な場合、コレクションは既知の値で初期化した後、変更することはない。不変であれば自動的にスレッドセーフになる。効率、とくにスペースについて改善するチャンスがある。JDKには一般的な目的のための不変コレクションはない。

Setの要素とMapのキーのイテレーションの順序については、HashSetやHashMapであれば公式には順序が保証されないとしていたが、長い間たいてい一貫していた。これを変更すると、順序に依存しているコードは動作が変わってしまう。適用するのは新しいコレクション実装だけにする。既存のコレクションは同じままとなるだろう。

ListやSetの要素、Mapのキーや値にnullを許可しない。NullPointerExceptionをスローする。1.2でコレクションにnullを許可したのは失敗だった。Java 5以降コレクションではnullを許可していない。とくにjava.util.concurrentのコレクションではそうだ。nullはNullPointerExceptionの原因だからだ。

Setの要素やMapのキーで重複したときはIllegalArgumentExceptionをスローする。”コレクションのリテラル”で重複があるのはプログラミングのエラーである可能性が高い。理想的にはこれをコンパイル時に検出する。値はコンパイル時の定数ではない。次にいいのは、実行時の生成においてフェイルファストにする。

2つの文字列の要素を持つSetを考えてみる。

どのぐらいスペースを使っているのか?オブジェクトを数えてみる。

  • unmodifiableなラッパーが1つ
  • HashSetが1つ
  • HashMapが1つ
  • 長さが3のObject配列のテーブルが1つ
  • Nodeオブジェクトが2つ、各要素に1つずつ

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-03-14-36-08

サイズの見積もりとしては、オブジェクトごとに12バイトのヘッダーがある(64ビットJVMで32GB未満のヒープ、OOP圧縮あり)。int、float、参照フィールドごとにプラス4バイトだ。ということは、先ほどのオブジェクトを合計すると152バイトになる。

 

  • unmodifiableなラッパー: ヘッダー + 1フィールド = 16バイト
  • HashSetが1つ : ヘッダー + 1フィールド = 16バイト
  • HashMapが1つ: ヘッダー + 6フィールド = 36バイト
  • 長さが3のObject配列のテーブル: ヘッダー + 4フィールド = 28バイト
  • Nodeオブジェクトが2つ、各要素に1つずつ: (ヘッダー + 4フィールド) * 2 = 56バイト

フィールドベースのSet実装だとオブジェクト1つとフィールド2つで20バイトとなる。

 

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-03-14-43-09

実装はすべてstaticファクトリの背後にあるプライベートなクラスとしている。

コレクションはすべてシリアライズできる。ただ、デフォルトのシリアライズ形式は内部実装について漏らしてしまっていた。新しいコレクションの実装は、カスタマイズしたシリアライズ形式となる。

将来的な作業

これらはまだ計画されていない将来的な作業だ。

VectorやHashtableといったレガシーなコレクションは非推奨にする。LinkedListもそうするかもしれない。

コアなコレクションのイテレーション順序をランダムにする。新しいミューテータのデフォルトメソッドを追加する。ArrayDequeへのインデックスアクセスを追加する。

JEP 269のコレクション拡張では、パフォーマンスの改善と、順序があるSet/Mapを追加する。

Project ValhallaでのValue typesなど。

感想

Stuartさんのセッションは熱い感じで印象深かったです。これはStuartさんの最後のセッションでしたが、他2つ、Stuartさんのすべてのセッションを聴きました。

このセッションの内容としては、Javaプログラミングで使わないことはほぼないであろう、コレクションについて歴史をたどれ、興味深いものでした。

JEP 269については、以前私は個人ブログにて動作確認していました。

そのためAPIの追加は知っていました。しかしほかにも数多くの改善があり、とくにコレクションが使うスペースについて理解が深まったのはうれしいです。



Hello world! コンテンツ・メディア第1事業部のjyukutyoこと阪田です。

前回の投稿ではJavaOneへの行き方を中心に書きました。今後数回に渡っていくつかのセッションの内容を紹介します。

Building Angular 2 Applications in Java 8

チュートリアルセッションです。Angular 2とGWT、Spring Bootでアプリケーションを作るには、という内容でした。スライドはこちらにあります。
http://lteconsulting.fr/angular2boot-javaone/#/
Angular 2とGWT 2.8を使う際の問題点と解決策が中心となっています。

内容

Angular 2はJavaScriptに加えてTypeScriptをサポートしている。Modules、Components、Dependency Injection、Change Detectionが基本的なものである。さらにJSR-269のPluggable Annotation Processing APIを使ってコードを生成する。バックエンドは、シンプルで強力なSpring Bootを使う。

GWTは今のWebからは遠いところにあるものなのは事実だ。GWTにあるものは、以下のもので置き換えられる。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-29-12-59-15

ただ、GWT 2.8は将来の過渡期にある。

  • JsInterop
  • Java 8対応
  • Widgetや古いものをなくす

GWT 3はJ2CL (Java to Closure) となり、今標準となっている多くの機能をなくす。

GWTはモダンで最適化されたJavaScriptへコンパイルしてくれる。JsInteropはJavaScritpとJavaコード間を双方向で統合する。JavaScriptのXMLHttpRequestは以下のようなJavaクラスとなる。
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-29-13-16-14
これを次のように使うことができる。
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-29-13-16-27
そしてSuper Dev Modeには開発用の早いインクリメンタルなコンパイラがある。

JSR-269のPluggable Annotation Processing APIでは、独自のアノテーションプロセッサを登録する。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-29-13-23-34

そして、Angular2GwtというGWTとAngular 2を統合するライブラリを使う。 Angular2Gwtがすべてやってくれる。User moduleなしのGWTのコンパイルは早い。アプリケーションに依存ライブラリを1つ足すだけでできる。

アーキテクチャは次のようになる。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-29-13-28-11

デモアプリケーション

セッションではデモもありました。デモのリポジトリはこちらです。
https://github.com/ltearno/angular2boot-demos

デモアプリケーションはいくつかあります。ここではセッションで実行していた”my-first-tree-orchard”を動かしてみましょう。

$ cd my-first-tree-orchard
$ mvn spring-boot:run

僕の環境ではjava.lang.IllegalArgumentException: Can not copy a non-root Methodが出ましたが、起動しました。http://localhost:8080/にアクセスすると、”Application is loading”が出た後、次の画面になります。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-29-13-53-23

あとは”Roll 6 faces dice”ボタンを押せば動作します。

さて、target/generated-sourcesディレクトリを見ると、コードが生成されています。
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-09-29-14-00-06
ライブラリがPluggable Annotation Processing APIを使って生成したコードですね。

このままではコンポーネントのテンプレートを変更しても、即座に反映されません。Super Dev Modeを使ってみましょう。spring-boot:runはしたままで、別のウィンドウを使って同じディレクトリで次のコマンドを実行します。

$ mvn gwt:run-codeserver
...
[INFO] Super Dev Mode starting up
...

Super Dev Modeが起動しました。画面をリロードします。”Compileing Application”と表示されるようになります。

最後に、起動時のjava.lang.IllegalArgumentException: Can not copy a non-root Methodですが、古いバージョンのSpring Loadedの問題のようです。angular2boot-demosディレクトリにある親pom.xmlのspringloadedを1.2.0から現時点で最新の1.2.6にすると、例外は出力されなくなりました。

感想

Angular 2にもGWTにもほとんど知識はありませんでしたが、おもしろいアプローチではあると感じました。現時点ですぐにJavaにすべてを寄せるアプローチを使う予定はないのですが、こういった方法もあることは知っておいてよいですね。



Hello world! コンテンツ・メディア第1事業部のjyukutyoこと阪田です。

私は昨年に続き、今年もJavaOneに参加しました。

JavaOneとは

JavaOneをご存知の方はここは読み飛ばしてくださいね。

JavaOneは、オラクル社(以前はサン・マイクロシステムズ)が1996年から毎年開催している、Javaに関する最大級のカンファレンスです。サンフランシスコで5日間、400以上のセッションがあります。朝から晩までJavaに浸っていられる、夢のような内容となっています。

毎年9月、10月といった秋に、ユニオンスクエアの近くにあるヒルトンやPark 55といったホテルや、国際会議場モスコーン・センターを会場としています。なお、同時にオラクルのOpen Worldというカンファレンスも開催されています。ビジネスとしてのメインはOpen Worldの方ですね。

モスコーン・センターは道を封鎖して、期間中ずっとイベント会場となっています。
img_2211img_2212

英語について

海外のカンファレンスに参加する際に、私も含めみんなが気になることは英語だと思います。もちろんまったく下地がない状態でセッションの英語を聴くのはおそらく難しいです。ただ、普段から技術文書を少しでも英語で読んでいて、セッションの対象となる技術分野をある程度知っていれば、スライドの文章もあるので大まかには理解できます。

私自身は海外留学の経験もなく、英語が話せるわけではありません。ですがJava Magazineを英語で読んでいたこと、去年参加してからNHKラジオのストリーミングを毎日聴いていたことで、昨年より聞き取ることができるようになりました。

費用について

次に気になる点は費用だと思います。参考までにだいたいの費用を載せますね。

飛行機

日本からサンフランシスコへ直行便を利用すると、2016年9月現在では往復15万円ほどかかります。飛行時間は行きが10時間、帰りが12時間です(関西空港-サンフランシスコ国際空港)。

なお、乗り継ぎ便を利用すると、現実的な乗り継ぎで10万円となり、5万円抑えることができます。私は今年香港経由でサンフランシスコへ行きました(業務での出張ではありましたが)。直行に比べて7時間ほど余計にかかりました。ただ、行きは朝早くの出発、帰りは深夜出発で、とても時差調整がしやすかったです。行きのサンフランシスコ到着時間や帰りの関西空港到着時間も、直行便を利用するときとほぼ同じで出発時間が早くなるだけです。すると帰りの1日分宿泊費がかからないため、ホテル代2,3万円を抑えることができます。

想定費用:10万円〜15万円

img_2198

ホテル代

サンフランシスコは大変物価が高いところです。ホテルも同様で、通常でも日本のビジネスホテルより設備が悪いところが1万円以上します。そして、数万人集まるOpenWorldとJavaOneの期間中は、ホテル代が高騰します。最低でも1泊2万円はかかると考えてください。ただし、安くて会場に近いホテルはすぐに埋まるため、よほど早く準備するか、会場からとても遠いホテルでないとこの価格では泊まれないと考えてください。

私は1ヶ月前にホテルを予約しましたが、会場から5分のところでやや広い部屋しかなく、1泊3万5千円でした。会場からかなり遠いところであれば、2万5千円もあります。Uberを使えば20ドル前後で会場まで移動できるので、その方法を使うのも一手です。その分移動に時間は取られてしまいますが…

宿泊期間は土曜日から金曜日まで、6泊となります(日曜日から木曜日の夕方までが開催期間)。前述の通り、木曜日の夜便を使えば5泊にできます。

想定費用:13万円〜20万円(3万円前後 * 5,6泊)

img_2200

パス代

JavaOneのパスは、直前に購入すると2050ドル、つまり20万円以上します。ですが、早期に購入すると割引があります。登録開始から1ヶ月ほどは、1650ドルでした。4万円抑えられます。ただ、まだJUGディスカウントもあります。JUG、つまりJavaユーザグループにはディスカウントコードが発行され、それを使うとさらに200ドル安くなります。JavaOne登録開始から数ヶ月経った後JUGに配布されますので、日本のJUG、JJUGあるいは関ジャバのTwitterアカウントをフォローしておくとよいかと思います。関ジャバの会長は私ですので、私に直接問い合わせていただいても大丈夫です。

ただし、スピーカーになればパスが無料でもらえます!スピーカーは公募制、Call for Papersを通過すれば誰でもなることができます。

このパスには、月曜日から木曜日のランチ、日曜日の夜のWelcomeレセプション、水曜日の夜のAppreciation Eventが含まれます。食事代がいくらか浮きます。

想定費用:15万円〜20万円

総費用

合計すると安くても38万円となります。安くはない金額ですね。また、食事も高い物価の影響で、ファーストフードであっても10ドルはします。レストランでは30ドルは見ておいたほうがよいです。

どんなことが得られるのか?

まず、日本でこの規模のカンファレンスはありません。そして、JavaOneには世界各国から人が集まってきています。日本人とか外国人とかそういったものは関係なく、すべての人が英語で発表し、質問もします。日本語母語の人も、英語でスピーカーをしています。これが世界的には普通だという感じられます。みなネイティブのように話すわけではありません。きれいな発音でなくても、堂々と大きな声で質問しています。参加すれば、きっと自分もそうなるのが普通であり、これからも技術と英語を深めていけばそうなる、と思えます。これは、言葉で読むよりももっと大きなインパクトです。私は若い方ではありませんが、とても大きな体験でした。できる限り若い年齢で行ってみれば、さらに大きな体験となります。少し無理をしてでも、ぜひ行ってみてほしいです。

もちろん、JavaやJVMの最新情報を手に入れることができます。それだけでなくJava Platform GroupチーフアーキテクトであるMark Reinholdさんなどに直接質問できるQ&Aセッションもあります。また、JCP(Java Community Process)メンバーになっていれば、夜に会員限定のパーティもあり、各国のJUGメンバーやJava Championに話かけることができます。こういったことは、ここに来ないとできません。

ただ参加するだけではそう得られるものはありません。年間を通じてJavaコミュニティに関わっていればいるほど、JavaOneで得られるものは指数関数的に大きくなります(私自身もまだまだです)。JavaOneとJavaコミュニティへの参加を通じて、フィードバックサイクルを回しながら進めば、とんでもないところに行けるのではないかと思っています。

次回予告

次回からは時系列順ではなく個別のセッションをいくつかピックアップして記事を書いていきます。お楽しみに!



AWSにはS3やCloudFrontという良いものがあり、サーバーを持たぬ者でも静的コンテンツを大規模に効率良く配信する事が可能です。
S3は大容量のコンテンツサーバー、CloudFrontは多拠点型のキャッシュサーバーで、共にAWSのサービスです。
一昔前はこのような事ができるのは一部の人間に限られており、良い世の中になったなあと思います。

S3とCloudFrontで静的コンテンツを扱うと、コンテンツ更新時のキャッシュコントロールに難しさがあります。
「新しくファイルアップロードしたのに、なんでヒョージ変わらへんねん」というアレです。
CloudFrontのキャシュの生存期間はコントロール可能で、TTLの設定を変えてみたり、AWSのコンソールでキャッシュの削除(invalidation)したり。
色々方法はありますが「コンテンツが更新されたら、キャッシュも更新される」をLambdaで自動化できるっぽいのでやってみました。

Lambdaとは

Lambda(ラムダ)を使うと、AWSで起こる色々なイベントを起点にプログラマが書いたプログラムを実行する事ができます。
プログラム実行に使うコンピュータをプログラマが意識する必要は無く、むきだしのサーバーはAWSに隠蔽されている事から「サーバレス○○」だ!と呼ぶ人もいます。
Lambdaはトリガー的に使えるので、invalidateを自動化するプログラムを書けばなんとかなりそうです。

 

 

ロールの作成

Lambdaの実行とCloudFrontの操作が必要になるので、これが可能なロールをIAM(権限コントロール)から作成しておきます。

Lambdaの実行権限から

cf5

ラムダ基本実行権とCFへのフルアクセスを選択します。

cf8
こんなかんじのロールができればオッケーです。

cf10b

LambdaFuntionの設定

AWSコンソールからLambdaFunctionの作成へと進みます。
BluePrint(一般的な利用用途のテンプレ)を使わずに「Configure triggers」に進みます。

cf1
AWSのどのサービスでどのような状態変化が起こった場合にトリガーを発動するのか、を設定します。
ここではS3を選択します。

cf1-5
「furyu-sandbox」というS3バケットに「拡張子がjpgのファイルが作られた(Object Created(All))」場合の設定はこんなかんじです。

cf2
いよいよラムダ関数の登録です。

実行環境はPython2.7を選択します。
コードは下記をそのままコピペして、XXXXXXXXXXの箇所をCFのディストリビューションIDに書き換えます。

 

print()された文字列は、CloudWatchでログとして出力されます。(後述)

先ほど作成したロールを選択します。
こんなかんじになります。

cf11
作成したLambdaFunctionの実行とモニタリング

ファイルS3バケットにアップロードし、反応を観察してみます。
S3へのファイルのアップロード後、作成したFunctionを選択して「Monitoring」タブを選択します。
LambdaFunctionの実行状況が確認できます。

cf12
「View Logs in Cloud Watch」で、詳細なログを確認できます。

cf13

printの内容やエラーもここに出力されます。

cf14

 

CloudFrontの対象ディストリビューションのInvalidationsタブにInProgressが見えます。

cf15

キャッシュ削除が起動された事が確認できます。

 

「雲の上のクラウドに向かってラムダ式を唱えると全ては計算された。サーバーから開放された人々は最も人らしくなった」
ミステリアスなイメージを持つラムダですが、利用用途は無限、うまく使うとAWSと仲良くする事ができます。
日宇


Posted bySatoKeita

2016年09月27日

SatoKeita

SPAJAM決勝に参加しました

カテゴリ
参加レポート

はじめましての方も、そうでない方も、こんにちは。フリューの佐藤こと、ジョンです。
遅くなりましたが、SPAJAM決勝戦に参加してきましたので、ご報告です。
予選については、こちらをご覧ください。SPAJAMについては、http://spajam.jp/をご覧ください。

予選から本戦にむけての準備

以前の予選を踏まえ、振り返ってその対応を試みました。

  • そもそもツールが使いこなせていなかった
    • 勉強するのにも時間が足りないため、使い慣れているツールに変更しました
  • アイデアが少し弱かった
    • 予めアプリの方向性を考えておく。今回はVRを使ったアプリにしていこうという方向性

果たしてこの方法が正しいことなのかは、さておき。
今度はディスプレイを持たず、新調してもらったMacBookをもって出発です。

本戦当日

会社の都合上、前日は金沢の旅館にいましたので、朝早く北陸新幹線で移動となりました。旅館から、旅館へ。温泉から、温泉へ。
向かう先は、埼玉県熊谷市ホテルヘリテイジ 四季の湯温泉です。
熊谷までつくとSPAJAM用の臨時バスが来ましたので、それに乗りました。

本戦会場は地下でした(高所恐怖症の方は安心です)

会場の雰囲気

荷物をとりあえず置いてから、別部屋に移動後、各チームの紹介、テーマ発表、アイデアソンという流れでした。
この辺りはニコニコ生放送でも放送されていたようです。
DSC_0817[1]

浴衣来てますが、すぐ脱ぎます。テレビ用ですね。
お昼もおいしく頂きました。ビールも提供されていました。ですが、流石にこの後二日間あると思うと飲めませんでした。

一日目の昼食
今回のテーマは「」でした。
予選よりも抽象度が高く、アイデアソンでのアイデア出しは、困難を極めました。

単純に音を鳴らす、ということではダメだろうという話はチームでも出ていましたが、打破する案も浮かばず。
ということで、前準備で考えていたVRを使った○○ということで、無理矢理にでも進めました。

各人の担当は、前回とほぼ同じくではありますが、今回はVRと言うことで、OpenGLを使ったプログラミングです。
行列の計算やシェーダの実装などを行いました。テーマに沿うように、音声認識もつかいました。

OLYMPUS DIGITAL CAMERA

目の前に温泉がありつつも黙々と作業をしていきます。
1日目の夕食、2日目の朝食、昼食……と食事はすべて用意されていたので、ありがたかったです。

予選と同様に、時間がないうちにどれだけ完成に近づけるか、プレゼンで発表される部分を意識して作成をしていきました。
そして、やはり予選と同様に、最終的には各自、自分の担当範囲だけではなく、今何が足りていないのか、時間が決まっているなかで、何ができて、何ができないのかを考え行動していけていました。

2日目の夜には、各チーム作成したアプリのプレゼンテーションをしました。その後、懇親会も踏まえ実際にアプリを触ってもらえる実演会が用意されていました。
同時に他チームの方との交流もでき、アプリも触ることができました。

他チームも、技術レベルが高くおもしろいアイデアのアプリを作られていました。もちろん、プレゼン途中でアプリが動かなかったりなどのハプニングもあり、同時にハッカソンの難しさを味わいました。

そして、時間が過ぎると、ハッカソンイベントの主要部は終了となります。

ようやく温泉にありつけました。部屋も用意されてあります。
ちなみに、温泉に入るタイミングは自由ですし、皆がアプリを必死で作成している間に温泉に入っても何も問題ありません。

成果物について

今回作成したアプリは、以下です。

logo01

音(歌)から広がる世界を楽しめるアプリというコンセプトで進めました。子供向けのVRという、将来的な展望も兼ねていました。
VRゴーグルは、Google Cardboard V2です(目はデザイナさんに描いてもらってます)。こちらを装着してVRの世界を見て回ります。

img_20160914_191945

始める前にまずBGMを選択します。

screenshot_20160914-172114

そうすると、そのBGMにあったVRの世界が表示されます。
スマートフォンには2つの画面が表示されますが、VRゴーグルを通すと2つの画面の差分により立体視ができます

screenshot_20160914-172727

技術的な部分としては、先ほど書きましたが3DCGを描画するためにOpenGLを使用しています。
他には、GoogleのCLOUD SPEECH APIを使っており、特定のキーワード(例えばリス、くま、など)に対して中の動物たちがリアクションを取ってくれたり、音楽が変わったりします。

結果

結果発表は、プレゼンテーションから夜が明けた3日目。会場を東京浅草にある HULIC HALLに移し行われました。
その前に、結果発表の前にドローンを使っての写真撮影をしました。すごかったです(詳しくは http://spajam.jp/final/result/ に動画がありますのでこちらを参照ください)。

本戦計12チームがありましたが、結果

ファイナリスト賞

を頂きました。大文字で書いておきながら、残念ながら、いわゆる参加賞です。

審査員の方から評価を聞いたところ、やはりアイデアでの点で弱かったようです。

ちなみに、最優秀賞は ちゅらゴンズさん の 音録(オドロク)でした。

感想

業務ではWebアプリケーションを触っており、スマートフォンのアプリ開発に携わることがありませんでした。
しかし、今回の予選から本戦までを体験でき、以下の知見を得ることができました。

  • 会社の中でも特に関わりが少ない人とのチームを組めた(大きな組織になってくるとなおさら!
  • 今までやったことのない技術を使う機会があった(特に本戦で使用した音声認識は個人的にはとても興味深く、業務にも活かせそう
  • 社外の方との交流を持てた(名刺は持って行きましょう!

以上いろいろありましたが、プラスしか無いです。(もちろん、疲れたとかはありますが
興味のある方は、次回SPAJAMに参加してみてください!

%e9%9b%86%e5%90%88%e5%86%99%e7%9c%9f