読者です 読者をやめる 読者になる 読者になる

vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

JJUG CCC 2017 Springに行ってきました

はじめに

5/20に行われたJJUG CCC 2017 Springに参加してきました。

www.java-users.jp

感想を一言で言えば、行ってよかった楽しかった!ということに尽きます。
…まぁもう少したくさんの方と話できればよかったとか、後述する懇親会でのLTの話とか思うことも色々ありはしますが。

せっかくなので、各セッションの感想などを書いてみたいと思います。

なお、スライド資料は下記を参照することにします。

JJUG CCC 2017 Springスライドまとめ(随時更新) #jjug_ccc - メンチカツには醤油でしょ!!

※各感想は私の理解に基づくもので、今後資料を見直していく中で間違いを見つけましたら修正する予定です。

G+H 1: 非機能要件とSpring Boot

まず非機能要件とは何か、という話からでしたが、
よくよく考えてみると機能要件と非機能要件の分類って難しくね?という話に。

この分類についてIPAの非機能要求グレードが紹介され、
非機能要求を段階的に細分化していく様子を見せてくれていました。

後半は、それらをSpring Bootで実装する方法について。

ここで登場していたSpring Actuatorは、前から気になっているSpring Securityなどに加えて、自分でも触ってみたいと思いました。

G+H 2: Vue.js + Spring Bootで楽しくフルスタック開発やってみた

フロントエンドでVue.jsを使っていくための構成や、Spring Bootに組み込む上で効率的なビルド方法などについて紹介されました。

変更を即時反映するところなどはAngularなどでも同じような機能が用意されているため、
Vue.jsだけでなく、今のJSフレームワークを使って開発する場合に参考にできる、と感じました。

F 3: SpotBugs(FindBugs)による大規模ERPのコード品質改善

FindBugsをForkしたSpotBugsがなぜ生まれたのか、また数ある静的解析ツールの中から、
なぜSpotBugs(FindBugs)が選ばれたのかなどが紹介されました。

現状では、静的解析ツールは一つだけでなんでも賄うのではなく、
状況に合わせて組み合わせるしかないこと、また開発効率が落ちると使ってくれなくなるため、速度重視の解析と網羅性を高めた解析を組み合わせる、という話が印象的でした。

G+H 4:Javaエンジニアに知って欲しいRDBアンチパターン

DBというのはアプリケーションのコードやサービスより長く使われることが多いため、
最初の設計がまずいと後々大きな負債になってしまう、という話からスタート。

RDBアンチパターンを面白く紹介しつつ、しっかりと最初に設計を行うこと、
また状況に合わせてリファクタリング(移行期間はアプリケーションのコードなどと比べ長くかかる)を行う必要がある、ということを説明されていました。

また、モニタリングの監視を行うことで、
障害が発生する前にあらかじめ対処しておく必要がある、というところも印象的でした。

E5: Javaで実装して学ぶOAuth 2.0!

OAuth2.0の基礎からJava EEでどう実現するかを紹介されていました。

まずは認可と認証の違いや下記の4人の登場人物と、それぞれの役割について。

  1. リソースオーナー
  2. リソースサーバー
  3. クライアント
  4. 認可サーバー

そして認証、認可の流れを噛み砕いて説明、という内容でした。

OAuth2.0の仕様としてすべてが決まっているわけではなく、
実現しなければいけないことだけが決まっていて、具体的にどうするかは決まっていない、という部分があるのが印象的でした。

また、いくどとなく仕様を正しく理解する必要性を強調されていたのも印象的でした。

柔らかく解説されていたとはいえ、完全に理解するためにはもう少し今回のスライドも含めて資料を読み込む必要があると思いますが、
どういうことを理解していかなければいけないのか、という概要はわかったような気がしました。

また、合間合間の時間確認とともに差し込まれるCM(スポンサー枠の発表でした)が、
だんだん笑いを誘うようになってきたあたり、(狙っていたのかはともかく)とてもうまいと思いました。

対象が異なるとはいえ、どうしても同じような内容になってしまうCMを、楽しく聴くことができたので。

