AWS Amplifyで30分でGraphQL環境を用意してAndroidとつなぐぞっ!つかまれッ!

こんにちは!本エントリは「AWS Amplify Advent Calendar 2019」の記念すべき第1日目です!

このエントリではAWS Amplifyを使ってユーザ認証機能付きのリアルタイムチャットアプリを30分で作ってみたいと思います。

AWS Amplifyとは

AWS Amplify(以下Amplify)を初めて耳にする方、ご安心ください!
Amplifyはスケールするモバイルアプリおよびウェブアプリを最速で構築するためのサービス/ツール群です。

aws.amazon.com

Amplifyは

  • API機能の簡単な追加
  • 認証機能の簡単な追加
  • 画像/文字認識などのAI機能の簡単な追加
  • オフラインのデータ同期
  • これらを利用するためのライブラリやUIコンポーネント

などを含んでおり、モバイルアプリやウェブアプリを開発して実際にサービスを提供するのに必要なフロントエンド/バックエンドを非常に簡単に用意することができます。実際にその様子をお見せしましょう!

今回作るアプリ

冒頭で述べたように、ユーザ認証機能付きのリアルタイムチャットアプリを作ってみたいと思います。 このアプリは次のような機能を備えています。

  • アプリから簡単にユーザ登録してログインできる
  • ログインしたらこれまでのチャット履歴が表示される
  • 好きな文章を入力して送信できる
  • 任意の人数がチャットに参加でき、参加者のコメントは能動的にリロードする必要なくリアルタイムに表示される
  • ログアウトしてチャットを終了できる

f:id:fushiroyama:20191201023816g:plain
Amplifyで作ったリアルタイムチャットアプリ

APIにGraphQLを使ってみる

AmplifyでAPIを追加するとき、主に2つの選択肢があります。

RESTful APIについては言わずもがな。これは裏ではAmazon API GatewayAWS Lambdaによって実現されますが、こちらは想像がつきやすいので今回は利用しません。

今回はRESTにかわる規格として注目を集めているGraphQLを利用してみたいと思います。GraphQLに関しては「GraphQL」徹底入門 ─ RESTとの比較、API・フロント双方の実装から学ぶが素晴らしい記事ですので別途ご覧ください。

AWSではAWS AppSyncというGraphQLのマネージドサービスが提供されています。マネージドサービスということは、自分たちでサーバを用意しなくても簡単にGraphQLを利用できるということです!

AmplifyでGraphQLをAPIに選択すると裏ではこのAppSyncが使われますが、今回のアプリを作るにあたってはバックエンドで何が使われているかといった内容はほとんど意識することなく簡単に開発することができます。

Androidプロジェクトを作成

今回はAndroidアプリでGraphQLを使ってみたいと思います。本エントリのiOS対応版も近い内に公開予定なので楽しみにしてください!☺️

AndroidアプリはAndroid Studioのウィザードから普通に作成して、アプリが起動するのを確認できればOKです。ここを便宜上 ${PROJECT_ROOT} とします。

Amplify CLIのインストール

Amplify CLIというコマンドラインツールをインストールして、これで対話的に機能を追加したり編集するのがAmplifyを利用する基本的な流れとなります。

ターミナルから次のコマンドでAmplify CLIをインストールします。環境によっては sudo が必要です。

$ npm install -g @aws-amplify/cli
$ amplify configure

amplify configureAWSにIAMユーザを作成し、そのユーザ権限でCLIを実行できるようになります。 この辺りに少し不慣れな方はAWS Amplify ハンズオン 基本ステップに画像つきで詳細に解説されているので参考にしてください!

amplify init

次に ${PROJECT_ROOT}amplify init コマンドを実行し、AndroidプロジェクトにAmplifyをセットアップします。

  • Enter a name for the project AmplifyAndroid
  • Enter a name for the environment dev
  • Where is your Res directory: app/src/main/res

あたりを選択/入力すれば、あとはデフォルトで問題ありません。

$ amplify init

Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project AmplifyAndroid
? Enter a name for the environment dev
? Choose your default editor: Vim (via Terminal, Mac OS only)
? Choose the type of app that you're building android
Please tell us about your project
? Where is your Res directory:  app/src/main/res
Using default provider  awscloudformation

For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html

? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use default
⠋ Initializing project in the cloud...
✔ Successfully created initial AWS cloud resources for deployments.
✔ Initialized provider successfully.
Initialized your environment successfully.

Your project has been successfully initialized and connected to the cloud!

Some next steps:
"amplify status" will show you what you've added already and if it's locally configured or deployed
"amplify <category> add" will allow you to add features like user login or a backend API
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

Pro tip:
Try "amplify add api" to create a backend API and then "amplify publish" to deploy everything

ここで何が行われているかは、AWS Amplify ハンズオン 基本ステップの「このとき何が起きているか」を参照してください。

無事に完了すると amplify status でこのプロジェクトのAmplifyの設定を見ることができます。

$ amplify status
| Category | Resource name | Operation | Provider plugin |
| -------- | ------------- | --------- | --------------- |

いまは何もしていないのでこれで問題ありません。

GraphQL(AppSync)セットアップ

このプロジェクトにGraphQLをセットアップします!
amplify add api で「GraphQL」を選択します。 本アプリでは認証機能を利用するので、認証は API Key ではなく API Amazon Cognito User Pool を選択します。

$ amplify add api

