エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

元サイバーエージェント エンジニアリングマネージャー 藤原 聖氏、JapanTaxi CTO 岩田 和宏氏の技術顧問就任のお知らせ

エンジニアリンググループ VPoEの山崎です。この度、サイバーエージェント エンジニアリングマネージャー 藤原 聖氏、JapanTaxi CTO 岩田 和宏氏のお二人に当社の技術顧問として就任いただく運びとなりました。

エムスリーのミッションは「インターネットを活用し健康で楽しく長生きする人を1人でも増やし、不必要な医療コストを1円でも減らす」です。このミッションを達成する為に重要なキーの1つがテクノロジーの活用だと考えます。

今回の技術顧問就任にあたり 「世界最高レベルの組織を作り、エンジニア全員が生き生きと仕事ができる環境を作ること」「テクノロジーのエッジをより利かせていくこと」これらにより医療業界のイノベーションを更に加速していくことを目指します。

藤原 聖氏

f:id:otkmym:20180713160502j:plain:w250

東京大学工学部を卒業後、ベンチャー企業を経て、2011年サイバーエージェントに入社。Androidエンジニアとして複数のサービスを立ち上げた後、エンジニアリングマネージャーとしてエンジニアの採用、採用ブランディング、人事評価制度設計などエンジニアが活躍できる組織作りを担当。プライベートではShibuya.apkや日本Kotlinユーザグループといった技術者向けコミュニティを運営。

岩田 和宏氏

f:id:otkmym:20180713160517j:plain:w250

東京工業大学大学院を卒業後、大手セキュリティ会社で画像センサーの開発、外資ベンチャースマホベンチャーmixi、ストリートアカデミーの取締役CTOを経て、JapanTaxiの取締役CTOに就任。現在は車載向け広告サイネージタブレットを展開するフリークアウトとJapanTaxiの合弁会社である、株式会社IRISの取締役CTOを兼務。


具体的な取り組みについては今後ご紹介していきたいと思います。


エムスリーではテクノロジーを活用し医療にイノベーションを起こすエンジニアを募集中です。少しでも興味がありましたらTech Talkやカジュアル面談にお越しください。お問い合わせは以下フォームより、お待ちしております。 jobs.m3.com

AWS FargateでElixirのコンテンツ配信システムを本番運用してみた

こんにちは、エムスリーエンジニアの園田です。

エムスリーでは医療・ヘルスケアサイト向けのコンテンツ配信システムであるChuoiというサービスを運用しています。

以前このブログでも紹介しましたが、このサービスは Elixir/Phoenix で実装されていて、ElasticBeanstalk のCustom Platformを使って運用していました。

www.m3tech.blog

2018/07/04 に AWS Fargate が東京リージョンでローンチされたので、ElasticBeanstalk から Fargate での運用に切り替えました (2018/07/13 切り替え完了)。その際の手順やTipsを書きたいと思います。

今回は構成の説明のみで、実際のコードなどは別のポストで解説していきます。

2018/07/30 追記 実装編の記事を書きました。 www.m3tech.blog

構成変更の動機

以前の構成はこうになっていました。

f:id:ryoheisonoda:20180712182254p:plain

ElasticBeanstalk を利用しているため、アプリケーションのデプロイはawsebcliebコマンドをメインとしたデプロイでした。

f:id:ryoheisonoda:20180712185517p:plain

この構成では、Elixirおよび弊社特有の以下の課題が発生しました。

課題1. Custom Platformのビルドに相当な時間がかかる。

ElasticBeanstalkでは、Packer によるCustom Platform AMIを作成するための機能があり、ElixirやNodeがインストールされたAMIを作成していたのですが、Erlangのビルドに相当な時間がかかっていました。

rpm化も検討しましたが、障害切り分けが困難になるため実装はしたものの利用しませんでした。

GitHub - sonodar/erlang-rpm: Erlang rpm build

こういった背景によりちょっとしたサーバの変更が行いづらく、サーバの細かい修正(fluentdのプラグイン追加など)はebextensionsで済ませてしまうことが多々ありました。*1

これが問題で、ebextensionsはアプリケーションのデプロイ時に実行されるため、ただアプリケーションのソースを変更しただけなのに、全然関係のないfluentdプラグインインストールでエラーになったりして、アプリのデプロイが失敗するといったことが頻発しました。

あと、AWSのバグなのかわかりませんが、Custom Platform Builderの機能がビルドの完了を検知してくれずにずっとビルド状態のまま、という事象が起きていました。そのため、Custom PlatformのビルドはGitlabからでもCodeBuildからでもなく、ローカル端末でebpコマンドを叩いていました。

せっかく構築したCodeBuildのパイプラインは、1回使っただけでお役御免となりました。

課題2. Gitlab CIのIAMユーザに強権限が必要

構成図にあるように、弊社ではCI/CDにオンプレのGitlabを利用しています。そのため、デプロイなどのトリガもGitlab CIで行うことが今はメインになっています。

GitlabからElasticBeanstalkにアプリケーションをデプロイするためには、Gitlab CI上にAWSのシークレットキーなどを保存してeb deployコマンドを実行する必要があります。
これはElasticBeanstalkのダメなところですが、eb deployコマンドを実行するIAMには、リソースの削除権限を含むかなり強い権限が必要です。*2

オンプレのGitlabとはいえ、強い権限を持つIAMユーザを作成すること自体、AWSのプラクティスから外れています。

課題3. TerraformがCustom Platformに対応していない

弊社では(というより私は)AWSのプロビジョニングにTerraformを利用することが多く、このChuoiというサービスもTerraformでAWS環境を管理しています。

ElasticBeanstalkのCustom Platformを環境に適用するためには、aws_elastic_beanstalk_environmentリソースにplatform_arnというパラメータが必要なのですが、今だにこのパラメータはTerraformでサポートされていません。

AWS: aws_elastic_beanstalk_environment - Terraform by HashiCorp

そのため、platform_arnパラメータを追加してビルドしなおしたterraformの実行バイナリを使っていました。 ただ、本体にプルリクエストを送るほどちゃんとした作りにはしておらず、terraformのバージョンがあがるたびに差分を適用するという地味に面倒な作業を行う必要がありました。

上記3つの課題以外にも小さい不満がたくさんあり、いっそのことEC2にしてしまおうかとも思っていましたが、AWS SummitでFargateが東京に来るというのを聞いたとき、ChuoiはFargateにしてしまおう!と思った次第であります。

