UUUM攻殻機動隊(エンジニアブログ)

UUUMのエンジニアによる技術ブログです

GraphQLをはじめよう!

皆さんは普段、WEBでAPIを作っていますでしょうか? 最近はリッチなUIやスマートフォンアプリの開発が当たり前になってきたので、 APIを作ることが多いのではないでしょうか。

APIの開発を楽にするために、GraphQLを使ってみませんか?

現状のAPI開発の悩み

現在APIを作るときは、REST設計で開発を行うことが多いと思います。 実際開発中に以下のような悩みを持ったことはないでしょうか?

  • API多すぎ
  • 仕様がよく変わる
  • ドキュメントがメンテできない
  • もうRESTのAPI作るの飽きたよ…

そんなあなたにぜひGraphQLを! GraphQLを使えば楽しいAPI開発が待っているかも!

GraphQLって何?

答: A query language for your API. (出典: GraphQL )

以上です!(^o^)

……要するに、SQLみたいにクエリを書いて、サーバにリクエストして、 自分が欲しいデータを取って来たり、変更するということができるものです!

GitHub GraphQL API を試そう!

GraphQLがどういうものなのかを知るためには、GitHubが公開しているGraphQL APIを試してみるのがわかりやすいと思います。

そこで、GitHubのAPIを使ってGraphQLがどのようなものなのか試してみましょう!

GraphQL API Explorer | GitHub Developer Guide

上のURLにアクセスして、右上の Sign in With GitHub からログインしましょう。 ログインするとすぐに使用できるようになります。

f:id:m-suzukix:20190418192948p:plain

左上がクエリエディタになっていて、ここにサーバにリクエストするクエリを書いていきます。 右側は、サーバからのレスポンスが表示されます。

自分のアカウント情報を取得する

ではクエリエディタに下のものをコピーして貼り付けて、実行してみましょう!

  • クエリ
query {
  viewer {
    name
  }
}

実行は 再生ボタン みたいなのがあるので押すと実行されます。

f:id:m-suzukix:20190418192929p:plain

実行すると下の様なレスポンスが返ってくると思います。

  • レスポンス
{
  "data": {
    "viewer": {
      "name": "masa"
    }
  }
}

ここにさらに、アバターURLを取ってみましょう!

  • クエリ
query {
  viewer {
    name
    avatarUrl
  }
}
  • レスポンス
{
  "data": {
    "viewer": {
      "name": "masa",
      "avatarUrl": "https://avatars2.githubusercontent.com/u/47341025?v=4"
    }
  }
}

追加で登録してあるアバターのURLが取得できました!

このように自分が欲しい情報に合わせて、クエリを書くことでレスポンスを調整することができるのがGraphQLの特徴になります。 SQLのSELECT文をイメージしていただくと、わかりやすいのではないかと思います。

query とは?

ここで、先程からエディタに書いてたものは、どのような構造をしているのかを見ていきましょう。

まず query についてです。

query {
  viewer {
    name
  }
}

query はデータを取得するリクエストであることを示しています。 SQLではデータを取得する時に必ず SELECT を書くと思いますが、GraphQLでは必ず query を書くことになります。

この query の中には viewer という指定がありますが、 これは viewer というデータを返してくれというリクエストになります。

上の query をSQLで書くと下のような感じになります。

SELECT name FROM viewer

viewer の中身

viewer の中には nameavatarUrl など指定ができましたが、他にはどんなものが指定できるのか気になると思います。

これを調べるために、このAPIコンソールには便利なドキュメントビューワーが付属されています。

エディタの右端に Docs というボタンがあるので押すとドキュメントを見ることができます。

f:id:m-suzukix:20190418194312p:plain

開いたら、viewer を検索してみましょう!

f:id:m-suzukix:20190419114854p:plain

すると Query.viewer が出てくると思います。 これをクリックすると、詳細が見れます。

f:id:m-suzukix:20190419114914p:plain

認証中のユーザ情報が返ってくることがわかりました! その下に TYPEUser! というのがあるので、TYPEについて説明します。