? Please select from one of the below mentioned services GraphQL
? Provide API name: amplifyandroid
? Choose an authorization type for the API Amazon Cognito User Pool
Using service: Cognito, provided by: awscloudformation

 The current configured provider is Amazon Cognito.

 Do you want to use the default authentication and security configuration? Default configuration
 Warning: you will not be able to edit these selections.
 How do you want users to be able to sign in when using your Cognito User Pool? Email
 Warning: you will not be able to edit these selections.
 What attributes are required for signing up? (Press <space> to select, <a> to toggle all, <i> to invert selection)Email
Successfully added auth resource

次にGraphQLのスキーマを変更します。今回はチャットのメッセージとして

  • 一意なID
  • メッセージの送信者
  • メッセージ本文

を持ったMessageというモデルを作ることにします。

次の例のように選択して進んでいき、 ${PROJECT_ROOT}/amplify/backend/api/amplifyandroid/schema.graphql を編集します。

? Do you have an annotated GraphQL schema? No
? Do you want a guided schema creation? Yes
? What best describes your project: Single object with fields (e.g., “Todo” with ID, name, description)
? Do you want to edit the schema now? Yes
Please edit the file in your editor: /Users/shiroyaf/git/amplify/AmplifyAndroid/amplify/backend/api/amplifyandroid/schema.graphql
? Press enter to continue

モデルは次のようにします。

type Message @model {
  id: ID!
  username: String!
  content: String!
}

編集したらウィザードを進めます。

GraphQL schema compiled successfully.
Edit your schema at /Users/shiroyaf/git/amplify/AmplifyAndroid/amplify/backend/api/amplifyandroid/schema.graphql or place .graphql files in a directory at /Users/shiroyaf/git/amplify/AmplifyAndroid/amplify/backend/api/amplifyandroid/schema
Successfully added resource amplifyandroid locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

ここまで来たらセットアップ完了です! amplify status するとローカルに「Auth機能」と「Api機能」が追加されています。

| Category | Resource name   | Operation | Provider plugin   |
| -------- | --------------- | --------- | ----------------- |
| Auth     | xxxxxxxxxxxxxxx | Create    | awscloudformation |
| Api      | amplifyandroid  | Create    | awscloudformation |

amplify push することで、このバックエンド構成がそのままAWS上にプロビジョニングされます。
とっつきにくいのはここまでです!ここからは楽しい楽しいアプリ開発です!

Androidアプリへの組み込み

このあと慎重な人であれば、AWSマネジメントコンソールから認証機能の実態である Cognito User Pool にユーザを作成してログインを試みたり、GraphQLのコンソールからクエリを発行したりしたいところですが、今回は敢えてそのあたりに可能な限り触れません。その辺りを意識しなくても開発できるのがAmplifyの良さだと思うからです。

なので、いきなりアプリを書き始めます。 まずGetting Startedを参考に必要なライブラリや設定を追加します。 どれも、Androidエンジニアにはお馴染みの設定なので特に詰まることもないでしょう。

// project's build.gradle
classpath 'com.amazonaws:aws-android-sdk-appsync-gradle-plugin:2.9.+'
// app's build.gradle
apply plugin: 'com.amazonaws.appsync'

dependencies {
    //AWS Base SDK
    implementation 'com.amazonaws:aws-android-sdk-core:2.15.+'

    //AppSync SDK
    implementation 'com.amazonaws:aws-android-sdk-appsync:2.8.+'
    implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.0'
    implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'

    // Needed for AppSync Subscription https://github.com/eclipse/paho.mqtt.android/issues/321
    implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

    // Cognito
    implementation 'com.amazonaws:aws-android-sdk-cognitoidentityprovider:2.9.+'
    //For AWSMobileClient only:
    implementation 'com.amazonaws:aws-android-sdk-mobile-client:2.15.+'

    //For the drop-in UI also:
    implementation 'com.amazonaws:aws-android-sdk-auth-userpools:2.15.+'
    implementation 'com.amazonaws:aws-android-sdk-auth-ui:2.15.+''
}
// AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>


    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <service android:name="org.eclipse.paho.android.service.MqttService" />

    </application>

認証機能の実前

前出の手順で amplify init を正しくセットアップできていると、 ./app/src/main/res/raw/awsconfiguration.json というファイルが作成されているはずです。これはAndroidアプリからAmplifyを利用するための設定を中央集権的にするための設定ファイルです。

念の為中を確認し、CognitoUserPool など設定がきちんとされているか確認してください。PoolIdAppClientId 等はAWSマネジメントコンソールの Cognito User Pool の設定画面で見つけることができます。

$ cat ./app/src/main/res/raw/awsconfiguration.json

{
    "UserAgent": "aws-amplify-cli/0.1.0",
    "Version": "1.0",
    "IdentityManager": {
        "Default": {}
    },
    "CognitoUserPool": {
        "Default": {
            "PoolId": "ap-northeast-1_xxxxxxxxx",
            "AppClientId": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
            "AppClientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "Region": "ap-northeast-1"
        }
    },
    "AppSync": {
        "Default": {
            "ApiUrl": "https://xxxxxxxxxxxxxxxxxxxxxxxxxx.appsync-api.ap-northeast-1.amazonaws.com/graphql",
            "Region": "ap-northeast-1",
            "AuthMode": "AMAZON_COGNITO_USER_POOLS",
            "ClientDatabasePrefix": "amplifyandroid-dev_AMAZON_COGNITO_USER_POOLS"
        }
    }
}

いよいよAndroidのコードに取り掛かります。 まずはGraphQLのクエリ(取得)やミューテーション(追加/変更)などの操作のすべての起点となる AWSAppSyncClient を作ります。
次の例を参考に、ビルダーには cognitoUserPoolsAuthProvider を指定するのを忘れないようにします。