Fargate化のためにやったこと

さて、前置きが非常に長くなりましたが、今回Fargate対応をするにあたり以下のことを実施しました。

  • Elixir/PhoenixアプリのDocker化
  • Docker化したアプリのFargate動作確認
  • 社内GitlabからのCI/CDパイプライン構築
  • Terraformテンプレート作成

詳しいことは別の機会に書きますが、個人的な感想としては、かなり大変でした。

変更後の構成

変更後の構成はFargate構成としてわりと普通な形になりました。

f:id:ryoheisonoda:20180713112117p:plain

デプロイ周りは以下のような構成です。

f:id:ryoheisonoda:20180713115557p:plain

図に書き忘れたのですが、CloudWatch Event で CodePipeline の状態変更を検知し、Slackにデプロイの進捗を通知する仕組みも実装しました。

デプロイ構成については、検討を行う上で別の選択肢もありました。

  • DockerイメージのビルドとECRへのプッシュはGitlab上で行い、デプロイのみCodeDeployに実行させる。

    • メリット:

      • 事前にDockerイメージのビルドが完了するのでデプロイが高速
    • デメリット:

      • オンプレのGitlabは全チームで使っているため、キュー待ちが発生するとなかなかジョブが始まらない。
      • 環境ごとの差分をGitlab CIの設定ファイルやビルドスクリプトで実現しなければならず、バグが生じやすい。
      • パイプライン全体をTerraformで管理できない。
      • Dockerイメージに埋め込む環境変数などの管理が、Gitlabの貧弱なパラメータ管理に依存する。パラメータ定義をコードで管理できない。
  • Gitlab CIからCodeCommitにプッシュしてパイプラインを開始する

    • メリット:

    • デメリット

      • Gitlab CIに対してCodeCommitにプッシュ可能な権限(秘密鍵もしくはcredentials-manager)を持たせなければならない。
      • AWS全体で保持するソース容量が大きくなる。
  • Gitlab CIからecs-cliでデプロイする

    • メリット

      • Gitlab CIだけでパイプラインが完結する。
    • デメリット

      • Gitlab CI上で利用するIAMユーザの権限が強くなる。
      • パイプラインの構成管理ができない、もしくはしづらい。

どの手段も、メリットがデメリットを上回らないため、S3へアップロードする方式にしました。

今回の手法のメリット、デメリットは以下です。

  • メリット

    • Gitlab CIのIAMユーザはS3に対するPutObjectの権限のみあればいい。
    • AWS側も、外部入力としてS3オブジェクトのみを意識すればいいため疎結合
    • ビルド時の環境変数としてParameter Storeが利用できる。(これ重要)
    • パイプライン全体をTerraformで管理できる。
  • デメリット

    • デプロイまでそれなりに時間がかかる。
    • アプリケーションのGitリポジトリとTerraformのGitリポジトリとでコードが分散する。

デプロイの時間については、今のところ5〜6分で完了するため問題ありませんでした。ソースの分散については課題です。buildspec.ymlのパスにS3のパスが指定できるようになればいいのですが。

構成変更前の課題について

  • Docker化したことによって、インフラ構成のビルド時間が大幅に削減できました。イメージのビルド自体には2分程度しかかかりません。もともと1時間以上かかっていたので、3000%改善されました!

  • Gitlab CI上のIAMユーザはS3バケットPutObject権限のみを持つようになり、安心してアクセスキーをGitlabに登録できます。

  • すべての構成をTerraformでコード化できました。

今回はここまでです。この構成をどう実装したかはまた別の機会に解説します。

2018/07/30 追記 実装編の記事を書きました。 www.m3tech.blog

一緒に開発してくれる仲間を募集中です!

エムスリーでは AWS だけでなく、GCP や Rancher が適材適所で利用されています。実装言語も Elixir だけでなく、JavaRuby、Kotlin、Scala などが大活躍してます。

一緒に開発する仲間を絶賛募集中です!メンバーとカジュアルに話す場も設けてますので是非気軽にお問い合わせください!

jobs.m3.com

*1:ebextensionsはElasticBeanstalkのEC2内ファイルやデプロイフックを管理する仕組みです。https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/ebextensions.html

*2:現在ではCodeDeployがElasticBeanstalkに対応したため、デプロイの入り口をCodeDeployにすることで権限を絞ることが可能

フロントエンド向けの API サーバリニューアルに GraphQL を検討している話

エムスリーでマルチデバイスチームのチームリーダーをしている松原@ma2geです。 マルチデバイスチームはこちらのテックブログでは初出なので簡単に紹介すると、iOSAndroid 等のデバイス対応を主導する開発するチームで、主に iOS, Android のネイティブアプリ開発から、アプリから叩く API サーバ(いわゆる Backends For Frontends(BFF))、プッシュ通知基盤システムのバックエンドサービスも開発しております。 私自身は3月までは別チームで Rails/Java/Elixir などを触っていましたが、4月から現チームに移動しこちらでもまた新たな挑戦をさせてもらっています。 💪

前提

今回はネイティブアプリ向けの RESTful な API サーバがレガシーとなっており、このサーバのリニューアルを検討している話を書きます。 対象のサーバはフレームワークが古く、諸事情により刷新することを前提としています。 せっかくなので技術的に新しいことも取り入れたく、リニューアルにあたって解決したい課題、選定する技術をチームで議論したところ、RESTful で行くか GraphQL で行くかについて決めかねる状況になっています。

※gRPC も検討時に候補として上がっていますが、今回の記事では触れません。

ちなみに RubyKaigi で「Ask Me Anything about GraphQL」Tシャツを来た方に、2018 年にもなって GraphQL 使っていないとかどういうこと?って煽られたことはしばらく忘れないと思います。

f:id:ma2gedev:20180703211931p:plain
Ask Me Anything About GraphQL Tシャツのイメージ

GraphQL については森が入門記事を書いているため、よければご覧ください。

RESTful vs GraphQL

今回のリニューアルに当たって特に改善したい課題は以下の通りです。

  • クライアントの自動生成(今までは手作り)
  • ドキュメントの管理を同一レポジトリで実装と差分なく行いたい(今までは別となっていた)