Type

型の指定のことです。これはClassのようなものとイメージするとわかりやすいです。 ルールは以下のようになっています。

type 型名 {
  名前: 型
}

型を構成する Field を書くことができます。 先程の User! の中身を見てみましょう。

f:id:m-suzukix:20190419114936p:plain

FIELDS にどんなデータなのかの指定があります。 先程指定した name , avatarUrl だけ定義してみると下の形になります。

type User {
  name: String
  avatarUrl: URI!
}

後ろに ! があるのは NULL不許可 を示しています。 avatarUrl を指定すると必ず URI型 のデータが返ってきます。 URI型 もドキュメントからどんな型なのか見ることができます。URIの定義に則った文字列が返ってくることがわかると思います。

データを更新する

APIではデータを作成、更新、削除したりすることがありますが、 GraphQLではこれらは mutation で指定する形になります。

mutation {
  ここにどの操作を行うかを書く
}

リポジトリにスターを付けてみる

addStar というスキーマが用意されているので、これを使うとGitHubのリポジトリにスターを付けることができます。

まずドキュメントを使って仕様を確認してみましょう。

f:id:m-suzukix:20190419114952p:plain

Mutation.addStar が表示されます。

f:id:m-suzukix:20190419115002p:plain

ARGUMENTS に指定する引数が書かれています。 addStar には inputAddStarInput! が必須であるとわかります。 (AddStarInputの後ろに ! が付いているからです)

f:id:m-suzukix:20190419115012p:plain

AddStarInput の中身を見ていくと、starrableIdID型 で必須ということがわかります。 ID型 はGraphQLの組み込み型で、通常はbase64エンコードされた文字列が使われるようです。

このIDはリポジトリごとにユニークなものが振られています。 スターを付けるためには、このリポジトリのIDを取得する必要があります。

以下のクエリで取ってこれます。

query {
  viewer {
    repository(name: "pokemon") {
      id
    }
  }
}

このクエリは自分が所持しているリポジトリの名前が pokemon であるものを取得するものになります。

レスポンスは以下になります。

{
  "data": {
    "viewer": {
      "repository": {
        "id": "MDEwOlJlcG9zaXRvcnkxODAyNTUzNDk1="
      }
    }
  }
}

ここで取ってこれたIDを使います。 スターを更新する構文は以下になります。

mutation {
  addStar(input: { starrableId: "MDEwOlJlcG9zaXRvcnkxODAyNTUzNDk1=" }) {
    clientMutationId
  }
}

これを実行するとスターが付くかと思います! 仕様通りに addStar の引数に input: { starrableID: "ID" } の形式で指定を行っています。

GraphQLでは addStar のような構文でも 必ず返り値を指定する ことになっています。 しかし更新系のスキーマでは返り値が不要なこともあるかと思います。 そこで、clientMutationId という指定をすることで、NULL の返り値をレスポンスとして受け取ることができるようになっています。

このスキーマのレスポンスは以下の様になります。

{
  "data": {
    "addStar": {
      "clientMutationId": null
    }
  }
}

この他にも、スターを外したり、PullRequestにコメントを付けたりするスキーマなどが用意されています。 ドキュメントを調べて実際に使ってみると、GraphQLが何となくわかってくるかと思います。

実装するには

各プログラミング言語用のライブラリが用意されているので、これを使うことでGraphQLの実装を行うことができます。

Code | GraphQL

また、Applo EngineAWS AppSyn のようにGraphQLを使う環境が既に用意されていて、後はデータやスキーマを用意するだけで簡単に利用することができるサービスもあります。

まとめ

GitHubが公開しているGraphQLを使ったAPIのご紹介をしました。 GraphQLはまだまだWEB上で知見があまりなく、自分でGraphQLを作る時にどうやって実装して良いのか悩むことがあるかと思います。

そんな時には既に GitHubのAPIの仕様を参考にして開発してみるといい感じに実装ができるのではないかと思います!

www.wantedly.com

Webpackerについてちゃんと調べてみた