lateinit var awsAppSyncClient: AWSAppSyncClient

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val awsConfig = AWSConfiguration(applicationContext)
    val cognitoUserPool = CognitoUserPool(applicationContext, awsConfig)
    val basicCognitoUserPoolsAuthProvider = BasicCognitoUserPoolsAuthProvider(cognitoUserPool)

    awsAppSyncClient = AWSAppSyncClient.builder()
        .context(applicationContext)
        .awsConfiguration(awsConfig)
        .cognitoUserPoolsAuthProvider(basicCognitoUserPoolsAuthProvider)
        .build()
}

次に、AWSのモバイルSDKでJWTトークンやAWSクレデンシャルを扱うための AWSMobileClient を初期化します。

AWSMobileClient.getInstance()
    .initialize(applicationContext, object : Callback<UserStateDetails> {
        override fun onResult(userStateDetails: UserStateDetails) {
            Log.i(TAG, "onResult: " + userStateDetails.userState)
        }

        override fun onError(e: Exception) {
            Log.e(TAG, "INIT: Initialization error.", e)
        }
    })

次に、認証画面を表示する部分を作ります。

先にインストールしたdependenciesのおかげで、認証用の画面は自分で作らなくても利用することができます。呼び出しは AWSMobileClient.getInstance().showSignIn(context) だけです。

問題は呼び出す場所ですが、AWSMobileClient には認証状態に合わせてコールバックしてもらえるリスナが用意されているので次のように登録します。 あとは、ユーザがサインインしていない状況で showSignIn メソッドを呼んでやるだけです。

リスナは、ログインの前後でActivityを行ったり来たりしても多重登録されないように onStart/onStop 辺りで登録解除してやるとよいでしょう。

private val userStateListener = UserStateListener { details ->
    Log.i(TAG, "onUserStateChanged: " + details.userState)
    when (details.userState) {
        UserState.SIGNED_IN -> {
            Log.i(TAG, "userState: SIGNED_IN")
        }
        else -> {
            Log.i(TAG, "userState: else: " + details.userState)
            AWSMobileClient.getInstance().showSignIn(this@MainActivity)
        }
    }
}

override fun onStart() {
    super.onStart()
    AWSMobileClient.getInstance().addUserStateListener(userStateListener)
    query()
}

override fun onStop() {
    AWSMobileClient.getInstance().removeUserStateListener(userStateListener)
    super.onStop()
}

これでログイン機能は実装できました!

サインイン画面にはユーザの新規作成、確認コードの送信なども最初から組み込まれており完全に動作します。ここで早速ユーザを作成してログインを試してみてください。

f:id:fushiroyama:20191201052622j:plain
サインイン画面
f:id:fushiroyama:20191201052751j:plain
ここからサインアップもできる
f:id:fushiroyama:20191201052828j:plain
確認コードも提供される

これでログイン状態でGraphQLのAPIを自由にアクセスすることができるようになりました。

ミューテーションを実装する

GraphQLではデータの作成や変更、削除などをミューテーションと呼びます。

先にウィザードでMessageモデルを作ったことで、Amplifyが自動的にそのモデルを使って行うミューテーションのためのデータ型などを自動生成してくれます。なのでアプリ作者は

  1. CreateMessageMutation オブジェクトを作る
  2. AwsAppSyncClient#mutate に対してエンキューする

だけでミューテーションを簡単に行うことができます。

次のようなメソッドを作って、ボタンクリックなどをトリガとして実行することで簡単にデータをGraphQL越しに永続化することができます。

private fun mutation(
    id: String = System.currentTimeMillis().toString(),
    username: String,
    content: String
) {
    val callback: GraphQLCall.Callback<CreateMessageMutation.Data> =
        object : GraphQLCall.Callback<CreateMessageMutation.Data>() {
            override fun onFailure(e: ApolloException) {
                e.message?.let {
                    toast(it)
                    Log.e(TAG, it, e)
                }
            }

            override fun onResponse(response: Response<CreateMessageMutation.Data>) {
                response.data()?.createMessage()?.let {
                    Log.i(TAG, it.toString())
                    editTextContent.setText("")
                }
            }
        }

    val input = CreateMessageInput
        .builder()
        .id(id)
        .username(username)
        .content(content)
        .build()

    awsAppSyncClient
        .mutate(CreateMessageMutation.builder().input(input).build())
        .enqueue(callback)
}

Amplifyのウィザードでセットアップした場合、デフォルトでAmazon DynamoDBをデータソースとして扱います。したがってミューテーション後はマネジメントコンソールからデータが格納された様子を確認することができます。

クエリを実装する

同様に、GraphQLではデータ取得操作をクエリと呼びます。
クエリもまったく同じように

  1. 型安全にリクエストやコールバックを作成し、
  2. AwsAppSyncClient#query

するだけでデータを取得することができます。

private fun query() {
    val callback: GraphQLCall.Callback<ListMessagesQuery.Data> =
        object : GraphQLCall.Callback<ListMessagesQuery.Data>() {
            override fun onFailure(e: ApolloException) {
                e.message?.let {
                    toast(it)
                    Log.e(TAG, it, e)
                }
            }

            override fun onResponse(response: Response<ListMessagesQuery.Data>) {
                val messages: MutableList<ListMessagesQuery.Item> =
                    response.data()?.listMessages()?.items() ?: mutableListOf()

                updateMessages(messages)

                val text = messages.joinToString()
                Log.i(TAG, text)
            }
        }
    awsAppSyncClient.query(
        ListMessagesQuery
            .builder()
            .limit(DEFAULT_LIMIT)
            .build()
    )
        .responseFetcher(AppSyncResponseFetchers.CACHE_AND_NETWORK)
        .enqueue(callback)
}