これらは GraphQL を使うことでどちらも解決できそうですが、今まで通り RESTful で行く場合も OpenAPI(Swagger) を使うことでほぼ同様の解決を行えます。

GraphQL にすることでさらに以下のメリットがあると理解しつつも、これらは今回は必須ではありません。

  • スキーマファーストな開発
  • 複数のリクエストをまとめる
  • 今後 Web のフロントエンドから叩くバックエンドサーバとしても機能させるのが容易そう

また GraphQL は今まで社内での採用事例がなく、RESTful で行くか GraphQL で行くか決定するための情報が不足しており、以下のような点が不明瞭でした。

  • ネイティブアプリなどから呼び出すクライアントは本当に自動生成できるのか、それの使い勝手はどうか
  • 生成されるドキュメントは要望を満たせるのか
  • GraphQL で作ることでバックエンドの作りが複雑になるのではないか(開発のイメージがついていない)

そこで GraphQL についての判断材料を増やすために今回はプロトタイピングを行うことにしました。

GraphQL でのプロトタイピング

プロトタイピングなので手軽な技術を使ってみることにします。 今回対象のサーバは Java で出来ており、弊社の各種 Microservice の API を叩くいわゆる BFF となっています。 そのため Java の資産が使えて、チームの技術スタックとしてもマッチする、Kotlin と Spring Boot をベースとして、GraphQL 部分には graphql-java の各種ライブラリを用いて検証しました。 以下に記載したサンプルコードはこちらへ上げましたので合わせてご覧ください。

GraphQL 実装の下準備

Spring Boot の雛形を https://start.spring.io/ からダウンロードし、下記 GraphQL 関連のライブラリを依存関係に追加することで簡単なサンプルの準備はすぐに出来ます。

# build.gradle から抜粋

dependencies {
  # 省略...
  compile "com.graphql-java:graphiql-spring-boot-starter:4.2.0"
  compile "com.graphql-java:graphql-spring-boot-starter:4.2.0"
  compile "com.graphql-java:graphql-java-tools:5.1.0"
}

GraphQL の簡単なサンプル実装

開発の流れを掴むために簡単な例をあげます。

GraphQL のスキーマ定義は src/main/resources/graphql/ に .graphpqls 拡張子でおきます。 例えば中身はこのような感じです。AuthorBook の型があり、クエリとして Author を取得するための getAuthorById フィールドを定義しています。

type Author {
    id: ID!
    name: String!
    books: [Book]!
}

type Book {
    id: ID!
    name: String!
}

extend type Query {
    getAuthorById(id: ID!) : Author
}

上記スキーマに対応する処理を書きます。 まずは AuthorBook に対応するデータクラスを作ります。

data class Author(
    val id: Int,
    val name: String
)

data class Book(
    val id: Int,
    val name: String,
    val authorId: Int
)

そしてクエリのフィールド getAuthorById に対応するリゾルバを書きます。GraphQLQueryResolver を継承したクラスにクエリのフィールド名に対応する関数を書くことで、graphql-java-tools が対応する処理を紐づけてくれます。

@Component
class AuthorQueryResolver(private val authorDao : AuthorDao) : GraphQLQueryResolver {
    fun getAuthorById(id: Int) = authorDao.getAuthorById(id)
}

AuthorDao が出てきましたが、ここでは感覚を掴むために実際のデータへ繋ぐのではなく簡易的なサンプルとして以下の実装をしています。

@Component
class AuthorDao {
    private val data = mutableListOf(
        Author(id = 1, name = "taka"),
        Author(id = 2, name = "susan")
    )

    fun getAuthorById(id: Int) = data.firstOrNull { author -> author.id == id }
}

最後にスキーマAuthor にある books フィールドについてですが、データクラスの Author には books プロパティがありません。 そのため対応するリゾルバを定義しますが、こちらはクエリではないので GraphQLQueryResolver ではなく、GraphQLResolver を使います。 GraphQLResolver に対応する型として Author を指定し、そのフィールド名である books に対応する関数を定義することで紐づけることができます。 また books にはそのフィールドを持つ Author のオブジェクトが渡されてきます。これによって以下の通り N+1 の問題を回避することが可能になっています。

@Component
class BookResolver(private val bookDao : BookDao) : GraphQLResolver<Author> {
    fun books(author: Author) = bookDao.getBooksByAuthorId(author.id)
}

BookDao はこのように実装しています。

@Component
class BookDao {
    private val data = mutableListOf(
        Book(id = 1, name = "Elixir入門", authorId = 1),
        Book(id = 2, name = "Erlang入門", authorId = 2),
        Book(id = 3, name = "Kotlin入門", authorId = 2),
        Book(id = 4, name = "Rust入門", authorId = 2)
    )

    fun getBooksByAuthorId(authorId: Int) = data.filter { book -> book.authorId == authorId }
}

ここまできたら $ ./gradlew bootRun でアプリケーションを起動をして、http://localhost:8080/graphiql へアクセスします。 左上のペインで以下のクエリを入力して実行すると、右のペインへ結果が表示されるはずです。

{
  getAuthorById(id: 2){
    id
    name
    books{
      id
      name
    }
  }
}

一連の開発の流れとしてはスキーマ定義し、型に対するモデルを定義、対応するリゾルバを作りモデルを返す、ということが確認できました。

これを元に既存 API を実際に実装してみましたが、大まかな開発のイメージとしては RESTful で今までやっていたものと大きく異なるものではないということが確認できました。 graphql-java-tools のようなライブラリを使うことで、GraphQL だからと言って開発が大変になるようなことはなさそうです。ただしシンプルではない複雑なクエリについては、さらに検証が必要と考えています。

ドキュメンテーション

ドキュメントについては、GraphiQL がとても便利なツールとなっております。 スキーマが定義されていれば、http://localhost:8080/graphiql アクセスした際の右ペインで定義されているスキーマ情報を確認することができます。 ただ、スキーマ情報だけではクエリや型に対する説明が足りておらずドキュメントとして情報が不足しています。 こちらについては以下のように .graphqls ファイルへコメントを加えてあげることで説明文を追加することができるようになっています。ちなみにこちらも graphql-java-tools が頑張ってくれています。そのおかげで Swagger UI のような便利さも手に入れることができていると感じます。

# 著者
type Author {
    id: ID!
    # 著者名
    name: String!
    # 著者の本
    books: [Book]!
}

# 本
type Book {
    id: ID!
    # 名前
    name: String!
}