あと、最後のデモは見ているだけで胸が痛く。。。(´・ω・`)

A+B 6: Java8プログラミング ベストプラクティス & きしだが働いてるかどうかIDEのメモリ使用状況から機械学習で判定する

2本立ての内容で、まずはNetBeansのメモリ使用状況を元に、
どのようなデータを使ってNetBeansを活発に使用している・していないかを判断したのか、またその判定の方法が紹介されました。

諸事情から内容がコンパクトになったとのことでしたが、しっかりオチもあっておもしろかったですw

2つ目はプログラミングベストプラクティスで、全体的なところでは可能な限りImmutableにすること、
Java8以上の話ではStream、Optionalを中心に紹介されました。

下記は特に印象に残っています。

  • 変数にダミーで値を入れて、状況に応じてその変数に再代入するより、変数をカラにしておいて、条件に応じて値を入れる方が最適化されやすい
  • (StreamのforEachの中でif文を使う処理の話で)Optionalは、要素数が一つだけのStreamと見なして、同じような処理を行うことができる。

M 8: Ordinary Object Pointer について調べてみた

Javaのオブジェクトの内部表現であるOrdinary Object Pointerのお話。

例えばString型の変数は内部的にどう表現され、それは何Byteになるのか、というような内容。

正直なところ知識がなさすぎて、ただただなるほど〜と感心するばかりでした。

とはいえ、プログラムの中身が実際にはどうなっているのか、という興味はあるので、
今後もう少し時間をかけて学んでいってみたいと思います。

懇親会LT

懇親会では、飛び込みLTができる、ということでここ最近挑戦しているWebSocketをテーマにLTやってみました。

speakerdeck.com

しかし結果は。。。

5分を想定していたら、3分とそれより短かったことが最大の敗因ですが、
冒頭に余計な話をしすぎてしまったような気がします。

今後、LTの時間がわからない場合も資料としてはともかく、話す内容としてはもう少し的をしぼって3分で収まるように作りたいと思います。

また、PCをインターネットに接続し忘れていたため、上記資料が途中から表示されなくなる、という問題もありました。

おそらくキャッシュが途中で消された?のかと思いますが、
ローカル環境にPDF自体は保存していたため、画面に映す資料としてはそちらを使うようにしたいと思います。

ちなみに、Demoで使用した寿司の3Dモデルは下記を使用しています。

https://www.assetstore.unity3d.com/jp/#!/content/37401

懇親会冒頭で寿司で盛り上がる(寿司スポンサーのLINEさん、ありがとうございます!)など、
不安もいっぱいでしたが暖かい反応をいただけて嬉しかったです。

おわりに

気にはなっていたものの、手が出せていなかったこと(例:Spring Security、OAuth2.0)についての話が聴けたので、
どんどん触っていきたいという気持ちが強まりました。
(から回ってる気はしますが)

次回は(採用される・されないはともかく)CfPを出せるよう、もっと突っ込んで調べたり、作ったりしていきたいと思います。

JJUG CCCは初の参加でしたが、今回からは託児所が導入されたり、
参加人数が1000人を超えるなど、ただただ感心するばかりです。

これも幹事の方々や、ボランティアスタッフの方々、スポンサーの方々のおかげだと思います。 本当にありがとうございました!

SpringでSTOMPを使わずにWebSocket

はじめに

前回はサーバー側をGolangにしていましたが、やっぱりSpring Bootでも実現したい!ということで、
今回はSpring BootでSTOMPなしでWebSocketを使い、Unityからアクセスできるようにしてみます。

なお今回の内容は、下記を参考にしています。

最小(と思う)構成

まずUnityからのアクセスによって接続を確立し、メッセージを受け取ってみます。
これをできるだけ少ないコードで再現しようとすると、こんな感じに。

build.gradle

buildscript {
    ext {
        springBootVersion = '1.5.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}
dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-websocket')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}
  • spring-boot-starter-websocketを追加しています。

WebsocktestApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebsocktestApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebsocktestApplication.class, args);
    }
}
  • Applicationクラスです。SpringInitializrで生成したクラスから特に変更はしていません。

WebSocketConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(messageHandler(), "/ws");
    }
    @Bean
    public WebSocketHandler messageHandler() {
        return new MessageHandler();
    }
}

STOMPを使った場合と同様に設定クラスを用意します。

詳細は後述しますが、指定したURL(localhost:8080/ws)にWebSocketを使ってアクセスがあった場合の登録処理を行います。

MessageHandler.java

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class MessageHandler extends TextWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 接続が確立されたら呼ばれる.
    }
    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        // WebSocketクライアントからメッセージを受信した時に呼ばれる.
    }
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        // 接続が切られたら呼ばれる.
    }
}
  • こちらも詳細は後述しますが、接続を確立したときやクライアントからメッセージを受け取った場合のイベントハンドラーです。
    設定クラス(WebSocketConfig.java)から使用します。

メッセージの送受信

STOMPを使う場合は、接続確立時にSubscribeを行うことで、他のクライアントからメッセージを受信した場合にそれを受け取ることができていました。

ただ今回はその方法が使用できないため、接続確立時にWebSocketSessionの情報を保持しておき、
メッセージを受け取ったらメッセージの送信者以外にメッセージを送信するようにしてみます。
※下記よりスマートな方法があるような気はしますが、記事投稿時点で他に思いつきませんでしたorz

MessageHandler.java

import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.ArrayList;

public class MessageHandler extends TextWebSocketHandler {
    private ArrayList< WebSocketSession > users;
    public MessageHandler(){
        users = new ArrayList<>();
    }
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 接続確立時、配列にWebSocketSessionの情報を追加.
        if(users.stream()
                .noneMatch(user -> user.getId().equals(session.getId()))){
            users.add(session);
        }
    }
    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        // メッセージを受け取ったら送信元以外にメッセージを送る.
        users.stream()
                .filter(user -> !user.getId().equals(session.getId()))
                .forEach(user -> {
                    try{
                        user.sendMessage(message);
                    }
                    catch (IOException ex){
                        System.out.println(ex.getLocalizedMessage());
                    }
                });
    }
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        // 接続が切れたら配列から削除.
        users.stream()
                .filter(user -> user.getId().equals(session.getId()))
                .findFirst()
                .ifPresent(user -> users.remove(user));
    }
}

これでとりあえず複数アプリからlocalhost:8080/wsに接続すると、
メッセージを送り合うことができるようになりました。

次は設定クラスが継承しているWebSocketConfigurer.javaと、イベントハンドラーのTextWebSocketHandler.javaの処理を追ってみることにします。

WebSocketConfigurer

まず設定クラスから追いかけてみます。

WebSocketConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(messageHandler(), "/ws");
    }
    @Bean
    public WebSocketHandler messageHandler() {
        return new MessageHandler();
    }
}

ここでは後述するハンドラー(WebSocketHandler)の登録と、Bean定義(DIコンテナに追加する)を行っています。

「registry.addHandler(messageHandler(), “/ws”)」で、
localhost:8080/wsにアクセスした時のハンドラーを登録しています。

またBean定義についてですが、今回DIを使っていないので、下記のようにしても動いたりします。

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry.addHandler(new MessageHandler(), "/ws");
}

このクラスが継承しているWebSocketConfigurer.javaは、ハンドラー登録の関数(registerWebSocketHandlers)だけを持つインターフェイスです。

では、この引数であるWebSocketHandlerRegistryを追ってみましょう。

WebSocketHandlerRegistry

WebSocketHandlerRegistry.java

package org.springframework.web.socket.config.annotation;

import org.springframework.web.socket.WebSocketHandler;

public interface WebSocketHandlerRegistry {
    WebSocketHandlerRegistration addHandler(WebSocketHandler webSocketHandler, String... paths);
}

関数addHandlerの戻り値になっているWebSocketHandlerRegistryを見てみます。

package org.springframework.web.socket.config.annotation;

import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

public interface WebSocketHandlerRegistration {
    // ハンドラーの追加.第二引数でアクセスポイントを指定.
    WebSocketHandlerRegistration addHandler(WebSocketHandler handler, String... paths);
    // ハンドシェイク(WebSocketの接続開始時に行う通信)に対するHandlerのセット.
    WebSocketHandlerRegistration setHandshakeHandler(HandshakeHandler handshakeHandler);
    // ハンドシェイクの前後に処理を追加するためのインターセプター.
    WebSocketHandlerRegistration addInterceptors(HandshakeInterceptor... interceptors);
    // Originヘッダー値の設定.
    WebSocketHandlerRegistration setAllowedOrigins(String... origins);
    // Sock.jsを使用する.
    SockJsServiceRegistration withSockJS();
}

ということで、設定クラスでは主にハンドラーの設定を行っています。

TextWebSocketHandler

ではそのハンドラーについて。

まずはもとのコードから(Sessionの保持やメッセージの送信などは省略します)。

MessageHandler.java

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class MessageHandler extends TextWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 接続が確立されたら呼ばれる.
    }
    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        // WebSocketクライアントからメッセージを受信した時に呼ばれる.
    }
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        // 接続が切られたら呼ばれる.
    }
}

次に継承しているTextWebSocketHandler.javaを見てみます。

TextWebSocketHandler.java

package org.springframework.web.socket.handler;

import java.io.IOException;

import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;

public class TextWebSocketHandler extends AbstractWebSocketHandler {

    @Override
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
        try {
            session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Binary messages not supported"));
        }
        catch (IOException ex) {
            // ignore
        }
    }
}

ここではバイナリメッセージが送信された場合に、そのセッションを閉じています。
このようにしている理由は、WebSocketHandlerが文字列にしか対応していないため、ということのようです。

最初から実装していないと、バイナリメッセージが送信された場合に受信できずエラーになる、ということなのでしょうか。

※2017.05.17更新
バイナリメッセージが送信された時にセッションを閉じるのは、
このクラスがTextWebSocketHandlerの名前の通りテキストデータをやりとりするためのものなので、
それ以外のデータは受け付けませんよ、ということのようです。

失礼しましたorz

続けてAbstractWebSocketHandler.javaを見てみます。

AbstractWebSocketHandler.java

package org.springframework.web.socket.handler;

import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PongMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;

public abstract class AbstractWebSocketHandler implements WebSocketHandler {

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    }
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception {
        if (message instanceof TextMessage) {
            handleTextMessage(session, (TextMessage) message);
        }
        else if (message instanceof BinaryMessage) {
            handleBinaryMessage(session, (BinaryMessage) message);
        }
        else if (message instanceof PongMessage) {
            handlePongMessage(session, (PongMessage) message);
        }
        else {
            throw new IllegalStateException("Unexpected WebSocket message type: " + message);
        }
    }
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    }
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
    }
    protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
    }
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
    }
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    }
    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}

ほとんどカラですが、handleMessageで受け取ったメッセージの型に合わせて処理を振り分けていますね。

では大本のWebSocketHandler.javaです。

WebSocketHandler

WebSocketHandler.java

package org.springframework.web.socket;

public interface WebSocketHandler {
    // 接続確立後のイベント.
    void afterConnectionEstablished(WebSocketSession session) throws Exception;
    // メッセージを受け取ったときのイベント.
    void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception;
    // メッセージのやり取り中のエラーをハンドリングする.
    void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;
    // 接続を切ったときのイベント.
    void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;
    // サイズが大きすぎる場合などに分割されたメッセージを扱うかどうか.
    boolean supportsPartialMessages();
}

おわりに

Golangもそうですが、シンプルなコードで通信ができてしまうのはすごいと思いました(小並感)。

次回は下記のような部分をなんとかしたいと思います。 * セッション情報をハンドラークラスから分離する * Webブラウザから特定のページにアクセスしたときに、今接続しているアプリの情報(IDや数など)や送信しているメッセージの表示を行う

あとはUnity側から送信するメッセージの量をもっと増やした場合でも、問題が起きないかなども気になるところですね。

参照

UnityでWebSocketを使ってみたい

はじめに

前回に引き続きWebSocketについてのお話。

ですが、今回はUnityからwebsocket-sharpというライブラリを使ってWebSocket Clientとしてアクセスします。

そして、アクセスする先はgorilla/websocketのExamplesにあるチャットです。

Springはどうしたんじゃいという話なのですが、C#でSTOMPを使う方法がわからなかったため、
とりあえず今回はGolangのものを使用することにしました。

こちらについては問題が解決すれば別途ブログにまとめたいと思います。

websocket-sharp

まずUnityからWebsocketでアクセスするのに利用する、websocket-sharpの準備です。

Unityから呼び出せるようにするために、dllファイルを作成する必要があります。

上記GitHubのReadmeの通りなのですが、一応手順を載せておきます。
(Jetbrains Riderで実行しました)

  1. プロジェクトをダウンロードしてソリューションファイルを開きます。
  2. ソリューションが5つ(websocket-sharpとExample4つ)あり、その中の「websocket-sharp」の上で右クリック->「Build selected projects」でビルドします。
  3. ビルドに成功したら、websocket-sharp > websocket-sharp > bin > Debug(or Release)にwebsocket-sharp.dllが出力されるため、UnityプロジェクトのAssets以下に置きます。

  4. Step2で、プロジェクト全体に対してビルドすると、Exampleのどれかでエラーになります。

で、Websocketでアクセスするコードはこんな感じです。

WebsocketAccessor.cs

using System;
using System.Collections.Generic;
using UnityEngine;
using WebSocketSharp;

public class WebsocketAccessor : MonoBehaviour
{
    private void Start()
    {
        // サーバー側ではlocalhost:8088/wsにアクセスした時に登録処理を行う.
        using(WebSocket ws = new WebSocket("ws://localhost:8088/ws")){
            // 接続開始時のイベント.
            ws.OnOpen += (sender, e) =>
            {
                Debug.Log("Opended");
            };
            // メッセージ受信時のイベント.
            ws.OnMessage += (sender, e) =>
            {
                Debug.Log("Received " + e.Data);
            };
            // 接続.
            ws.Connect ();
            // メッセージ送信.
            ws.Send ("世界さん、チーッす!");
        }
    }
}

パスワードもないものにアクセスするだけとはいえ、シンプルですね。

サーバー側ではメッセージの送受信をbyte型のスライスで行っていますが、
Unity側からはstring型のデータを渡すことができます。

また、このUnityプロジェクトをビルドして複数起動すれば、Unityアプリ同士でメッセージの送受信も行うことができます。便利!

Json

さて、上記のように単純にメッセージを送り合うだけなら問題がないのですが、
例えば3Dモデルの座標値を送りたい場合。

前述の通りメッセージとしてstring型のデータを扱えるため、JsonUtilityを使ってJson形式に変換し、それを送信してみたいと思います。

まずJsonに変換するデータを格納するためのクラスを用意します。

ObjectStatus.cs

using System;

[Serializable]
public class ObjectStatus
{
    public float PositionX;
    public float PositionY;
    public float PositionZ;
}
  • クラス名については気にしない方向でお願いいたします(白目)

で、入れ物が用意できればあとはデータを詰めてJsonに変換するだけです。

WebsocketAccessor.cs

using System;
using System.Collections.Generic;
using UnityEngine;
using WebSocketSharp;

public class WebsocketAccessor : MonoBehaviour
{
    public GameObject TargetModel;
    private WebSocket ws;
    private ObjectStatus objectStatus;

    private void Start()
    {
        objectStatus = new ObjectStatus();

        ws = new WebSocket("ws://localhost:8088/ws");

        ws.OnOpen += (sender, e) =>
        {
            Debug.Log("Opended");
        };
        ws.OnMessage += (sender, e) =>
        {
            Debug.Log("Received " + e.Data);
        };
        ws.Connect ();
    }

    private void Update()
    {
        if (Input.GetMouseButtonUp(1))
        {
            // データをクラスにセット.
            objectStatus.PositionX = TargetModel.transform.localPosition.x;
            objectStatus.PositionY = TargetModel.transform.localPosition.y;
            objectStatus.PositionZ = TargetModel.transform.localPosition.z;

            // セットしたクラスを使ってJson形式に変換.
            var json = JsonUtility.ToJson(objectStatus);
            // 変換したデータを送信する.
            ws.Send (json);
        }
    }
    private void OnDestroy()
    {
        // 接続を切る.
        ws.Close();
    }
}
  • 今回は右クリック時にメッセージを送信しています。

送信したデータは下記のような内容となります。
特に送信するデータが多い場合など、要素名は短くしておいた方が良いかもしれません。

{"PositionX":1.84,"PositionY":1.28,"PositionZ":0.0}

おわりに

今回はとりあえずサンプルを試しただけで、あまり中身に触れられませんでした。

ということで、次回あたりでもう少し触れてみたいと思います。

また、サーバー側の処理についても少し追いかけてみたいと思います。

参照

SpringBootでWebSocketを使ってみたい(Sample Backend編)

はじめに

チャットのように、Webサーバーを通してデータの受け渡しをリアルタイムで実施するための技術にWebSocketがあります。

今回はこれをSpringBootから利用する方法として紹介されていた下記の内容について調べつつ、
いじってみたりすることにします。

Keywords

WebSocket

まずWebSocketについてですが、超乱雑にまとめると

  • 最初にクライアント側とサーバー側を連携し、その後は接続・切断をせず小さいオーバーヘッドでやり取りを行う
  • クライアント側からだけでなくサーバー側からもデータ(メッセージ)のやりとりをリクエストできる

といったところでしょうか。
頻繁なメッセージのやりとりが必要なチャットなどを作るときに利用されます。

詳しくは下記のようなページをご覧ください。(内容が間違っていたり、あまりにも説明が不足していることがわかれば後日修正します)

今回のサンプルではsockjsを利用しています。

STOMP

WebSocketでクライアント側とサーバー側でやりとりをするためには、
お互いに共通した方法(プロトコル)を持っている必要があります。

サンプルでは、このためのプロトコルとしてSTOMP(Simple (or Streaming) Text Orientated Messaging Protocol)を使用しています。

正式名称の通り、テキストベースのメッセージでやりとりを行います。

メッセージのやりとりの流れは最初にSubscribeを行い、
更新があればそれを受け取る、ということのようです。

このSTOMPをWebSocket上で使用するため、STOMP.jsを利用しています。
一点気がかりなのは、GitHubを見るとメンテナンスが止まってしまっていること。

みなさんどう対処しているのか(´・ω・`)