カイシャで唯一 Emacs を使ってる✨✨✨✨キラキラエンジニア✨✨✨のtakeokunnです。

ギョームで webpacker を使っているのですが、中のコードを読む機会があったのでせっかくだからまとめてみました✨✨✨✨✨


コードについて書く前に、そもそも Webpacker がどんな機能を提供しているか書く。

READMEによると:

Features
* webpack 4.x.x
* ES6 with babel
* Automatic code splitting using multiple entry points
* Stylesheets - Sass and CSS
* Images and fonts
* PostCSS - Auto-Prefixer
* Asset compression, source-maps, and minification
* CDN support
* React, Angular, Elm and Vue support out-of-the-box
* Rails view helpers
* Extensible and configurable

これ入れとけば最新のwebpack使えて諸々の設定省いて最新 javascript をいい感じにrailsに組み込める!って書いてある。すっごーい✨✨✨✨✨


Webpacker のコードを追ってみる。$ ./bin/webpack が実行されてから webpackbundle.js が生成されるまでを見ていく。

./bin/webpack:

#!/usr/bin/env ruby

ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
ENV["NODE_ENV"]  ||= "development"

require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath)

require "rubygems"
require "bundler/setup"

require "webpacker"
require "webpacker/webpack_runner"
Webpacker::WebpackRunner.run(ARGV)

諸々の設定をしたあと、Webpacker::WebpackRunner.run(ARGV) が実行されている。

lib/webpacker/webpack_runner.rb抜粋:

def run
  env = Webpacker::Compiler.env

  cmd = if node_modules_bin_exist?
    ["#{@node_modules_bin_path}/webpack"]
  else
    ["yarn", "webpack"]
  end

  if ARGV.include?("--debug")
    cmd = [ "node", "--inspect-brk"] + cmd
    ARGV.delete("--debug")
  end

  cmd += ["--config", @webpack_config] + @argv

  Dir.chdir(@app_path) do
    Kernel.exec env, *cmd
  end
end

ここではもともと引数で渡していたものを元に webpack のcommandを生成して、 Kernel.exec env, *cmd で実行をしている。

@webpack_config の定義元について調べてみる。

lib/webpacker/runner.rb抜粋:

def initialize(argv)
  @argv = argv

  @app_path              = File.expand_path(".", Dir.pwd)
  @node_modules_bin_path = ENV["WEBPACKER_NODE_MODULES_BIN_PATH"] || `yarn bin`.chomp
  @webpack_config        = File.join(@app_path, "config/webpack/#{ENV["NODE_ENV"]}.js")

  unless File.exist?(@webpack_config)
    $stderr.puts "webpack config #{@webpack_config} not found, please run 'bundle exec rails webpacker:install' to install Webpacker with default configs or add the missing config file for your custom environment."
    exit!
  end
end

Railsconfig/webpack/#{ENV["NODE_ENV"]}.js から取得している。

config/webpack/development.js:

process.env.NODE_ENV = process.env.NODE_ENV || 'development'

const environment = require('./environment')

module.exports = environment.toWebpackConfig()

toWebpackConfig の定義元はここ: https://github.com/rails/webpacker/blob/b041a1d3e53c55ad801654892c99f5ec4ff099f2/package/environments/base.js#L150-L165

entry, plugins, module, resolve などをまとめて webpack.config.js を吐き出している。

config/webpack/environment.js:

const webpack = require('webpack')
const { environment } = require('@rails/webpacker')

environment.loaders.get('sass').use.splice(-1, 0, { loader: 'resolve-url-loader' });

environment.plugins.prepend(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    jquery: 'jquery',
    Popper: ['popper.js', 'default']
  })
)

module.exports = environment

ここで webpack の設定を書く。

記述の仕方は公式ドキュメントにある: https://github.com/rails/webpacker/blob/master/docs/webpack.md

以上が Webpacker$ webpack --config webpack.config.js の一連の処理だ。


次にRailsとのつなぎ込みの部分を見ていく。