extend type Query {
    # 著者を取得する
    getAuthorById(id: ID!) : Author
}

クライアント側

クライアントの自動生成については Android のプロジェクトで試しました。使用したライブラリは Apollo GraphQL Client for Android を使いました。 今時のライブラリっぽく RxJava2 にも対応しているので、既存のアプリから使うのにもちょうど良いです。

こちらについてアプリへの導入は ApolloREADMEサンプルアプリを見ていただくのが良いと思います。

流れとしては .graphql のファイルにアプリで必要とするクエリを定義してやると、それを元にクライアントが自動生成されます。 この辺りは GraphQL の柔軟さをうまく取り込んでいるなと感じます。

例えば上で例として出した getAuthorById クエリについて例を出すと、src/main/graphql/com/example/sample/api/API.graphql というファイルを作って以下のように中身を記載します。

query Author($id: ID!){
  getAuthorById(id: $id){
    id
    name
    books{
      id
      name
    }
  }
}

すると、AuthorQuery という名前でクエリ用のビルダークラスが生成され、レスポンスについてもクラスが作られ Response<AuthorQuery.Data> として受け取れるようになります。 今までは各 RESTful API についてシリアライズするようなコードを書いて手作りしていたので、それと比べるとかなり楽をできそうだと想像できました。

プロトタイピングしてみて

プロトタイピングを通して、一通りの開発の流れを追ってみることで、不明瞭だった以下の点について情報量を増やすことができました。

  • ネイティブアプリなどから呼び出すクライアントは本当に自動生成できるのか、それの使い勝手はどうか
  • 生成されるドキュメントは要望を満たせるのか
  • GraphQL で作ることでバックエンドの作りが複雑になるのではないか(開発のイメージがついていない)

ちなみにこの他にも認証をどうするか、並列処理はできるのかなど、あまりクリアでなかった点も合わせて確認したり、メンバーから受けた指摘をさらに検証したりといったことをしています。

まとめ

プロトタイピングした本人というのもありますが、個人的には今のところ GraphQL に傾いています。 ただそのまま1人の意見で決めてしまうのは好ましくないので、チームメンバーにもフィードバックをもらいつつ進めていってます。 エムスリーでは自分たちの環境は、自分たちで良くしていくという文化があると思っています。 その一部を見せられたらなと思い、今回検討途中ではありましたが検討の過程を記事にしてみました。

マルチデバイスチームではエンジニアを募集中です

マルチデバイスチームでは AndroidiOS、サーバサイドエンジニアを募集しています。 私も3ヶ月ほど現チームで仕事をしてみて思ったのですが、サーバサイドやりつつ機会があれば AndroidiOS にも手を出せる(あるいはその逆も)という環境のあるチームなので、キャリアを広げたい人にも向いている環境ではないかと感じています。 もし興味がわいた方は、カジュアルにお話しましょう。ぜひエンジニア向けフォームよりご連絡お願いします。 もちろんtwitterで私に直接声をかけていただいても構いません。

参考リンク

GraphQL入門 - React.js & Express.js & Apollo の簡単チュートリアル

M3 ではグローバル CTO の Brian が、サービスの海外展開や技術基盤の共通化などを積極的に進めています。その中のプロジェクトの1つとして、アメリカで提供している医療ニュースのリニューアルにチャレンジしています。2018 年 5 月には日本オフィス所属のイギリス人エンジニア @christophrowley と日本人のエンジニア (筆者)が 1 ヶ月ほどニューヨークに出張してリニューアルの検討をしてきました。

f:id:ke-mori:20180612092457j:plain ( ↑ Chrisが撮影してくれた NY の写真 )

今回の記事は、リニューアルで採用を検討している GraphQL を Apollo + JavaScript で作るチュートリアルです。

TL;DR

  • Apollo を使って、クライアントサイド、バックエンドを作るチュートリアルを紹介
  • 英語・海外での開発に挑戦したいエンジニアを絶賛募集中です。もし興味があればランチ行きましょう!

GraphQL の概要

GraphQL は Facebook が発表した、クライアントアプリがサーバから柔軟にデータを取得できるように設計されたクエリ言語です。

GraphQL の概要を知るのに How to GraphQL (英語)がオススメです。GraphQL の初心者向けの説明資料や 40 分ほどの動画を掲載されています。React.js や Ruby での実装チュートリアルなどもあります。GraphQL の概要を知るのに最適なサイトです。

GraphQL にトライ

GraphQL API Explorer では GitHub が公開している GraphQL の API を簡単に試すことができます。GraphQL がいったいどういうものかを触ってみるのにちょうど良いサンプルです。 右上の「Docs ボタン」をクリックすると GraphQL のドキュメントを見ることができるので、参考にしつつクエリを書いてみてください。

GraphQL のメリット

一般的な GraphQLのメリット

GraphQL を採用するメリットは次のようなものがあります。

  • 複数の API リクエストを一つにまとめることができ、リソースを無駄なく取得できる
  • Web/iOS/Android の共通したバックエンドを効率的に開発ができる
  • スキーマファースト、ドキュメントファーストで開発ができる
  • 既存・新規アプリや外部サービスの API を束ねる API Gateway として活用できる

M3 US におけるメリット

M3 US で GraphQL を採用したいと考えている理由は:

  • USの開発チームは人数が少ないため、ContentfulOkta のような外部サービスを利用したい。 GraphQLはそういった外部サービス、既存・新サービスのAPI Gateway として活用できそう
  • USは利用者のスマートフォン比率が高いので、バックエンドをGraphQLにしてスマホアプリの開発を加速させたい (バックエンドとフロントエンドが非同期に開発しやすかったり、仕様と一致したドキュメントを整備しやすいなど)

Apollo プロジェクトについて

Apollo GraphQLMeteor.js を開発しているチームが中心となって 開発している GraphQL のための OSS、サービスです。

代表的なツールとしては、

  • Apollo Client
    • React や Vue.js、スマホネイティブ(Swift/Java)のための GraphQL クライアント。GraphQL API へのクエリリクエストをサポートしてくれます。
  • Apollo Server
    • Node.js で GraphQL API を構築する OSS で、既存の REST API やバックエンドからデータを取得する仕組みを構築できます。
  • Apollo Engine
    • GraphQL クエリの実行状況を監視したり、エラートラッキング、キャッシュの利用状況の監視などを行うツールとクラウドサービスです。