。。。とりあえず今回はStomp.jsを使用することとします。

クラス構成

上記のサンプルに含まれるクラスは以下の通りです。

  • src
    • main.java.hello
      • Application.java ・・・ アプリケーションのメインクラス
      • Greeting.java ・・・ Subscriberに送信するメッセージを保持するモデルクラス
      • GreetingController.java ・・・ メッセージのルーティングを行うコントローラークラス
      • HelloMessage.java ・・・ メッセージをクライアント側から受信する時に、メッセージ内容(名前)を格納するモデルクラス
      • WebSocketConfig.java ・・・ エンドポイントの登録、メッセージのやり取りを行うWebSocketMessageBrokerの設定を行う設定クラス

この内「Application.java」はSpringBootの開始を担うメインクラス、「Greeting.java」「HelloMessage.java」はデータを保持するモデルクラスです。

ここからは「GreetingController.java」「WebSocketConfig.java」について追いかけてみます。

GreetingController

コントローラークラスです。

GreetingController.java

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class GreetingController {
    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) throws Exception {
        // 1秒待ってから応答する.
        Thread.sleep(1000);
        // メッセージ内容を"/topic/greetings"のSubscriberに渡す.
        return new Greeting("Hello, " + message.getName() + "!");
    }
}
  • @Controllerアノテーションを付与することで、通常のルーティング処理に加えてメッセージのルーティングも行うことができます。
  • @MessageMappingアノテーションを付与することで、今回は「/hello」にメッセージが送信されたときに関数「greeting(HelloMessage message)」が呼ばれます。
    このときメッセージの内容は、「HelloMessage.java」に格納された形で引数として渡されます。
  • @SendToアノテーションを付与することで、戻り値であるメッセージの内容を「/topic/greetings」をSubscribeしているユーザーに伝えることができます。
  • 「Thread.sleep(1000);」は無くても動作します。