<%= javascript_pack_tag 'application' %>
<%= stylesheet_pack_tag 'application' %>

上記のようにlayout部分に記述するといい感じに bundle.js を読み込んでくれる。

javascript_pack_tag の定義元を読む。

lib/webpacker/helper.rb抜粋:

def javascript_packs_with_chunks_tag(*names, **options)
    javascript_include_tag(*sources_from_manifest_entrypoints(names, type: :javascript), **options)
end

def sources_from_manifest_entries(names, type:)
    names.map { |name| current_webpacker_instance.manifest.lookup!(name, type: type) }.flatten
end

current_webpacker_instanceWebpacker::Instance のインスタンスだ。

lib/webpacker/instance.rb抜粋:

class Webpacker::Instance
    def initialize(root_path: Rails.root, config_path: Rails.root.join("config/webpacker.yml"))
        @root_path, @config_path = root_path, config_path
    end
end

config/webpacker.yml を読み込んでいる。

current_webpacker_instance.manifest.lookup!(name, type: type) の処理を追ってみる。

lib/webpacker/manifest.rb抜粋:

class Webpacker::Manifest
    # Computes the relative path for a given Webpacker asset using manifest.json.
    # If no asset is found, returns nil.
    #
    # Example:
    #
    #   Webpacker.manifest.lookup('calendar.js') # => "/packs/calendar-1016838bab065ae1e122.js"
    def lookup(name, pack_type = {})
compile if compiling?

find(full_pack_name(name, pack_type[:type]))
    end

    # Like lookup, except that if no asset is found, raises a Webpacker::Manifest::MissingEntryError.
    def lookup!(name, pack_type = {})
lookup(name, pack_type) || handle_missing_entry(name)
    end

    def full_pack_name(name, pack_type)
        return name unless File.extname(name.to_s).empty?
        "#{name}.#{manifest_type(pack_type)}"
    end

    def find(name)
        data[name.to_s].presence
    end
end

この lookup! 部分でpath解決をしている。 webpack をしてない状態でrenderingした時に compile が走るのはこの部分が原因みたいだ。

webpack 側の output はどうなっているだろうか。

package/config.js抜粋:

const configPath = resolve('config', 'webpacker.yml')

const config = deepMerge(defaults, app)
config.outputPath = resolve(config.public_root_path, config.public_output_path)

config/webpacker.yml を元に output 先を指定している。

webpack 側も rails 側もお互い config/webpacker.yml を元にしてるためpathを解決することができる。


webpack のconfigを拡張していくとなると結構大変だけれども、特に弄らないなら導入も運用も楽で良いですね!

create-react-app のように eject 出来るように( webpack のみの運用)なってくれるともっと運用が楽になると思います。

へーしゃでは emacs を使うのが好きなエンジニアを切に募集しています✨✨✨✨✨✨✨

一緒にインスタ映えする emacs を作りましょう✨✨✨✨✨✨✨

View this post on Instagram

インスタ映えするemacs

@ takeokunnがシェアした投稿 -

詳しくはこちら

www.wantedly.com www.wantedly.com

今年も合宿にいきました!

こんにちは。2月入社でシステムユニット所属の井上です。

今日のテーマは合宿です!!

システムユニットでは年に2回、開発旅行合宿があります。

合宿ではテーマが設定されており、テーマに沿って自分の好きなプロジェクトの開発だったり、新技術の勉強などそれぞれが自由に研鑽します。

今回はその一環として2/22(金)~2/23(土)に、UUUM攻殻機動隊のエンジニア達で開発合宿に行ってきました。

今回の合宿場は、神奈川県の箱根にある「COLONY 箱根」でした。

施設紹介

企業の研修用の施設らしく広々としていました。また箱根名物の温泉もありました。

そしてラッキーなことに利用者は他におらず(!!)貸切りのような状態で利用することができました。

簡単に施設紹介をします。

お昼ご飯

f:id:iammyeye1:20190225192101j:plain
お昼ご飯

広間 

f:id:iammyeye1:20190225194458j:plain:h551:w681
広間