チュートリアル

今回は Node.js による GraphQL バックエンドと、GraphQL を使ってデータを表示する React フロントエンドのチュートリアルを紹介します。動作検証はnode.js v8.11.2で行っています。

GraphQL バックエンド

プロジェクトの初期状態の構築

Express.js を使って、GraphQL のバックエンドを構築します。ターミナルで次のコマンドを実行してください。

# プロジェクトのフォルダを作成して移動
mkdir express-graphql && cd express-graphql

# Nodeプロジェクトの初期状態を構築
npm init

# ライブラリをインストール
npm install --save express body-parser cors graphql apollo-server-express graphql-tools graphql-tag

ちなみにインストールしたライブラリは次の通りです。

そしてpackage.jsonscriptを次のように変更します。

"scripts": {
  "start": "node ./index.js"
}

GraphQL サーバアプリの作成

次にindex.jsを作成して次のように記述します。

const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const { graphqlExpress, graphiqlExpress } = require("apollo-server-express");
const { makeExecutableSchema } = require("graphql-tools");

// モックデータ
const books = [
  {
    title: "Harry Potter and the Sorcerer's stone",
    author: "J.K. Rowling",
    price: 2000
  },
  {
    title: "Jurassic Park",
    author: "Michael Crichton",
    price: 3000
  }
];

// GraphQLのスキーマ情報
const typeDefs = `
  type Query { books: [Book] }
  type Book { title: String, author: String, price: Int }
`;

// resolver(データ処理)の設定
// DBからデータを取得したり、APIを呼び出したりする処理もここで記述
const resolvers = {
  Query: { books: () => books }
};

// GraphQL の Schema 設定
const schema = makeExecutableSchema({
  typeDefs,
  resolvers
});

// Expressの初期化
const app = express();

// Cross-origin resource sharing (CORS) の設定
const corsOptions = {
  origin: "http://localhost:3000",
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
};
// GraphQLのエンドポイントの追加
app.use(
  "/graphql",
  bodyParser.json(),
  cors(corsOptions),
  graphqlExpress({ schema })
);

// GraphiQLのエンドポイントの追加 (テストで使う GraphQLのWeb GUI)
app.use("/graphiql", graphiqlExpress({ endpointURL: "/graphql" }));

// サーバの起動
app.listen(4000, () => {
  console.log("Go to http://localhost:4000/graphiql to run queries!");
});

module.exports = app;

GraphQL サーバアプリの動作確認

先程作成した Express.js のアプリを起動するために、ターミナルで次のコマンドを実行してください。

npm run start

http://localhost:4000/graphiqlにアクセスできれば成功です。ちなみに、これは GraphQL のクエリを簡単に試すことができる Web 上のエディタです。例えば、

{
  books {
    title
    author
  }
}

を左側のペインに入力して再生ボタン(Execute Query)を押すと GraphQL からデータを結果を取得できます。

React フロントエンド

React アプリの初期構築

create-react-app を使って、React フロントエンドのアプリを構築します。ターミナルで次のコマンドを実行します。

# create-react-app のインストール (インストール済の場合はスキップ)
npm install -g create-react-app

# create-react-app を使って、プロジェクトを作成してそのフォルダに移動
create-react-app react-graphql && cd react-graphql

# ライブラリのインストール
npm install

# 動作確認
npm run start

http://localhost:3000 にアクセスして以下の画面が表示されたら成功。

Apollo Client の導入

Apollo Client で使うライブラリをインストールして、GraphQL にアクセスできるようにします。

npm install apollo-boost graphql-tag graphql react-apollo --save
  • apollo-boost : Apollo Client を設定なしで使えるようにしたライブラリセット
  • graphql-tag : GraphQL クエリ記述をサポート
  • graphql : GraphQL のリファレンス実装で、graphql を使うときに利用
  • react-apollo : React.js で Apollo を簡単に使えるようにするライブラリ

GraphQL サーバからデータを取得

次に、./src/App.jsを次のように変更します。

import React, { Component } from "react";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import Books from "./Books";

const client = new ApolloClient({ uri: "http://localhost:4000/graphql" });

class App extends Component {
  render() {
    return (
      <ApolloProvider client={client}>
        {/* ↑ Apolloクライアント(GraphQLのクエリ)を使えるように設定 */}
        <div>
          <h2>My first Apollo app</h2>
          <Books />
        </div>
      </ApolloProvider>
    );
  }
}

export default App;

次に、./src/Books.jsを作成して、GraphQL から書籍bookの情報を取得します。

import React from "react";
import { Query } from "react-apollo";
import gql from "graphql-tag";

const Books = () => (
  <Query
    query={
      /* GraphQLのクエリ */
      gql`
        {
          books {
            title
            author
            price
          }
        }
      `
    }
  >
    {/* GraphQLのクエリの実行結果の処理、成功したら結果を表示 */}
    {({ loading, error, data }) => {
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error</p>;
      return data.books.map(course => (
        <div key={course.title}>
          <p>title: {`${course.title}`}</p>
          <p>author: {`${course.author}`}</p>
          <p>price: {`${course.price}`}</p>
          <hr />
        </div>
      ));
    }}
  </Query>
);
export default Books;

React アプリの動作確認

作成した React フロントエンドを起動するために、ターミナルで次のコマンドを実行してください。

# React フロントエンド(react-graphql)フォルダで以下のコマンドを実行
npm run start

ブラウザで、http://localhost:3000/にアクセスして、GraphQL サーバのデータを取得できていれば成功です。

Apollo Engine

GraphQL のパフォーマンスの監視などを行うサービスとして Apollo Engine があります。このサービスは、GraphQL のクエリの実行日時、処理時間、処理時間の内訳、エラーの発生率やキャッシュヒット率などを確認できます。100 万クエリ / 月までは無料で利用することができます。

まずは Apollo Engine の Web 画面からAPI KEYを取得します。

次に apollo-engine と環境変数を管理する dotenv をインストールします。

# Apollo Server(Express.js)のフォルダへ移動
cd express-graphql

# apollo-engine dotenv をインストール
npm install --save apollo-engine dotenv

# .env に先程取得したAPI KEYを設定
echo "APOLLO_ENGINE_API_KEY=XXX" > .env

Apollo Server(Express.js)のプロジェクトのindex.jsを次のように変更。