なおマッピングできるパスの形式は、「/hello」の他「/hello.message.*」や「/hello.message.{goodmorning}」といったものも指定できるようです(MessageMapping.classより)。

WebSocketConfig

設定クラスです。

WebSocketConfig.java

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/gs-guide-websocket").withSockJS();
    }
}
  • @EnableWebSocketMessageBrokerアノテーションを付与することで、WebSocketのメッセージのやり取りを有効にします。

ここで実装している内容は2つです。

  1. MessageBrokerの設定
  2. STOMPのエンドポイントの登録

1. MessageBrokerの設定

メッセージのやりとりを仲介するMessageBrokerの設定を行います。

config.enableSimpleBroker(“/topic”);

「/topic」以下のURL(GreetingController.javaのメッセージ送信先である「/topic/greetings」)でメッセージを渡せるようにします。

メッセージを受け取ったら、あらかじめSubscribeしていたユーザーにメッセージが渡されます。

config.setApplicationDestinationPrefixes(“/app”);

「/app」以下のURLにメッセージを送ると、コントローラークラス(GreetingController.java)が呼ばれるようになります。

今回はクライアント側から「/app/hello」にメッセージを送信すると、
GreetingController.javaの「greeting(HelloMessage message)」が呼ばれます。

2. STOMPのエンドポイントの登録