取得したデータは RecyclerView などに渡して画面に反映するのが典型的な使い方でしょう。

サブスクリプションを実装する

最後に、リアルタイムにミューテーションを通知してもらうためのサブスクリプションを実装します。 もうお気づきだと思いますが、これもまったく作法は同じです。

private fun subscription() {
    val subscription: OnCreateMessageSubscription =
        OnCreateMessageSubscription.builder().build()
    val callback = object : AppSyncSubscriptionCall.Callback<OnCreateMessageSubscription.Data> {
        override fun onFailure(e: ApolloException) {
            e.message?.let {
                toast(it)
                Log.e(TAG, it, e)
            }
        }

        override fun onResponse(response: Response<OnCreateMessageSubscription.Data>) {
            response.data()?.onCreateMessage()?.let {
                val item = ListMessagesQuery.Item(
                    it.__typename(),
                    it.id(),
                    it.username(),
                    it.content()
                )
                addMessage(item)
                Log.i(TAG, "subscription onResponse: ${it.toString()}")
            }
        }

        override fun onCompleted() {
            Log.i(TAG, "subscription onCompleted")
        }
    }
    val subscriptionWatcher: AppSyncSubscriptionCall<OnCreateMessageSubscription.Data> =
        awsAppSyncClient.subscribe(subscription)
    subscriptionWatcher.execute(callback)
}

新しいメッセージが追加されるごとに onResponse がコールバックされるので、それを RecyclerView が持つリストに追加して表示を更新するなどといった使い方が一般的でしょう。

f:id:fushiroyama:20191201055140j:plain
完成図

サインアウトを実装

最後にサインアウトを実装します。 サインアウト処理自体は AWSMobileClient#signOut メソッドを呼ぶだけです。

今回はオプションメニューに追加してみました。

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    when (item.itemId) {
        R.id.signOut -> {
            AWSMobileClient.getInstance().signOut(
                SignOutOptions.builder().invalidateTokens(true).build(),
                object : Callback<Void> {
                    override fun onResult(result: Void?) {
                        Log.i(TAG, "signOut(): onResult ok")
                    }

                    override fun onError(e: java.lang.Exception?) {
                        Log.e(TAG, "signOut(): onResult error")
                    }
                })
            return true
        }
        else -> {
            return super.onOptionsItemSelected(item)
        }
    }
}

f:id:fushiroyama:20191201055459j:plain
サインアウト

サインアウトすると自動的に認証状態の変更を検知するリスナが発火されてサインイン画面が再び表示されるはずです。

まとめ

ちょうど30分ぐらいでしょうか!GraphQLがこれほど簡単に使えることに驚かれたのではないでしょうか。

AmplifyはバックエンドはすべてAWSのサービスであり、設定や組み合わせは柔軟でスケールもします。この使い勝手の良さとカスタマイズ性のバランスがAmplifyの大きな魅力だと個人的には考えています。

今日の例はごくごく初歩的な内容に留めましたが、今後いくつかのエントリでAmplifyを使ったネイティブアプリ開発の実践的な例をご紹介できたらなと考えています。楽しみにお待ち下さい!

JAISTに入学してひと月が経ちました

JAISTに入学してようやくひと月が経ちました1
ひと言でいうとJAISTは最高です。働きながら大学院生になった感想を残しておこうと思います。

JAISTは最高

JAISTは最高です。僕は東京サテライトの学生なので以下特に「石川本校」と断りのない限り東京社会人コースのことだと思ってください。

学生のレベルとモチベーションが高い

社会人コースはその名の通り社会人しかいません。働きながら勉強しようという連中なので当然非常に高いモチベーションです。
グループワークをするとみんなつばを撒き散らしながら白熱の議論をしますし、発表するとなるとマイクを奪い合って登壇します。

また、どういうわけかすでに高い教育を受けて世界を股にかけて活躍している第一線のビジネスパーソンがずらりと揃っています。JAISTは入試の際に「自分の出身大学、指導教官、勤め先などを一切明かしてはならない」というルールがあります。これは面接官にバイアスを与えないためですが、それにも関わらず彼らがきっちり勝ち残って入学しているのはとても不思議に感じます。
とにかく、そのおかげで授業は引き締まって高レベルに展開され、議論は実経験を反映した実りの多いものになり、みな自分の社会人生活を通じて得た知識や学びをシェアするのに惜しみがありません。これは大変な刺激です。

JAISTは甘くない

JAIST浅野哲夫学長の入学の挨拶の言葉を引用すると「偏差値一辺倒の大学教育から一線を画した多様な人材を受け入れる」大学です。このため、確か数年前から「線形代数」や「英語」の筆記試験を廃止し、いまは小論文と面接一本に絞られました2。これによってこれまでは入学できなかった数学/英語が苦手な生徒も多く入学したことと思います。ただし当然のことですが、入学したからといって修了できるとは限らないのです。

JAISTの授業はレベルが高く、単位の認定は厳しいです。これは複数の教授と元卒業生の同僚の証言から確かなことです。「門戸は開いてやるから実力で這い出てこいよ」ということです。例えば僕が履修している「情報解析学特論」という講義は、初回の授業で「今日はいきなりで心の準備もできてないだろうから、高校・大学の復習のつもりで聞いてくださいね」と始まった内容すら、僕にはついていくのが必死でした。

