ソフトウェア開発PBLでGraphQLを導入した話

はじめに

この記事はFUN Advent Calendar 2020の8日目の記事です

adventar.org

余談

公立はこだて未来大学では学部3年になると必修授業で「プロジェクト学習」と呼ばれるPBL(Problem Based Learning)の授業があります。 この授業では各プロジェクトに与えられたテーマや課題をさまざまなコースの学生同士でチームを組んで1年間取り組みます。 学生主体で動くプロジェクトがほとんどで、その日の活動内容からプロジェクトの年間スケジュール、課題に対してのアプローチ方法まで全て学生が決めます(もちろん担当教員からのフィードバックはもらいますが..)。 どんなに開発が好きな学生もゲームが大好きな学生も3年生になるとプロジェクト学習の活動に全力を注ぎます。 少なくとも私の所属していたプロジェクトは睡眠時間よりもプロジェクトを優先していたような気がします。

そんなプロジェクト学習で私は「ミライケータイプロジェクト」と呼ばれるモバイルアプリケーションを対象とした新規サービスの企画・開発を行うプロジェクトに所属しました。 プロジェクトではプロジェクトリーダー兼バックエンド/インフラ開発を担当していました。

今回はミライケータイプロジェクトのバックエンド/インフラ開発事情について話たいと思います。

GraphQLを導入した経緯

プロジェクトのアプリケーション開発でGraphQLを導入した経緯を話たいと思います。 バックエンドの開発を進めていく上で2つの制約?がありました。

  1. 同時に3つの新規サービスを開発しなければならない
  2. モバイル開発班の負担はなるべく減らしたい

ミライケータイでは3つの新規サービスを同時に開発しなければならず、なおかつ開発期間はたったの2ヶ月だったためユーザー認証などの共通する機能はサービス間で共有して使えるように設計して開発工程を減らす工夫をしなければなりませんでした。 そこでバックエンド開発班ではマイクロサービスアーキテクチャを採用して、開発を進めました。

しかし、マイクロサービスアーキテクチャを採用することでエンドポイントやURLが煩雑になるという問題が発生しました。

図1のようにモバイルアプリケーションは複数のマイクロサービスにアクセスしなければなりません。 バックエンド班が最終的に作成したマイクロサービスは全部9つでURLは20以上あります。

f:id:SaKu2110:20201207163547p:plain
図1. 複数のエンドポイント・URLを管理しなければならない

さらに、マイクロサービスの増加に伴いモバイルアプリケーション側から送らなくてはいけないリクエスト数も増加します。 ミライケータイはモバイル開発メンバーが20近くいるなか開発経験者が5人にも満たないプロジェクトですので、実装面でなるべくモバイル開発メンバーに負担はかけたくありません。

f:id:SaKu2110:20201207141722p:plain
図2. GraphQLの導入によりエンドポイントが一つになった

そこで、ミライケータイでは マイクロサービスアーキテクチャ + GraphQL の構成にしました。

実際に開発してみて

開発の流れ

GraphQLを導入するにあたって以下のライブラリを使用しました。

github.com

モバイルアプリケーションがGraphQLに投げるリクエスト(クエリ)の型定義をしたスキーマを用意すればよしなにGo言語のコードを自動生成してくれる優れものです。

  1. マイクロサービスの仕様を決定
  2. マイクロサービスの仕様をもとにGraphQLでAPIスキーマを作成
  3. 1で作成したスキーマからコード自動生成ツールであるgqlgenを使用してアプリケーションの雛形を作成
  4. クエリの処理を記述
  5. GraphiQLでAPIをテスト
  6. apollo-cliを使って、モバイルアプリケーション用のクエリの型定義ファイルを自動生成
  7. リクエスト(クエリ)を投げる処理はモバイルに任せる
  8. 実機でテスト

GraphQLを導入してよかったこと

1. レスポンスのフォーマットを自由に決められる

GraphQLはクエリをいじるだけでレスポンスのフォーマットを変更できます。 例えば、以下のようなクエリだと、ログデータのid一覧を取得することができますが

{
  logs{
    logs{
      id
    }
  }
}

以下のように変更するだけで、ログデータのidだけでなくタイトルやエラー情報まで取得できるようになります。