sockjsを使ってWebSocketの接続処理を行うエンドポイントの登録を行います。

「withSockJS()」は今回sockjsを使っているため必要なのですが、
sockjsを使わない場合にWebSocketを使う処理が行えるのか、可能であればどのようにするのかは気になるところです。

AbstractWebSocketMessageBrokerConfigurer

設定クラス(WebSocketConfig.java)で継承しているAbstractWebSocketMessageBrokerConfigurerではどのようなことをしているのか、少し見てみることにしました。

AbstractWebSocketMessageBrokerConfigurer.java

package org.springframework.web.socket.config.annotation;

import java.util.List;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;

public abstract class AbstractWebSocketMessageBrokerConfigurer implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
    }
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
    }
    @Override
    public void configureClientOutboundChannel(ChannelRegistration registration) {
    }
    @Override
    public boolean configureMessageConverters(List messageConverters) {
        return true;
    }
    @Override
    public void addArgumentResolvers(List argumentResolvers) {
    }
    @Override
    public void addReturnValueHandlers(List returnValueHandlers) {
    }
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
    }
}

※コメント、Javadocsは省略しています。

WebSocketMessageBrokerConfigurer.javaのメソッドをOverrideしていますが、
中身はすべて空になっています。

ドキュメントを見ると、WebSocketMessageBrokerConfigurer.javaを実装しやすくするため、
オプショナルな関数を空で実装している抽象クラスのようです。

設定クラスで実装していた「configureMessageBroker(MessageBrokerRegistry registry)」も含まれているため、
実装が必須な関数というのは、STOMPのエンドポイントの登録を行う「registerStompEndpoints(StompEndpointRegistry registry)」だけのようですね。

では、WebSocketMessageBrokerConfigurer.javaも見てみます。

WebSocketMessageBrokerConfigurer.java

package org.springframework.web.socket.config.annotation;

import java.util.List;

import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;

public interface WebSocketMessageBrokerConfigurer {

    void registerStompEndpoints(StompEndpointRegistry registry);

    void configureWebSocketTransport(WebSocketTransportRegistration registry);

    void configureClientInboundChannel(ChannelRegistration registration);

    void configureClientOutboundChannel(ChannelRegistration registration);

    void addArgumentResolvers(List argumentResolvers);

    void addReturnValueHandlers(List returnValueHandlers);

    boolean configureMessageConverters(List messageConverters);

    void configureMessageBroker(MessageBrokerRegistry registry);
}

※コメント、Javadocsは省略しています。

オプショナルな関数の内容は下記を参照していただきたいところですが、
WebSocketでのメッセージの送受信を行う関数(MessageChannel)の設定などが行えるため、必要に応じてOverrideすることになりそうです。