あらためて、高校まででやるような数学はごくごく基本的なパズルのピースであって、大学や大学院の授業ではそれらを使って応用的な内容を学ぶのです。院試でわざわざ問わないから君たちが当然にそれを用意してくるのですよ、さもなくば単位は与えませんよということです。とにかく必死に食らいついていくしかありません。

それから内容以上にキツイのがその過密スケジュールです。JAIST東京では情報科学系の授業は金曜夜と土日にだけ開講されるのですが、これで平日フルに勉強している石川本校と同じ到達点を目指すので、土日100分 * 2コマずつで2ヶ月で期末試験まで完了というような厳しい日程になります。土日連続開講なので予習復習の期間もほとんどなく、試験勉強も充分にできません。履修がたとえ4半期1教科でも平日夜は予習復習で必死です。授業はほとんどその確認ぐらいの余裕がないと到底ついていけません。

正直すでに働きながら2年での修了は現実的ではないので3年計画を立てています。JAISTは長期履修制度を使って2年分の学費で3年通うことができるのでこれもありがたい点ではあります。

コンピュータサイエンスって何なんスかね

僕はアルゴリズムとデータ構造、グラフ理論、計算複雑性理論などに関心が高く、ちょいちょい自習していたので正直に言うと大学院でコンピュータサイエンスを学ぶと言ってもこれまでの復習かそれを補強するものになるだろうと高をくくっていましたが、これは大変に甘い目論見であることを知りました。コンピュータサイエンスの裾野は自分が知っているよりも遥かに広く、そのごく一分野を掘るだけでもやっとだとよくわかりました。

シラバスを眺めるだけでも

と楽しみな授業ばかりです。

いまはBluetoothメッシュネットワーク、非同期分散データベース、信号解析(とくに音声データ)などに関心が高まっているので、この3年で研究につながる授業を幅広く取れたらなぁと思っています。JAISTに在学中の先輩方、これから入学を考えているみなさんとぜひ交流したいです。

JAISTは大学を出てなくても入れるよ

そういえば意外にご存じない方が多かったのでここで触れておくと、JAISTは必ずしも大学の学部を卒業していなくても出願できます。
詳しくは「学生募集要項」を見て欲しいですが、社会人経験や取得資格を「学部卒業相当」と見なし、受験資格を与えるための事前審査があり、概ね5年以上の勤務経験があれば受験できることが多いようです。なんでもチャレンジしてみるものです!応援しています!

おわりに

そういえば「34歳からの数学博士」のさのたけとさんのVoicyに呼んでいただけたので、さのさんと堤修一さんと一緒に大学教育やらについて話しました。
さのさんも堤さんも理系修士で、堤さんは情報系なのですが、修了された立場から必ずしもコンピュータサイエンスが必要かどうか分からないといった生の意見が収録されています。もし良かったらぜひ聴いてみてください!ほいじゃあの!

voicy.jp


  1. 指導教官とは入学前からかれこれ半年も研究テーマについて議論したり面談をしてもらったりしていたのでまだひと月か!という感想が大きいです。

  2. その代わり面接は論文発表会さながらの緊迫感で、自分の研究計画を他の論文やデータを交えながら発表して自分が入学を希望する学科の教授4人に囲まれてボコボコにされるという厳しい場です。

ゴキブリが出た

乾かして取り込んだばかりの子供のお昼寝タオルを見ていると、もうすぐ小学校に行ってしまう娘のことを考えて胸が詰まった。

毎週毎週洗われて少しずつ色あせ、厚みもだんだん減っていったタオルケット。
保育園のベッドにフィットするようにゴムが縫い付けてあり、長女の卒園と共に退役してももはや普通のタオルには戻れない、まさに長女の幼少期に命を捧げたタオルケット。

乳児クラスのときにだけ使っていたお手拭きが引退し、幼児クラスのお昼寝タオルも現役を退く。それに連れて子供は大きく強く成長し、少しずつだがそれでも確実に親からの自立の道を歩んでいる。子供の親への依存と執着はちょっとずつタオルに吸い込まれ、気付いたら子供らは親のことは忘れて自分の足で歩んでゆくのだろう。

まるでトイストーリーのおもちゃのようではないか。タオルがではない、我々親がだ。 いつしか子供はおもちゃから巣立ち、親もまたその現実を受け入れて子供から巣立たねばならないのだ。

子供との出会いはあまりにも破壊的で不可逆に僕の人生を変えてしまった。もうそれ以前の暮らしがどうだったかすらよく覚えていない程だ。 それほどまでに愛情を注いだ対象がだんだん我々の存在を必要としなくなるという事実を受け入れるのは難しいことだ。しかしそれは正しいことだし、確実に起こることなのだ。かつての自分がそうだったのだから。

とか何とか言ってたらゴキブリが出た。終わった、我が心の安寧は子供の自立よりも早く失われた。 このマンスョンを買って5年。初めてゴキカブリにお目にかかった。なに?住んでるの?実は住んでたの?娘と共に成長していたの?

恐怖で背筋が凍るとはこのことだ。私は机の上に飛び乗ってそのまま動けない。本当の恐怖に触れると、人間は声すら出ないのだ。 奴はどこだ。一旦部屋の対角に行った。するとメラメラと闘争心が湧き上がってきた。子供たちを守らなくてはならない(論理の飛躍)

まず奴の退路を断つために逃げられる扉をすべて閉じた。それから然るべき決戦の地、五丈原(風呂場)に誘導するために岸田メルのポーズで新聞紙を掻き鳴らす。 しめた!うまく五丈原に陽動した。そのまま一旦風呂場の扉を閉める。