温泉

f:id:iammyeye1:20190225193734j:plain:h551:w681
温泉

訪問した施設はこちらです。

colony-hakone.com

開発合宿では何をしたのか?

ここからが本題ですが、今回の開発合宿のテーマは「自由開発」でした。

今回の合宿も前回に引き続き、それぞれが一つのプロダクトを開発しました。

お昼ご飯をいただいて2時ごろから雑談混じりで開発は始まり、次第にそれぞれの開発に集中していきました。

f:id:iammyeye1:20190225193857j:plain:h551:w681
開発風景

みんな自由に開発しています!

夕飯・温泉後

18時半の夕ご飯で一旦休憩。

f:id:iammyeye1:20190225192051j:plain
豪華な夕御飯

温泉に入るなど各自で休憩を挟みつつ開発を続けます。 温泉とマッサージ機は凶器ということも知りました。

f:id:iammyeye1:20190225193907j:plain
開発デスマーチ開発を楽しんでる様子

開発の合間の一枚です!

楽しそうな笑顔〜。

しかし開発が進まず5時ごろまで開発をしていた人も、、、

2日目

朝から豪華な食事をいただき、開発の最終調整と発表資料を作成して10時半から発表開始です。

僕は遅くまで起きていたので寝不足で食欲がなく、ご飯は2杯しか食べれませんでした、、、(美味しかったです)

f:id:iammyeye1:20190225192332j:plain
朝食

発表風景

最後にそれぞれが発表資料をスライドで作り、プロダクトのデモンストレーションなども交えながら発表しました!

f:id:iammyeye1:20190225193929j:plain
個人的に一番好きだったプロダクトを開発されたメルさんの発表

こんな感じで発表会が行われました。

発表では、技術的にはAWS, Rails, Lambda, bulma, dockerなど様々な分野に渡り、開発物も自分が欲しいプロダクト、業務効率改善用プロダクト、日頃の業務で関わることの少ないライブラリを試してみた開発など十人十色でした。

結果として幅広い分野の発表となり、非常に知的好奇心を刺激しあう発表でした。

まとめ

まず2日間に渡り開発に集中できる素晴らしい環境を整えてくださった施設の皆様ありがとうございました。

合宿全体に関してですが、まとまった時間を設けて勉強することのできる非常に貴重な機会でした。

発表を通じてそれぞれの成果や知識の共有をすることで、チーム全体で大きく成長できたのかなと思います。

また2日間一緒に過ごしたことでコミュニケーションが活発になったのも良かったと感じています。

個人的に非常に楽しかった合宿でした。

最後は集合写真でもって締めさせていただきます。

f:id:iammyeye1:20190225192239j:plain

また参加したい〜。

www.wantedly.com

すごいTerminal 楽しく学ぼう!

最近Terminalが楽しい takeokunn です。

社内勉強会の順番が回ってきたので、せっかくだから最近ハマってるterminalについて語っちゃおうかなーと思いスライドを作りました!

インスタ映えしちゃうterminalを創ってスタバでドヤ顔したいですね!

スタバといえば、木下ゆうかさんの動画!

↑チャンネル登録しよう

www.wantedly.com

Vue.jsコミッターのkazuponさんをお招きして社内勉強会をしました

こんにちは!エンジニア(仮)のめる(@c5meru)です。
最近、弊社のフロントエンドエンジニアごーさんが、社内勉強会に立て続けにスーパーゲストを呼んでくださっています(第1回第2回 )。
上記に続いて今回はなんと、なんと、Vue.jsコミッターのkazuponさんこと、川口和也さんをお招きしました!🎉✨

続きを読む

Webの技術でYoutube Viewer作った

はじめに

この記事は、UUUM Advent Calendar 2018 20日目です。

12月から新入社員の takeokunn です。普段は LMND の開発をやっております。

UUUMに入った初日(9/18)、 クリエイター(youtuber)のことを全く知らず他の人と会話が合わずに詰みました。 そこでこの会社でこの先生きのこるには効率よくキャッチアップする方法が必要だと思ってUUUM Creatorの動画viewerを作りました。