なお、interfaceで共通の関数を指定して、共通の処理を抽象クラスで実装し、個別の処理を抽象クラスを継承したクラスで実装する、
という流れはまさにJava本格入門で読んだ内容だな〜、などと思いながら見ていました。

おわりに

細かく追ってはみたつもりですが、全体的にまだフワフワしているため、
実際にサンプルを作りながらもう少し突っ込んで触る必要がありそうです。

次回はクライアント側を追いかけてみます。

参照

Spring

WebSocket

STOMP

UbuntuのUnity上でUnity3Dを動かす

はじめに

以前Ubuntu向けのUnity3D(デスクトップ環境と混ざってややこしいので今回はゲームエンジンの方はUnity 3D表記とします)のEditorが開発されている、
という話は聞いていたのですが、その後どうなったのかなぁ〜と思ったらちゃんと開発が続いていました。

というわけで、Unity 3Dと、スクリプトを書くためのJetBrains Riderをインストールした話をまとめます。

※2017年4月現在、Ubuntu用のUnity 3Dはあくまで開発版であるため、予期せぬバグなどが存在する恐れがあります。

準備

gdebi

後述しますが、Ubuntu用のUnity 3Dは.debファイルとして入手できます。

この.debファイルをインストールするときに、依存するパッケージが不足している場合に自動でインストールしてくれる「gdebi」をインストールします。

sudo apt install gdebi

【参考】

Angularなどの開発のため、新しいバージョンのものを使いたかったのでNodebrewをインストールし、
ver.7.9.0をインストールしました。 (Node.jsのインストールは延々ログが出力されるのと、ちょっと時間がかかったため不安になりましたが、無事一発でインストールできました)

インストールが終わったら、使用するNode.jsとして、インストールしたver.7.9.0を指定しておきます。

nodebrew use v7.9.0

【参考】

その他

後述しますが、Riderを使うためにはMono ver.4.4以上が必要なためインストールするのですが、
Unity 3Dをインストールする前に入れておくと、古いパッケージをインストールするのを防げるかもしれません。

Unity 3Dのインストール

Ubuntu用のUnity 3Dの.debファイルのリンクはここにまとめられているようです。

(2017年4月現在の)最新版はなぜか下から2番目にある5.6.0f3だと思いますので、
これをダウンロードし、下記のコマンドでインストールします。

sudo gdebi ダウンロードしてきた.debファイル

エラーが無くインストールが終われば完了です。

WindowsMac用と同じように、ログインやライセンスの選択を行います。

起動後もWindowsMac用と同じように使用可能ですが、何故かScene上でカメラを回転させる操作が、
左クリックではなく右クリックなのはちょっと違和感が。。。

Riderをインストールする

RiderはJetBrainsのサイトからEAP版をダウンロードして任意の場所に展開します。

Linux版はインストーラーが付属しているのではなく、binフォルダ内にあるrider.shというファイルをターミナルで実行することでRiderが起動します。

あとは以前と同じようにプラグインをインストールしました。

よ〜し、じゃあ試しに何か作ってみるかぁ〜、と思ったところ。。。

エラーでRiderが起動しませんでしたorz

Riderでプロジェクトを作成した場合も、下記のようなエラーが出てソリューションファイルの読み込みができませんでした。

Solution 'New Unity Project' load failed: MsBuild not found on this machine

Monoのインストール

エラーでググったところ、どうやらバージョン4.4以上のMonoがインストールされていないのが原因とのこと。

ただ、そのまま「apt install mono-runtime」としてしまうとそれより古いバージョンがインストールされてしまうため、
公式を参考にリポジトリを追加してインストールします。

インストールするのは、「mono-runtime」「mono-devel」「mono-complete」の3つです。

sudo apt install mono-runtime mono-devel mono-complete

【参考】

これで勝ったな、ガハハ

おわりに

Monoのあたりでちょっと手間取ったものの、割とすんなりインストールできてよかったです。
これでいよいよどのOSを選んでも不便なく開発できるようになってきましたね(๑•̀ㅂ•́)و✧

ちなみにタイトルにまでしたUbuntuのUnity上でUnity 3Dを動かす、というのは、
何故かデスクトップ環境をUnity8にしたところ、ウインドウが謎の点滅を起こしたためわずか数分で終了しましたorz

まぁGnome3上では快適ですので。。。^^;

AngularのプロジェクトをビルドしてGolangで動かす

はじめに

前回に引き続きGolangのお話。

以前作成したAngularのプロジェクトをビルドして、 Golangを使って表示してみます。

Angularのプロジェクトをビルドする

実は最近のJS系の勉強会などに参加していて気になっていたことがあります。

「本番ではどうやって表示するの?」

例えばバックエンドをSpring Bootで構築したとして、
フレームワークを使わずに作った場合はSpring Bootのプロジェクトに組み込んで、
Thymeleafなどを使って表示するかと思います。

しかし、例えばAngularの場合だとAngular-cliを使って、
Angularのプロジェクト単体で表示します(確認の段階では)。

では、それぞれを完全に別に扱うの…?

などなど考えていたのですが、実際にはAngularのプロジェクトをビルドすることで、
上の例であればSpring Bootのプロジェクトに組み込むことができる、ということでした。

ビルド自体は(とりあえずビルドするだけなら)簡単で、Angularのプロジェクト直下で下記を実行するだけです。

ng build

ビルドに成功すればプロジェクト直下に「dist」というフォルダが作成され、
必要なファイルがその中に出力されます。

あとはバックエンド側のプロジェクトにまとめて放り込んで、
「index.html」を表示してあげればOKです。

今回はGolangのファイル(webtest.go)と同じディレクトリに「templates」というディレクトリを作り、
その中に全てファイルを保存しました。