すぐに我が家の論理ストレージであるところのセブンイレブンで高エネルギーポジトロンライフルキンチョール型を調達して戻る。 敵は渭水を背にして陣取っているが、背を向けた一瞬を捉えて一気に噴射した!ここで引くわけにはいかないのだ。やるかやられるかなのだ…許してくれ張郃

いずこよりいまし荒ぶる神とは存ぜぬも、かしこみかしこみ申す。この地に塚を築きあなたの御魂をお祭りします。怨みを忘れ静まりたまえ🙏

iOSDCでテストとDevice Farmについて登壇してきました

ご無沙汰しております。大学院入学前の最後の凪を楽しんでおります。

さて、9月5-7日に開催された iOSDC Japan 2019 で運良くプロポーザルが採択されたので 「実機の管理とおさらば!AWS Device FarmでiOSのテストをしよう!」 というトークで登壇させていただきました。

speakerdeck.com

内容は主に次の通りです。

  1. XCTestを使ったUnit Test
  2. XCTest UIを使ったUI Test
  3. AWS Device Farmを使った自動テスト
  4. Circle CIと組み合わせてパイプラインの構築

自分はモバイル開発者としての経歴が長いものの、iOS開発は素人のようなものです。トークが採択されたときは喜びと同時に不安も大きかったですが、これをより詳しくなるためのよい潮と捉えて楽しんで資料を作り込むことができました。

当日は満員御礼+立ち見まで出るという盛況で、自分としてはかなりのプレッシャーになりました。数あるセッションの中から時間を割いて自分のそれを見に来てくださったわけですから、とにかく楽しんでいただけたことを祈るばかりです。何名かはその後のAsk the Speakerブースに足を運んでくださって色々お話しすることができました。本当によい経験になりました。

内容についてなのですが、UI Testについて1点だけ補足させていただくと、

  1. Accessibility Identifier で特定できる UIImageViewUIImage リソースがボタンタップによって変わることを確かめるUI Testで、Accessibility Label を付け替えることで擬似的にUI Test側から変更を検知する例を紹介しています。自分としてはこれを推奨する意図というよりは、こういったテクニックもあるという程度だったのですが、やはり本来の「Accessibility」の観点を軽視するような面があったかもしれないなと反省しております。Labelとして、Voice Overされても意味のあるテキストを入れるなどフォローできれば良かったなと反省しました。

他にも質疑応答で鋭いご質問やご指摘をいくつももらいました。これこそが登壇の醍醐味であり、スピーカーもオーディエンスもともに学ぶことができるという最高のチャンスでした。機会を与えていただいたことに感謝します。

さて、iOSDCに限らず、自分にとっては記憶にある限りおそらく初めてのiOS系のカンファレンス参加となりました。率直な感想は「最高」です。 主催の長谷川さんのお人柄がスタッフの皆さんから会場の隅々に至るまで染み出しているような、暖かくフレンドリーで、それでも初めて参加する自分も変な内輪感で戸惑うというようなところの一切ない、本当に素晴らしいカンファレンスでした。また来年も是非何か応募したいですし、仮に採択されなくとも足を運びたくなりました。

また、普段Twitterのアイコンしか知らない人にたくさん会えたのも実に新鮮でした。よく参加しているAndroidの勉強会では逆に知っている人ばかりという環境だったので、これは本当によい気分転換となりました。

それから、僕がブログを読んで絶大な影響を受け、渡米したいと押しかけ相談までした堤修一さんと数年ぶりにお会いしてお話するチャンスを得たこと。また、アカデミックに対する熱い思いにこれまた多大な影響を受け、僕自身が大学院を受験する最大のモチベーションをいただいた佐野岳人さんとも久しぶりにお会いしてお話できたこともiOSDCという場があってこそと深く感謝します。

これからも新しい技術への興味を失わず貪欲に学んでいきたいと思います。ご指導ください!ほいでは。

JAISTの博士前期課程に進学します

TL;DR

2019年10月から北陸先端科学技術大学院大学(Japan Advanced Institute of Science and Technology: JAIST)の先端科学技術専攻、博士前期課程に進学します。フルタイムでの勤務を継続しつつ、修士情報科学)の学位を目指します。

f:id:fushiroyama:20190713114835j:plain

最大の動機

端的に言うと、この先40年現役でいるための力を養いたいと思ったからです。

以前のエントリに書きましたが、自分は文系学部の学士であり、ソフトウェア技術者として求められる技能はすべて業務内で身につけて来ました。これはそれなりにワークしているのですが、知識はいかにもツギハギであり、時に自分の理解の浅さに恐ろしくなることがあります。

この先の10年は、今までにも増して速いペースでイノベーションが勃興しては消えてゆくはずです。その中にあって、すでに人口に膾炙し書籍で解説されるような知識は競争力となりません。論文を調べて新しいアルゴリズムを自分で見つけ出して実装できるような、根源的かつアカデミックな能力を身につけないと早晩限界がくるなと危機感を覚えています。

それに、これからは人生100年・労働80年の時代が来るなと感じます。僕は今年36歳になりますが、仮に38歳で修士、41歳で博士になったとして1、労働80年時代ではまだ折り返しですらありません。ここでの学び直しという投資は十分すぎるリターンとして返ってくると期待しています。

JAISTについて

石川県に本部を置く国立大学です。
一般的な知名度はそれほど高くないかも知れませんが、情報系の人はLinuxディストリビューションのミラーサーバーで ftp.jaist.ac.jp というドメインに見覚えがあるのではないかと思います。