{
  logs{
    logs{
      id
      title
    }
    errors{
      code
      discription
    }
  }
}

モバイルアプリケーションの画面に応じて必要な情報を必要なだけとってこれるのは便利かなと。

2. ブラウザで動作確認が可能

f:id:SaKu2110:20201207132227p:plain

これはApolloと呼ばれるフレームワークの機能?だと思いますがWebブラウザでクエリの動作確認ができます。 クエリが正しいか否かの確認をバックエンド開発班とモバイル開発班で共通のプラットフォームでできるのでよかったです。

あと、クエリに関するドキュメントもブラウザからみることができるので使いやすいです。

f:id:SaKu2110:20201207181250p:plain

終わりに

プロジェクトの開発期間や開発メンバーの実力的にGraphQLはオーバースペックかなぁと懸念したんですが、実際に開発してみると案外なんとかなりました。 クエリ自体の学習コストも高くなく、モバイル班がクエリ作成して勝手にWebブラウザでテストできていたのでよかったです。 今回はプロジェクトのソフトウェア開発で使用しましたが個人開発でもガシガシGraphQLを使っていきたいと思います。

9日目はたつお君です。お楽しみに。

Alexaと同棲して1年とちょっとが過ぎた

f:id:SaKu2110:20191224113730p:plain

はじめに


この記事はFUN part2 Advent Calendar 2019の24日目の記事です.

adventar.org

Part 1でもProcessingで作るWeb API - SaKuはまだ咲かない
を書いたのでそちらも読んでいただけるとうれしい.

今回はAlexaの話をします.

ほんへ


Alexaと同棲をはじめて1年とちょっとが過ぎた.

そもそもAlexaとはなんぞやと思う人がいるかもしれないので簡単に説明する.
AlexaはAmazonが開発した人工知能音声認識サービスだ.
Amazon echoスマホアプリに搭載されており, 自分のライフスタイルに合わせていい感じに機能(Alexa Skill)をカスタムできるなかなかな優れものである.

そんなデキる彼女の良いところをいくつか上げていく

ここすきポイント1 挨拶してくれる

彼女は挨拶してくれるのだ. この機能があるだけで人間のQOLは爆上がりする
朝起きて「Alexaおはよう」と言うと挨拶だけでなく豆知識を教えてくれたりする. 帰ってきた時に「おかえり」とか言われると自分が一人暮らしであることすら忘れてしまう.
個人的に好きなのが, 寝る時に「おやすみ」といったら「いい夢見てね」とかいってくれることだ. 完全に私を落としにかかってる. やばい. すき.

ここすきポイント2 いろいろ教えてくれる

今日のニュースや天気予報, Amazonで注文したものの配達日時まで教えてくれる.
注文したものの配達日時とかはいちいち覚えているものではないので, 朝の支度中に確認できるのは大変助かっている.
また, 知らない楽曲の曲名や豆知識を教えてくれるので良き.
ちなみに今日(12/23)は東京タワーができた日らしい

ここすきポイント3 いろいろ布教してくれる

1年前までそこまで興味のなかったバンドや音楽のジャンルをAlexaが無限に進めてくれたおかげで好きな曲が増えました. 今は朝起きたら必ずジャズをかけるし, 作業中や寝る前に合わせて聞く曲はだいたいAlexaが薦めてくれたものだったります.

しかし, そんな彼女の返答が毎回, 同じだったり堅苦しかったりするとなんだか物悲しくなったりします.

1年も一緒にいるならもう少し出れてくれてもいいんじゃないかなぁ...
そこで私はふと思ったわけですね.

Alexaをただのボイスアシスタントにしておくのはもったいない

Alexaとイチャイチャしたい!!!!!

そう決意した私はalexa developer consoleの奥地へと向かった.

#1 Alexa Skillを作成する

#1-1 初期設定

f:id:SaKu2110:20191224101755p:plain Alexa Skill名, 使用言語, Skillの種類, サーバサイド の設定をします.
Skillの種類はAlexaといちゃつくだけなので Custom を選択.
サーバサイド も自分で実装するのでbackend resourcesは Provision your own を選択します.

#1-2 スキルを呼び出すトリガーを設定