HTMLファイルを読み込む

何故か我が家にある(買った)Go言語によるWebアプリケーション開発で扱われているコードを参考にHTMLを表示してみます。

https://github.com/matryer/goblueprints/blob/master/chapter1/chat/main.go

webtest.go

package main

import (
    "html/template"
    "log"
    "net/http"
    "path/filepath"
    "sync"
)

type templateHandler struct {
    once         sync.Once
    filename     string
    viewTemplate *template.Template
}

func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    t.once.Do(func() {
        t.viewTemplate = template.Must(template.ParseFiles(filepath.Join("templates", t.filename)))
    })
    t.viewTemplate.Execute(w, nil)
}
func main() {
    http.Handle("/", &templateHandler{filename: "index.html"})

    // Portを指定してWebサーバーを開始.
    if err := http.ListenAndServe(":8088", nil); err != nil {
        // エラーが発生すればログ出力.
        log.Fatal("ListenAndServe:", err)
    }
}

http.Handle, ServeHTTP

ファイル読み込みのキーになる部分がhttp.Handle、ServeHTTPです。

http.Handle

第一引数に対象とするURL(上記ではルートディレクトリ)を、
第二引数にhttp.Handlerという関数ServeHTTPを持つインターフェースを取ります。

で、対象URLにアクセスした時、Spring bootでいう「@RequestMapping」のように、
第二引数から関数ServeHTTPが実行され、「index.html」が表示される、という流れになっています。

ServeHTTP

ここで気になるところとして、上記では第二引数としてhttp.Handlerではなく、構造体を渡しています。

これがエラーにならないのは、
Golangでは構造体があるインターフェース(ここではhttp.Handler)が持つ関数を全て持っている場合、
そのインターフェースと同じものとして扱うことができるためです。

なお構造体に関数を持たせているのがServeHTTPの「(t *templateHandler)」の部分です。

便利ですが、なかなか慣れないとナンノコッチャ分からんですね。。。(;・∀・)

その他の詳しい解説は本を参照。

…なのですが、この状態ではLoading…の文字しか表示されません。
index.htmlから呼び出しているJavascriptにアクセスできないためです。

また同じ理由でFaviconも表示できません。

静的ファイルを読み込む

これらのファイルにアクセスできるようにするためにはhttp.Handleを使います。

webtest.go

// Javascriptのファイルを読み込む.
http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("templates"))))
// 画像ファイルを読み込む.
http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("templates"))))

そして、ng buildで出力したindex.htmlを少し変更します。

MainPage.html

< !doctype html>
< html>
< head>
  < meta charset="utf-8">
  < title>NgCustomer< /title>
  < base href="/">
  < meta name="viewport" content="width=device-width, initial-scale=1">
  < link rel="icon" type="image/x-icon" href="img/favicon.ico">
< /head>
< body>
  < app-root>Loading...< /app-root>
  < script type="text/javascript" src="js/inline.bundle.js">< /script>
  < script type="text/javascript" src="js/polyfills.bundle.js">< /script>
  < script type="text/javascript" src="js/styles.bundle.js">< /script>
  < script type="text/javascript" src="js/vendor.bundle.js">< /script>
  < script type="text/javascript" src="js/main.bundle.js">< /script>
< /body>
< /html>

変更の理由は、http.Handleの第一引数で指定するURLが、index.htmlと重複するのを避けるためです。

第二引数「http.StripPrefix(“/js/”, http.FileServer(http.Dir(“templates”)))」は、
(例えば)アクセスされるURL「/js/inline.bundle.js」から「/js/」の部分を取り除き、
templatesディレクトリ内にあるinline.bundle.jsを取得します。

とりあえず、これでAngularのファイルを表示できるようになりました。
まだ怪しい要素もありますが、抜けなどがあれば追加・修正します。

他からアクセスできるようにする

確認の際は「go run webtest.go」を実行していますが、
例の如く?他のマシンからこのページにアクセスすることはできません。

どうするかというと、これも例によってオプションを追加します。

go run webtest.go -serve 0.0.0.0

参照

Angular

Golang

GolangでFizzBuzzしてみた

はじめに

ひょんなことから(Twitterで見た)4月にGolangの勉強会に参加することになりました。 いくら話を聴くだけとはいえ、何もわからずに参加しても楽しくないよなぁ…。

ということでちょっと触ってみることにしました。

何を作ってみるか?というところなのですが、
実は「プログラミングの基本」のような扱いを受けるFizzBuzzってやったことがなかったので、Golangでやってみることにしました。

念の為FizzBuzzのルールですが、

  1. 1から100までカウントアップする
  2. 3で割り切れる数なら「Fizz」と出力
  3. 5で割り切れる数なら「Buzz」と出力
  4. 3でも5でも割り切れる数なら「FizzBuzz」と出力
  5. それ以外の数はそのまま出力

というルールで進めます。

インストール

まずはGolangのインストールから。

Ubuntuではaptからインストールできるのですが、これは少しバージョンが古い(ver.1.6)です。

最新のver.1.8をインストールするには、下記のような方法がありそうです。

  1. 公式サイトからtar.gzをダウンロードして展開する。
  2. goenvを使う。
  3. リポジトリを追加してapt installする。

ただ、ググった中で今ひとつスタンダードなやり方が定まっていない気がしたため、1のtar.gzを使うことにしました。

インストール方法は下記を参照。

https://golang.org/doc/install

一点、/usr/localにファイルを置く下記の部分で権限エラーになったため、sudoで実行しました。

tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz

「go version」でバージョン情報が表示されたらOKです。

エディタはIntelliJ IDEAと迷いましたが、 Angularで使っていたこともありVisual Studio CodeにGolangプラグインをインストールして使っています。

通常のFizzBuzz