僕は石川本校ではなく、東京の品川インターシティにあるサテライトキャンパスに通います。

なぜJAIST

明確な理由がいくつかあります。順に書いていきます。

大学院大学

JAISTは「大学院大学」であり、学部を持ちません。このため大学院には幅広いバックグラウンドの生徒が集まります。これには僕のようないわゆる文系出身者や、社会人を経て再びアカデミックの場に戻ってくる人、外国人留学生などが多数含まれます。

大学説明会で見聞きした限り、これらの状況に対処するために修士1年生には比較的みっちりと基礎の足並みを揃えるための講義が集中しているようです。これは僕にとって望むところです。
また修士研究に重きを置いており、修士論文となる主テーマの研究の他に、主テーマと相互に補い合い相乗効果を得られるような副テーマも必修としています2。これは地に足の着いた研究力を養いたいと考えていた自分の志向と合致するところです。

社会人コース

JAISTは社会人コースを設けており、自分もこの区分です。東京キャンパスは品川駅から徒歩5分ほどの品川インターシティ内にあり、自分が進学する情報科学の場合、授業は金曜夜と土日にのみ開講しています。
これが良いか悪いかは個人によりそうです。平日夜に授業がほぼないということは残業が多めの社会人には助かりそうですが、僕の会社のように柔軟な働き方がゆるされ、かつ休日は家族との時間を大切にしているタイプにはチャレンジとなりえます。

とはいえ、授業のメインが平日昼間ではないという時点でたまらなく魅力的であり、時間をうまくやりくりできればフルタイムの社会人として経済的に安定したバックグラウンドを持ちながら修士・博士を目指せる大学は他にそうたくさんあるものではありません。

また、長期履修制度というものが設けられており、端的にいえば修士は2年分の学費で3年、博士は3年分の学費で4年間在籍することができます3。社会人で学位取得を目指す身としてはありがたい制度です。

その他

他にも、国立大学だから学費が安いとか、教育訓練給付制度対象なので標準年限で修了できる人は更に安く通うことができるとか4、友人のJAIST卒業生が皆一様に優秀であるとか5、色々あってJAISTが第一志望校となりました。合格通知が届いて本当に嬉しいです!!

まとめ

最後に、この決断を後押ししてくれたのは妻です。この先修了までには恐らく幾多の困難があると思います。応援してくれた家族に感謝しながら勉学に励みつつ、妻や子供たちとの時間もうまく確保できるように工夫していきたいと思います。先生方、在学生のみなさん、卒業生の方々、色々ご指導ください。


  1. こんなにスムーズに行くとは露も考えていません😅働きながらなので、完遂できるとしても相応の時間が掛かるでしょう。

  2. 主テーマも副テーマも自分で定める。

  3. 長期履修制度 | JAIST 北陸先端科学技術大学院大学

  4. 長期履修制度との併用はできません。

  5. 実はこれが一番大きい動機かも知れません。これはNAIST卒業生の友人にも共通する特徴です。

唐突にYAMAHAルータ愛を吐露する

こんにちは、お父さんです。日本に帰ってきて4月1日からとある多国籍企業で働いております。本当はその辺の話を書きたかったんですが、存外に社外発信について厳しいので面倒くさくなってしまい、職場のことは書かないと決めました。それで、唐突に僕のYAMAHAルータ愛を吐露したいと思います。

僕はYAMAHAのルータが大好きです。僕の技術者としてのキャリアのスタートはネットワークエンジニアでした。当時はまだオンプレの時代だったので、会社の1室にサーバルームがあったり、データセンタの一画にラックサーバを持ってるのが当たり前でした。社員10人以下の典型的SOHO1だったので、YAMAHAのRTX1100を使って小さなネットワークを制御していました。

YAMAHAのRTXシリーズはまさにこのSOHO向けVPNルータとして最強の存在でした。「VPNルータ」と冠していますが、いまやVPN機能を持ったルータは珍しくもなんともないですね。僕がRTXシリーズを好きな理由はたくさんありますが、少し列挙するならば

  • 実売価格10万円以下とお手頃
  • 抜群の安定度
  • PC-UNIXライクな独自のYAMAHAコマンド2による簡潔な設定
  • 柔軟なフィルタ

などがあります。やっぱり、家庭用とは比較にならない安定度ですね〜。ルータ起因でパケット詰まりとかは自分が関わってきた規模では記憶にないです。設定変更のたびに再起動がかかって30秒待つ…みたいなバカバカしいこともありません。即時反映です。ここで比較してるのはあくまで家庭用ですよ。CISCO vs YAMAHAみたいな戦争構図にもっていくつもりはありません😉

これだと単なるカタログのセールススライドみたいなので、もう少し具体的に自分が使ってて便利だった機能を書きましょうか。

マルチホーミング

複数プロバイダを契約して、片方が落ちたときに自動的にもう片方から迂回みたいなことが簡単にできます。 メインで使う高価で信頼性の高いプロバイダ1と、月500円で緊急時のバックアップに使うプロバイダ2を用意して、プロバイダ1のネットワークにkeepaliveしておき3、一定時間/回数応答がないとデフォルトゲートウェイをプロバイダ2のPPインタフェースに向けるというようなことがめっちゃ簡単にできます。

ip route default gateway pp 1 keepalive 1 gateway pp 2 weight 0
ip keepalive 1 icmp-echo 5 3 監視対象IP

ネットボランチDNS