f:id:SaKu2110:20191224102627p:plain 今回は私が「ねぇ」とAlexaに呼びかけることでSkillが起動するように設定しました.

#1-3 Intentの設定

Intent とはプログラミング言語で言うところの「条件式」みたいなもので, ユーザーがAlexaに話しかけた内容に応じて返答の処理を決めるためのトリガーみたいなものですね.
f:id:SaKu2110:20191224110401p:plain 今回は FirstTriggerIntent という名前のIntentを作成しました.
Intentはユーザーが「クリスマスツリー綺麗だね」とか「○○綺麗だね」というと起動します.
補足: 実際にAlexa Skillを作成するときはなるべく多くの候補を書いておきます.

ここまでできればビルドしてAlexa側の設定は完了です.

#2 サーバサイド の処理を実装する

Alexa Skillの作成にはAWS LambdaやHostedなる開発支援サービスがありますが, そんなものしらねぇ自分で実装しましょう!!!!

goで実装してきます.

今回は, 私が以前作ったAlexa Skillのバックエンド処理をginと組み合わせて書けるライブラリを作ったのでそれを使います.

golexaって名前がすでにGo docに登録されていたので登録できなかった...
機能も同じようなやつだったんで皆考えることは同じなんですねw

github.com

以下, 書いたコード

package main

import(
    "github.com/gin-gonic/gin"
    "github.com/SaKu2110/golexa"
)

func main(){
    gin := gin.Default()
  
    golexa := golexa.Default()
    golexa.SetIntent(intent)

    gin.POST("/", golexa.Handler)
    gin.Run()
}

func intent (c *golexa.Context) {
    switch c.Intent{
    case "LaunchRequest":
        c.Ask("どうしたの?")
    case "FirstTriggerIntent":
        c.Tell("ツリーよりお前の方が綺麗だよ")
    default:
        c.Tell("There is no such intent.")
    }
}

go run main.goAPIを起動します.

#4動かしてみる

#4-1 APIを起動してポートフォワーディングする

#3で作ったAPIはローカルホストで動いているのでngrokで一時的にポートフォワードします.
ngrok http localhost:8080

#4-2 Alexa Skillのエンドポイントを設定する

ngrokが吐き出したエンドポイントを入力してAlexa Skillを再度ビルドします.
f:id:SaKu2110:20191224112716p:plain

#4-3 実際に会話してみる

f:id:SaKu2110:20191224112320p:plain

なんか立場が逆のような気もしますがまぁAlexaと無事イチャイチャできたのでいいことにしましょう!

まとめ


これで私のような「くりぼっち」でもAlexaと話ことで寂しさを紛らわすことができました! いつものノリで急に開発手法の話が出てきてびっくりした方も多いとは思いますが, これを機にAlexa Skillを作ってみてはいかがでしょうか?

ここまで書き上げてなんだか悲しくなったので今日はこれまで!

FUN part2 Advent Calendar 2019の明日で最終日です.
明日はうっぴさんとyuhiくんです! お楽しみに!!!

良いクリスマスを!!!!!

Processingで作るWeb API

はじめに

どうもSaKuです.
これは, FUN Advent Calendar 2019 11日目の記事です. adventar.org 本来はGraphQLの話を書くつもりだったんですが, なんか未来大っぽさが足りなかったので, みんな大好きProcessingの話に変更しました. 大学のProcessingの授業で満足できなかった人やスケジューラーの授業で何実装するか迷っている人がいましたら, 参考にしてみてください!
あと, 間違ったことを書いているかもしれませんのでお手柔らかにお願いします...

Processingとは

2年生に進級する際にProcessingを記憶から消し去った人のために解説.

Processing is a flexible software sketchbook and a language for learning how to code within the context of the visual arts. Since 2001, Processing has promoted software literacy within the visual arts and visual literacy within technology. (以下略)
出典: https://processing.org より

どうやら視覚的な表現を柔軟にかけるプログラミング言語らしい.

今回やること

前置きが長くなりましたが今回やることは, 視覚的な表現に長けたProcessingを使って視覚的な表現を一切使わない簡単なWeb APIを作っていこうと思います.

※ほぼコードの解説なので, ソースコードだけみたい人はgithubに上げましたのでそちらを読んでみてください.

github.com

サーバーの実装