木下ゆうか さん、ごはん美味しそうに食べるから好き

Effective Youtube Viewer

f:id:bararararatty:20181217152621p:plain

Electronは配布してないので自前でbuildしてください

使ってる技術

  • electron
  • webpack/babel
  • react
  • redux/redux-saga
  • service worker
  • CSS Grid
  • csscomb/eslint
  • CircleCI

よくあるweb frontendの構成です。

redux-saga 楽しい

サービスの質を決めるのはエラーハンドリングだと思っています。 web frontendにおいて、非同期処理、特にAjax周りのハンドリングは非常に面倒ですが、redux-sagaを使えば体感すっきり書くことができます。

この辺がわかりやすいです。 redux-sagaで非同期処理と戦う f:id:bararararatty:20181217160209p:plain

以下はyoutube channelを取得する action/reducer/saga の例です。

抜粋: src/actions/youtube.action.js

export const fetchChannelVideo = {
    request: channel_id => action(YOUTUBE.FETCH_CHANNEL_VIDEO_REQUEST, { channel_id: channel_id }),
    success: data => action(YOUTUBE.FETCH_CHANNEL_VIDEO_SUCCESS, { data: data }),
    failure: () => action(YOUTUBE.FETCH_CHANNEL_VIDEO_FAILURE)
};

抜粋: src/reducers/youtube.reducer.js

const channel_video = (state, action) => {
    switch (action.type) {
    case YOUTUBE.FETCH_CHANNEL_VIDEO_REQUEST:
        return { ...state, is_fetching: true };
    case YOUTUBE.FETCH_CHANNEL_VIDEO_SUCCESS:
        return {
            ...state,
            is_fetching: false,
            search_videos: action.payload.data.items.map(item => ({
                title: item.snippet.title,
                description: item.snippet.description,
                thumbnail_url: item.snippet.thumbnails.medium.url,
                video_id: item.id.videoId,
                comments: [],
                comment_count: null,
                dislike_count: null,
                favorite_count: null,
                like_count: null,
                view_count: null,
            }))
        };
    case YOUTUBE.FETCH_CHANNEL_VIDEO_FAILURE:
        return { ...state, is_fetching: false };
    }
};

抜粋: src/sagas/youtube.saga.js

function* handleFetchChannelVideo() {
    for(;;) {
        const action = yield take(YOUTUBE.FETCH_CHANNEL_VIDEO_REQUEST);
        const response = yield call(ajax.searchChannelId, action.payload.channel_id);
        switch (response.status) {
        case 200:
            yield put(youtube.fetchChannelVideo.success(response.data));
            break;
        default:
            yield toastr.error('失敗', '通信失敗');
            yield put(youtube.fetchChannelVideo.failure());
        }
    }
}

今後の展望

弊社ではクリエイター分析ツールの開発などもやっているのでそれと連携して拡張できたら良いなぁと思います。

また、新着のsubscribe(webpush)やそのクリエイターの詳細情報など載せられるともっともっと良くなるだろうなぁと

今はクリエイターの管理がjsonで手書きで書いていて雑なのでなんとかしたいのと、あまりテストを書けてないので充実させたいなぁと src/options/youtuber.json

最後に

木下ゆうか さんの動画をデバッグに使い始めてから体感開発効率は上がるわ、体感javascript力も上がるわ、体感リロード時間も早くなるわで良いことづくめでした。本当にありがとうございました。 ← チャンネル登録しよう

UUUMではエンジニアを募集しているそうです。 www.wantedly.com www.wantedly.com

CSS3のkeyframesで はじめしゃちょーを走らせてみました

こんにちは!エンジニア(仮)のめる(@c5meru)です。
こちらの記事は、UUUM Advent Calendar 2018 19日目の記事です!

5日目の前回は、CSSでHIKAKINをかきました
HIKAKINときたので、今回もUUUMのクリエイターである、はじめしゃちょーを題材にしたいと思います!

続きを読む