require("dotenv/config"); // .envの環境変数の読み込み
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const { ApolloEngine } = require("apollo-engine");
const { graphqlExpress, graphiqlExpress } = require("apollo-server-express");
const { makeExecutableSchema } = require("graphql-tools");
const { APOLLO_ENGINE_API_KEY } = process.env;

// モックデータ
const books = [
  {
    title: "Harry Potter and the Sorcerer's stone",
    author: "J.K. Rowling",
    price: 2000
  },
  {
    title: "Jurassic Park",
    author: "Michael Crichton",
    price: 3000
  }
];

// GraphQLのスキーマ情報
const typeDefs = `
  type Query { books: [Book] }
  type Book { title: String, author: String, price: Int }
`;

// resolver(データ処理)の設定
// DBからデータを取得したり、APIを呼び出したりする処理もここで記述
const resolvers = {
  Query: { books: () => books }
};

// GraphQL の Schema 設定
const schema = makeExecutableSchema({
  typeDefs,
  resolvers
});

// Expressの初期化
const app = express();

// Cross-origin resource sharing (CORS) の設定
const corsOptions = {
  origin: "http://localhost:3000",
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
};
// GraphQLのエンドポイントの追加
app.use(
  "/graphql",
  bodyParser.json(),
  cors(corsOptions),
  graphqlExpress({ schema })
);

// GraphiQLのエンドポイントの追加 (テストで使う GraphQLのWeb GUI)
app.use("/graphiql", graphiqlExpress({ endpointURL: "/graphql" }));

// Apollo Engineのインスタンスの作成
const engine = new ApolloEngine({
  apiKey: APOLLO_ENGINE_API_KEY,
  // メモリキャッシュの設定
  stores: [
    {
      name: "inMemEmbeddedCache",
      inMemory: {
        cacheSize: 104857600 // 100 MB、デフォルトは50MB
      }
    }
  ],
  logging: {
    level: "INFO" // ログの設定変更。DEBUGにするとより細かい情報を確認できます
  }
});

// サーバの起動
engine.listen({
  port: 4000,
  expressApp: app
});

module.exports = engine;

これで Express.js を再起動して、画面をリロードしてみます。ターミナルで以下を実行してください。

# express-graphqlフォルダで npm run start を実行したターミナルでCtrl + C 。その上で再度以下を実行
npm run start 

そうすると Apollo Engine にパフォーマンス情報などを送信するようになります。React側の画面にもう一度アクセスをしたあとに数秒おいて Apollo Engine をリロードしてみてください。

実際に GraphQL の API を運用する場合はこういった情報を使ってパフォーマンスを計測したり、必要に応じてリゾルバやキャッシュの改善を行う必要があると考えています。

GitHub レポジトリ

今回の チュートリアルのコードを GitHub に公開しています。良ければ参考にお使いください。

https://github.com/m3dev/graphql-apollo-sample

今後の課題

今回のチュートリアルでは GraphQL の API を簡単に構築できることを紹介しました。実際に本番で運用する場合には次のような課題を検討・解決していく必要があると考えています。

  • AWS App Sync のような仕組みを使うべきか、自前で GraphQL アプリを開発すべきか?
  • 不特定多数のクライアントに GraphQL の API を公開する場合は、クエリの実行権限管理や事前にクエリのコストを見積もる仕組みの整備が必要
  • Web 画面のフォームのバリデーションと、GraphQL 側のバリデーションをどう整合性を取るか?などのアプリ開発での実践的なノウハウ

海外で一緒に働いてもらえるエンジニアを募集中

M3 ではアメリカやイギリス(EU)のビジネスの拡大に伴い、一緒に働いてもらえるエンジニアを募集しています!

また社内や グループ会社の メビックス でもGraphQL や Vue.js を使ったプロジェクトが着々と進行中です!GraphQLの開発に興味がある方もぜひ エンジニア向けフォーム からお気軽にご連絡ください!

GraphQL 関連のおすすめサイト

Apollo Blog

Apllo を開発しているチームの Blog (英語)です。セキュリティやキャッシュ、負荷対策など GraphQL を本番で使うために必要な実践的な知識を紹介してくれており、大変参考になります。

おまけ:便利そうなサービス・ツール

その他、GraphQL を調査している段階で面白そうなサービスをいくつか見つけたのでその紹介です。

Prisma & graphql-yoga

Postgraphile

PostreSQL のフロントで GraphQL を提供してくれる OSS、既存の DB に対して簡単に GraphQL を提供できるのが面白い。

参考リンク

学生から見たエムスリー

初めまして。エムスリー2019年新卒内定者の金山です。今回は、学生という立場からエムスリーという企業について書きます。これから就活をされる方々の参考になれば幸いです。

簡単な自己紹介

私は東京大学大学院情報理工学系研究科に所属する修士課程2年生です。研究では主に画像認識・機械学習をテーマとしています。学部時代から3社ほどインターンを経験していましたが、本格的に就活の情報収集を始めたのは修士1年の6月くらいからでした。

インターンのきっかけ

就活を始めた当初、私は「エムスリー」という名前を一度も聞いたことがありませんでした。正確に言えば、ポストイットを作っている会社だと思っていました。(のちに、それはスリーエムだと知ることになる)

そんな折、新卒向けの求人イベントにてエムスリーの社員と接触する機会があり、話を聞くにつれ、もしかしたら面白い企業かもしれないと直感的に思いました。その後、他社と十分に比較検討した上で、ここでインターンすることを決めました。決め手としては、インターンの内容や時期を自分の最も興味のある/最も都合の良い時期にカスタマイズ可能だったことが大きかったように思います。

インターンから内定まで

8月に1ヶ月ほどAIチームにてインターンを行い、主に深層学習を用いた自動要約システムの開発を担当しました。インターン中は、西場さんという見かけ上は非常にストイックですが中身も大変ストイックな社員がメンターとしてついてくださり、大変刺激を受けました。

f:id:otkmym:20180607143155j:plain
左から私、当時メンターを担当した機械学習エンジニアの西場さん

業務内容としては、一言で言うと「Seq2Seqモデルを用いた自動要約の論文を読んで実装し、社内のデータで試す」というものでした。一見すると研究室でやっていることとあまり変わらないようにも思えますが、コードの品質や再現性を担保するための工夫や、実際にサービスに組み込むことを想定した実装など、普段と違った視点で深層学習を捉えることができとても新鮮でした。