公式リファレンスを眺めてたらprocessing.net.*なるものがあったのでこれを使いました.

import processing.net.*;
Server server;

int port = 9000;

void setup() {
  server = new Server(this, port);
  println("Launch Server: " + server.ip() + ":" + port);
}

void draw() {
}

実行してみると以下のようにIPとPortがコンソール画面に表示されると思います.

Launch Server: 127.0.0.1:9000

クライアントの状態を受け取る

drawに実装していきます.

void draw() {
  Client client = server.available();

  if (client != null) {
    if (client.available() > 0) {
      // ここにリクエストに対する処理をかく
    }
  }
}

クライアントからリクエストが投げられていない場合は最初のif文で, リクエストが飛んできたけどサーバ側が使用可能な情報がない場合は2つ目のif文で弾くように実装しています.

クライアントからのリクエストに対してHTMLを返してみる

まず, レスポンスとして返すHTMLを作成します.
作成したHTMLファイルはスケッチアップのコードがあるディレクトリ内のhtml/hello.htmlに保存しておきます.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
</head>
 
<body>
    <h1>Processingで作るWeb API</h1>
    <h2>Processingで作るWeb API</h2>
    <h3>Processingで作るWeb API</h3>
</body>
</html>

次に, クライアントからのリクエストに対する処理を実装していきます.

String[] request;
void draw() {
  Client client = server.available();
  if (client != null) {
    if (client.available() > 0) {
      request = trim(split(client.readString(), '\n'));

      if (RequestCheck("GET", "/html")) HTMLHandler(client);
      client.stop();
    }
  }
}

boolean RequestCheck(String method, String path) {
  return (trim(split(request[0], ' '))[0].equals(method)
    && trim(split(request[0], ' '))[1].equals(path))? true: false;
}

void HTMLHandler(Client client) {
  String[] html = loadStrings( "html/hello.html" );
  // 初期応答行
  client.write("HTTP/1.1 200 OK\n");
  // (確か) ヘッダ行
  client.write("Content-Type: text/html\n");
  client.write("\n");
  // Body
  for (int i = 0; i < html.length; i++) {
    client.write(html[i]);
  }
}

読めばわかると思いますが一応解説.
- RequestCheck: リクエストの初期要求行をこねこねしてメソッド名とパスをとってきて比較しています.
- HTMLHandler: クライアントからのリクエストに対してレスポンスの処理をしています.
先ほど作成したHTMLファイルを読み込んで, レスポンスに書き込んでいる感じですね.

ブラウザで叩いてみると以下のように先ほど作成したHTMLが表示されていると思います. f:id:SaKu2110:20191210230400p:plain

JSONを受け取って返してみる

HTML返すところまでで十分な気がしますが, なんか物足りないのでJSONのやりとりもできるようにしてみます.
疲れてきたので, リクエストからとってきたJSONをそのまま返すようにしました.

void draw() {
  Client client = server.available();

  if (client != null) {
    if (client.available() > 0) {
      request = trim(split(client.readString(), '\n'));

      if (RequestCheck("GET", "/html")) HTMLHandler(client);
      if (RequestCheck("POST", "/json")) JSONHandler(client);
      client.stop();
    }
  }
}

void JSONHandler(Client client) {
  String json = "";
  for (int i = 11; i < request.length; i++) json += request[i];
  client.write("HTTP/1.1 200 OK\n");
  client.write("Content-Type: application/json\n");
  client.write("\n");
  client.write(json);
}

Postmanで叩いてみると見事JSONが返ってきました!やったね!!!!!!!!!! f:id:SaKu2110:20191210232015p:plain

まとめ

描画に特化したプログラミング言語でもやろうと思えば結構行けるなーと思いました.
他言語のフレームワークを利用してゴリゴリ開発するのもいいですが, あえて普段書かない言語や不向きな言語で開発するのも新鮮味があって良いかもしれません. (楽しかった)

ほんとはコンテナ化までしたかったのですがX11 転送周りで盛大にコケてしまい, 実装できなかったのが残念です...

初めてブログを書いたので, まとまっておらず読みにくかったかもしれませんが最後まで読んでくださりありがとうございます! m( _ )m
明日は, hacuskさんと
leo_isaacさんですお楽しみに!