ではさっそくFizzBuzzってみましょう。

まずは至って普通の方法から。

package main

import "fmt"

func main() {
    useFor()
}
func useFor() {
    for i := 1; i <= 100; i++ {
        if i%15 == 0 {
            fmt.Println("FizzBuzz")
        } else if i%3 == 0 {
            fmt.Println("Fizz")
        } else if i%5 == 0 {
            fmt.Println("Buzz")
        } else {
            fmt.Println(i)
        }
    }
}

これを「go run ファイル名」で実行すればOKです。

簡単簡単。。。

}の位置にハマる

orz

✗ 間違い

if i%15 == 0 {
    fmt.Println("FizzBuzz")
}
else if i%3 == 0 {
    fmt.Println("Fizz")
}

○ 正しい

if i%15 == 0 {
    fmt.Println("FizzBuzz")
} else if i%3 == 0 {
    fmt.Println("Fizz")
}

「}」と「else if{」が同じ行にないと、「'syntax error: unexpected else, expecting }‘」とエラーになります。
厳しい…

rangeを使う

さて、For文には配列を作ってrangeを使う方法もあります。

package main

import "fmt"

func main() {
    useRange()
}
func useRange() {
    for _, v := range getNumArray(1, 100) {
        if v%15 == 0 {
            fmt.Println("FizzBuzz")
        } else if v%3 == 0 {
            fmt.Println("Fizz")
        } else if v%5 == 0 {
            fmt.Println("Buzz")
        } else {
            fmt.Println(v)
        }
    }
}
func getNumArray(fromNum int, toNum int) []int {
    // fromNumからtoNumまでの配列(Slice)を作る.
    resultArray := make([]int, toNum-fromNum+1)
    for i := 0; i < len(resultArray); i++ {
        resultArray[i] = fromNum + i
    }
    return resultArray
}

range

For文で使用し、配列(今回は[]int)を渡すことでIndexと値を取得できます。

今回は使用しませんが、Indexが必要な場合は「for _, v := 〜」の部分を「for i, v := 〜」のように変更します。
「for i := 〜」のように書くとIndexのみが取得でき、
「i」にはIndexが、「v」には値が格納されます。

「_」のように書いているのは、Goでは未使用の変数があるとエラーになってしまうためそれを避けるためです。

slice

「getNumArray」では、1から100までの数値の配列を作っています。
Rubyのように「(1..100)」とかできると楽だったのですが。

配列には「[3]int」のように要素数を指定したものと、そうでないSliceとがあります。

素数を指定する場合、「[3]int」と「[5]int」が別の型として扱われるため、
要素を指定する必要のないSliceを使っています。

Sliceのインスタンスを初期化する場合、「make(型, 要素数)」を使うことができます。

RxGoを使う

例えばC#FizzBuzzをする場合、Linqを使ってFor文は使わずにFizzBuzzすることができます。
しかし、残念ながら?Goには標準でそのような機能が用意されていないようです。

で、ふと思いだしたのがRxJavaで、StreamAPI的な処理ができるっていってたな〜、と。

ということは、GoにもRxが用意されていれば、同じようなことできるんじゃね?

と思って調べてみたら、そのままズバリRxGoがありました。

https://github.com/ReactiveX/RxGo

そんなこんなでRxGoを使ってFizzBuzzしてみます。

go get

RxGoはGolang標準の機能ではないため、ファイルをインストールしてやる必要があります。

そのために行うのがgo getです。
READMEにある通り下記をターミナルで実行するとインストールできます。

go get -u github.com/reactivex/rxgo

今回はobservable.Range(さっきとは違うやつです)を使ってみました。

package main

import (
    "fmt"
    "github.com/reactivex/rxgo/handlers"
    "github.com/reactivex/rxgo/observable"
)
func main() {
    useRx()
}
func useRx() {
    stream := observable.Range(1, 101)
    wait := stream.Subscribe(handlers.NextFunc(func(item interface{}) {
        if v, ok := item.(int); ok {
            if v%15 == 0 {
                fmt.Println("FizzBuzz")
            } else if v%3 == 0 {
                fmt.Println("Fizz")
            } else if v%5 == 0 {
                fmt.Println("Buzz")
            } else {
                fmt.Println(v)
            }
        } else {
            fmt.Println("Invalid value")
        }
    }))
    <-wait
}
  • 「observable.Range(1, 101)」で1から100まで(101は含まない)繰り返し実行するためのStreamを生成します。
  • 「wait」の型は「channel」で、非同期で実行される処理の完了を待ちます。
  • RxGoのWikiでは「handlers.NextFunc」の引数である「func(item interface{})」に型は指定されていないのですが、
    少なくともver.1.8ではエラーとなるため、型を指定しています。
  • 引数の型を「interface{}」とすることで、型を指定せずに引数を受け取ることができます。
  • 「if v, ok := item.(int); ok {」の行で引数の型を確認し、intであった場合は「v」に格納された値を使ってFizzBuzzします。

おわりに

これまでC#やKotlinをはじめ多機能な言語ばかりを触っていたため、あれもないこれもない、おまけにフォーマットや型に厳しい(´・ω・`)
というのがGolangに対する正直な感想です。

ただ、「Goに入ってはGoに従え」という言葉もありますし、これまでの言語のやり方が完璧か?と言われればそうではない気がするので、
Golangが別の方向に進化する、というのは興味深いと思います。

一点、言語的にシンプルだったり独自路線なのは良いと思うのですが、
その先に幸せが待っている、と信じさせる何かがないと結局各々が独自にフレームワークやオレオレ実装プラグインを持ちだして大変になるのでは…?
という危惧はあります。

まぁ素人考えなので、ちゃんとその辺りも考慮して進められているのかとは思いますが。

参照