RTXシリーズを使ってるだけで無料で利用できるポーリング不要のダイナミックDNSのようなものです。要は、ルータがインターネット側にもつグローバルIPfoo.bar.netvolante.jp のように名前解決できるようになります。なんでこれが嬉しいか?

ひとつには、自宅サーバのようなことが固定IPなしで非常に簡単にできます。僕のように2000年代前半に自宅サーバをやってたおじさんとしては、このクラウド全盛期においても自分が持ってるグローバルIPは何か有効活用したいと思っちゃうんですけど、ネットボランチDNSで自宅のネットワークがなかば静的に解決できるとStatic NAT機能を使ってLAN内のRaspberry Piなりを一瞬でサーバとして公開できるんですよ。これはホームゲートウェイとしてsshdの入り口にしたり、httpdに向けて自分のパイロット版ウェブアプリを公開したり色んな使い途があります。

もうひとつ、もし外部にサービスを公開しなくても、VPNの対向側をネットボランチDNSで指定できるという絶大なメリットがあります。 僕は東京の家、四国の実家、そしてアメリカの家すべてにRTX1100を置いて拠点間VPNを構築していました。それぞれは固定IPなど持たない普通の家庭用プロバイダなんだけど、ネットボランチDNSでホスト名を指定できるのでPPPoEセッションが切断・再接続されても気にする必要がありません。僕はこれを利用して、アメリカの家では特定のLANポートに接続すると自動的にトンネル越しに日本側インターネットに出るようにしていました4

中古価格が安い

上の方で「実売価格10万円以下」と書きましたけど、これは最新モデルの新品価格であって、僕をプロにしてくれたRTX1100は中古で3000円ぐらいで買うことができます。RTX1100はLAN側インタフェースすら100Mbpsという旧時代感がありますが、その後継モデルでギガビットイーサ対応のRTX1200も中古で1万円ちょっと。ホームネットワークを構築するには余りあるポテンシャルです。

いまはEdgeRouter Xのように廉価で非常に高機能なルータが出てきたので昔よりも色んな選択肢があるかと思うんですが、急に思い出したのでズアーッと書いてみました。ほいでわ👋


  1. いまはSOHOなんて言わなくなりましたね!今となってはスタートアップ企業とほぼ同義だよ!

  2. これは慣れが必要なんですが、見たら意味が分かるシンプルさとCUIだけで完結する手軽さが魅力

  3. 例えばこのプロバイダのプライマリDNSなどにICMPパケットを送るとかね

  4. 参考: 拠点間VPNで日米それぞれのネット環境を活用する - 怠惰を求めて勤勉に行き着く

父、帰国

家族みんなで日本に帰って来ました。3月末付で現職の米国ラボから離れ、誠に勝手ながらそのまま退職する道を選びました。

改めて経緯を整理すると、最大の理由は家族(特に長女)の環境に対する拒絶感が想像より大きく、親として完全にサポートしてやることができませんでした。様々な不運が重なり、娘としては最悪の米国体験となってしまいました。これはたったひとつのサンプルであり、これをもって米国の何たるかを語るつもりはサラサラありません。他の皆さんの米国進出の妨げとならないことを願いつつ、こういうこともあるのだという体験のシェアはしておこうかなと思います。

我々はBurlingameというサンフランシスコ半島の中ほどにある美しい街に住みました。ここはこの辺りではかなり珍しく、白人比率が高くアジア人が少ない特異な街でした。娘のクラスも、アジア系は娘ともうひとりだけ1でした。人種としてマイノリティというのみならず、言葉が満足ではない子供が娘だけという環境下で彼女の絶望と苦労は想像を絶するものがあったと思います。申し訳ないことです。また、先生も本人のコメントによると教師になって2年目ということで、どう好意的にみようとしてもトラブル因子である娘をかなり邪険に扱っていました。これは最後までとても悔しかったです。

先生同士の引き継ぎが不十分で娘がひとりだけお弁当を食べられなかったり、他の子のイタズラのぬれぎぬを着せられたり、クラス内のパーティで娘にだけおやつが配られなかったりと、信じられないような扱いを受けました。怒り、悲しみ、混乱しました。親としてもツライですが、生まれて初めてこんなアンフェアな扱いを受けた娘を思うと夜も眠れなかったです。よく娘と抱き合って泣きながらベッドに入りました。校長も交えて話し合いも試みたのですが、結局のところ校長先生は「担任を信じたい」という立ち位置で、担任は「誤解や勘違い」というスタンスを崩さず、あまつさえ「証拠がない」とまで言い放つ始末で大きく失望しました。

単身赴任ではなく家族みんなで帰国という選択はこれまた極端にすぎると思われる向きもあるかもしれませんが、我が家ではこの点は一貫しています。つまり、パパと離れては暮らせないという長女の意見を常に尊重しました。
結局の所、米国に来たのも「パパと離れ離れになるぐらいなら嫌だけど米国に来る」というものでしたし、日本に帰るにあたっても「パパと離れて私だけは帰らない」という意見でした。このまま居たら娘の精神はどうにかなってしまいそうでしたし、こう言ってはなんですが、女の子が「パパと居たい」と言ってくれるのなんて、せいぜいあと5年でしょう。それならばパパも日本に帰るよということに決めました。

子どもたちは日本で心安く過ごしています。3月も終わり、妻も子どももそして僕も新しい生活が始まります。これからもどうかご指導ください。それでは近い内にまたどこかで。じゃあの。


  1. この子は台湾系アメリカ人の2世なので、人種としてはアジア人というだけで完全なアメリカ人