このインターンを経て私はエムスリーという企業が大変気に入ったため、インターン後すぐに面接を受け、内定に至りました。選考を申し出てから内定に至るまではわずか2週間弱で、ベンチャーならではのスピード感を感じ取ることができました。私の場合は内定の直後に入社の意思決定をしたため、残りの大学院生活は就活のことは気にせず、やりたい勉強や研究・課外活動などを満喫しています。また、6月からは自分の希望通りAIラボという部署でインターンを再開する予定となっており、大変楽しみです。

f:id:otkmym:20180607142400j:plain
左からAIラボ所長の高木さん、私

エムスリーの特徴

さて、以下では私がインターン中に感じたエムスリーの特徴を3つ挙げます。(本当は8個挙げたのですが、ブログらしからぬ大作となってしまいそうなので3つに絞りました。)

少数精鋭のチーム

まず、第一の特徴として、1人のエンジニアが担当する業務が非常に幅広いことが挙げられます。これは、1チームあたりのメンバーが非常に厳選されていることに起因します。例えば前述の西場さんの場合、数学系の博士号を持っていながらも、業務としては理論の構築から論文実装・実サービスへのデプロイまで幅広く行っています。

ここで、「1人が担当する業務の幅が広い」ことの賛否に関しては議論の余地があります。私がここまで見聞きした感覚として、世の中には「専門に集中できる」ことを売りにする企業と、「幅広い業務をこなす」ことを売りにする企業の二種類が存在するように感じます。

これは私の価値観ですが、私は周辺領域まで含めて網羅的に理解することによってかえって専門性を高めることができると考えています。例えば機械学習の場合、理論を構築してそれを実際のサービスに組み込み、ユーザーからのフィードバックを得てなおかつビジネスにおける位置づけを理解することにより、実世界における機械学習をより深く理解できると考えています。

こうした理由から、各チームが最低限の人数で構成され1人1人が担当する業務の幅が広いエムスリーの体制は非常に魅力的に映りました。

立場にとらわれず、お互いを一人のプロとして対等に扱う

個人的には、「若い人でも正しい意見が通る組織かどうか、フラットな組織かどうか」は重視したいと考えていました。若いから・経験が浅いからという理由で発言権が無かったり簡単な仕事しか任されなかったりしたらつまらないです。そしてこの点でも、エムスリーは自分の希望にかなっていると感じました。

例えば、エムスリーでは年齢や立場に関係なく、能力のある人に相応の仕事が任されています。具体例として、新卒2年目で臨時CTOを務めた加藤さんの事例が好例です。

ただ、これだけでは不十分でしょう。エムスリーの良いところは、先輩社員が後輩に教えるだけでなく、逆に教わる姿勢も持っているという点にあります。例えば私はインターン期間中、自分の専門である画像認識の近年の知見に関していろいろと聞かれたり、社内勉強会で発表する機会を設けてもらったりしました。私は、takerでありながらgiverでもあり続けることで効率よく技術が身につくと考えているので、このような風土は大変居心地よく感じられました。

エンジニアであってもビジネス視点を身につけることができる

1つめの特徴とも関連しますが、エムスリーのエンジニアは技術だけでなくビジネスに対しても造詣が深いという特徴があります。例えば、ROI(投資利益率)を重視する考え方が根付いています。ROIを常に考えることは一見窮屈にも思えますが、エンジニアでありながらもビジネスに対する考え方も身につけたい私にとってはとても都合の良い環境だと言えます。加えて、各人が共通の指標を持って業務に臨むことにより、2つめの特徴として挙げた「正しい意見が通る組織」を実現しているとも言えます。

一方で、コストに見合ったリターンが見込めるのであればすぐに実行できる環境でした。

また、このような堅実な社風柄、がんがん人を採用して急激に規模を拡大するようなことは無いので、よくあるメガベンチャーのように突然環境や社風が変わってしまうことがない安心感があります。

終わりに

さて、ここで話を覆しますが、結局一番の決め手になったのは上記で挙げた特徴ではありません(!?)。最終的な決め手になったのは、「この人たちと一緒に働きたいと思えるか」という点でした。私のインターン期間中に特に深く関わってくださったエンジニアが3人いましたが、3人ともそれぞれ尊敬できる強みを持っていました。この人たちと仕事ができたらきっと楽しいだろうと確信しています。(そして、会社の規模的にも、一緒に働きたいと思った人たちと働ける可能性はかなり高いのです。)

f:id:otkmym:20180607143158j:plain

以上が、学生の目から見たエムスリーでした。インターンで実際に働いてみると、いろいろな人と関わることができ、多くの発見があります。もちろん、インターンや就活をするためだけに大学に行っている人はいないと思うので、どこでインターンするかはよく吟味する必要がありますが、エムスリーもその選択肢の一つに入れてみてはいかがでしょうか。私は留年して進路がエムスリーではなくM3にならないように研究をがんばります。

エムスリーのインターンに興味を持っていただけた方は以下サイトからご応募ください! jobs.m3.com

RubyKaigi2018にPlatinum Sponsorとして参加してきました!

エンジニアの @suusan2go です。RubyKaigi2018にPlatinum Sponsorとして参加してきました。

私は2015年以来、二度目の参加でした。前回参加時も参加者の多さにびっくりしていましたが、今回は参加者数がついに1000台に(会場が仙台だけに)到達したそうです。個人的には海外のエンジニアの方を前回よりもたくさんお見かけしたので、さらにインターナショナルなカンファレンスになっているのかなと感じました。

エムスリーブースの様子

f:id:suzan2go:20180605182120j:plain *1

今回、エムスリーはRubyKaigi2018にPlatinum Sponsorとしてブースも出していました。今回はノベルティーとして、マスク、じゃがりこウコンの力、ステッカーなどを配布しましたが、発注数を控えめにしていたら割と初日でなくなってしまったのが反省点です……

「医療系企業としてマスク、ウコンの力はわかるけど、じゃがりこは何でですか?」と何人かの人に聞かれましたが、特に意味はありません。

じゃがりこや、うこんの力に描かれているキャラクターは ことるびぃちゃん です。Kotlinのイベントにもいたきがするけど、RubyKaigiなので 「ことるびぃちゃん」 です。

じゃがりこの様子 f:id:suzan2go:20180605181741j:plain

ウコンの力の様子 f:id:suzan2go:20180605181759j:plain

ブースにはMatzもきてくれました!!

話題のGitHub(当日はMS買収の話は全く知らなかったけどw)のAaronさんも来てくれました!f:id:suzan2go:20180605181911j:plain

ブースにお立ち寄り頂いた皆様ありがとうございました!

興味深かったセッション

Rubyの型についてのセッション、高速化に関するセッションが特に興味深かったです。

TypeScriptの型定義ファイルのように別ファイルで型情報を作成して静的な型チェックが行えるSteep、まだOSSとしては公開されていないもののStripe社が社内で使っているというsorbetなど、Rubyに静的な型チェックを導入する野心的な試みがいくつか紹介されていました。

speakerdeck.com

sorbet.run

個人的にはRubyを静的型付け言語のようにも扱えるようにすべきとは思いません。しかし直近TypeScriptを触っていて、typoを事前に検知できたり、IDEで補完などの支援を受けながらの開発を経験すると、完璧でなくても型情報があると開発の生産性があがるなとは感じているので、Rubyもその方向でゆるくても型チェックが扱えるようになるといいなと思っています。 Matz的にも目指しているのはLint的なものらしいです。

高速化に関するセッションでは @k0kubunさんのRuby 2.6に入るMJITによる高速化のお話がとてもおもしろかったです。

speakerdeck.com

Cから呼び出されるメソッドをインライン化するのが難しいなら、これをRubyで再定義してしてしまえ!からの、実際にRubyだけで Integer#times 書いてみたらCよりも高速化!からの、 C language is dead は勢いがありましたw

まとめ

久しぶりのRubyKaigiでしたがとても楽しめました!RubyKaigiは特に他のカンファレンスと比べてもRuby自体の実装の話などディープなものが多く、Ruby自体を今後どうしていくかという話をコミッターやMatzの口から直接聞けるのは本当にスゴイことです。

また、これは重要なことなので太字でかきますが、エムスリーのエンジニアはRubyKaigiには全員業務として参加しており、宿泊費と交通費を会社から負担してもらっています!

ブースとしては持ち込むノベルティの数が少なすぎたとか色々と反省点がありますので、次回のRubyKaigi 2019でもスポンサーができたらもっと参加者に楽しんでもらえるブースづくりをしたいなと思います!

エムスリーではRubyに熱い思いをもったRubyistを募集しています!!RubyKaigiでエムスリーに興味をもったという方、カジュアルにお話しましょう!

jobs.m3.com

*1:写真は公式PhotoスポンサーのLovegraphさんのものをお借りしています。https://t.co/w6Dgq8HBb4

JJUG CCC 2018 Spring 登壇発表レポート

こんにちは、エンジニアの矢崎 (@saiya_moebius) です。

"Spring Boot と一般ライブラリの折り合いのつけかた" と題して JJUG CCC 2018 Spring のスポンサーセッションにて登壇発表いたしました。

発表内容について

以下に資料がございます:

Qiita 版はこちら (内容はほぼ同じです)

以下の 4 章について実際のプロジェクトで使える手法・コード例をご紹介いたしました:

  1. DI, Spring らしい方法で各種ライブラリのオブジェクトを使う
  2. ライブラリの設定を簡潔に制御する
  3. プロジェクト間で共有できる足回りを共有する
  4. ライブラリの脆弱性対応・バージョンアップを安全に行う

Java, Spring Boot のプロジェクトの足回りの整備方法という地味なテーマですが、 なかなかまとまったノウハウを見つけられず手探りになっておられる方も多いかと存じます。

そういったまとまったノウハウが見つけられずに回り道や苦労をしてきた実体験があったため、 今回このような内容で発表いたしました。

上記のコンテンツがそういった方への一助となれましたら幸いでございます。

発表の準備について

筆者は 45 分の発表枠であったため合計 49 スライドを用意したのですが、 ほぼ予定通りの時間で発表することができました。 もちろん発表練習を行うことで内容のバランスなどを事前に調整したほうが良いかとも思います。

一方で失敗した点として、VGA 端子での出力アダプタを持参しなかったのは誤算でした。 当日はスタッフの方から予備のアダプタをお借りして事なきを得ましたが、こういったイベントでは必ず VGAHDMI 両方の出力手段を持参しようと思いました。

登壇を終えて

"Don't be shy"

f:id:Saiya:20180530181123j:plain:w200

パブリックなイベントでの登壇発表は筆者にとっては初の経験でして、定員数百人ものお部屋での初登壇というのには緊張もあったのですが、 社内の皆様に後押しいただき、思い切って参加いたしました次第です。

結果として #ccc_g4 タグ でも参考になったといったお声をいただくことができ、 当日の会場でも Java のプロジェクトの足回りで悩んでおられる方々からの実感のこもったフィードバックやご質問をいただけて、 大変励みになりました *1

ランチセッション 普通の人のためのJavaコミュニティイベントのススメ にて強調されておりました "Don't be shy" というのはこういうことなのか!と実感を以て知ることが出来たのは非常に大きな体験でした。

発表のテーマについて

あなたの知っていることは
他の人が知らないことかも

普通の人のためのJavaコミュニティイベントのススメ より引用

筆者は Java のプロジェクトの足回り整備を少なからず経験してきたため 「こんな地味で普通の内容はおもしろいものなのだろうか...」 と思ってしまうこともありました。

しかし、一言にエンジニアといいましても、バックグランド・利用技術・興味・環境などは人によって大きく異なりますし、 多様な技術をすべて把握することは難しいことです。 それゆえに、自分にとっては当たり前のナレッジであったとしても言語化・整理してシェアすることには意味があるものです。

特に JVM, Java は利用シーンや技術の幅も広いため、自身にとっては馴染のあるナレッジであっても共有する価値が高いのではないかと感じました。

Java のみならず

今回、JJUG という Java, JVM の場に参加させていただきましたが、それに限らず様々な場に赴きナレッジを交換してゆくことで、 インターネット企業であるエムスリーの一員としてコミュニティ貢献を続けてゆこうと思います。

また、この記事の書かれている M3 Tech Blogm3dev Qiita では弊社スタッフのさまざまな記事がございますので、 ぜひウォッチいただければと存じます。

関連記事

*1:アンケートの結果はまだ処理中とのことなので、楽しみにしております