どっかああああちゃあああんんんん

前略

酸いも甘いもドカDocker。 最近、諸事情で機械学習がらみの個人プロジェクトにハマっていたのだが、そのアーキテクチャにDockerを導入した。そのアーキテクチャが以下。

f:id:yuyaito:20190612235504p:plain
アーキテクチャ

この構成だとMongoDB以外はセットアップが鬼楽だけど、今後キャッシュサーバーにRedisとかを導入することなど見越してDocker構成に切り替えた。導入過程でメリットデメリットあったので書いていく。

デメリット

難しい

Dockerの構成ファイル難しすぎじゃヴォケ!!とくにポート関連とかボリューム関連のお前! 幸いnode界隈には先人たちの知恵がネット上に落ちているのでなんとかなったが、ホットリローディング効かせるのとかめっちゃ時間かかったわ!!

重い

f:id:yuyaito:20190613000033p:plain
4GB
メモリ4GBって舐めとんのか!!! メモリ8GBのわしのmacbookは重くて何回もフリーズしてまうわ!!

メリット

環境依存が消える

おしゃ、PCのOS変えたで〜さて環境構築構築。。。

docker-compose up

え?終わり?神〜〜〜

マイクロサービスにつながる

さーてRedis入れて、Golang入れて。。。え、Redisはpython2依存でGoはpython3依存。。。?ひとつのサーバーインスタンスでバージョン管理しなきゃいけないの。。。?つら・・・。

Docker: コンテナに分ければいいんだよ!

なるほどお!

Kubernetes

さーてサービスも大きくなってきたことだし、スケーリングに耐えられるようにしなきゃ!えーと、AWS EBだと。。。え、コンテナ群をそのまま増やしてくだけなの!?それ非効率じゃない。。。?

Kuberstes: わたしがいます

くべちゃん。。。!(お金かかるから自分で動かしたくない)

結論

メモリ16GB以上だったら即導入すべき。8GBだったら導入してはならない。

サイバーエージェントのWebフロントエンドチャレンジで賞をもらった

f:id:yuyaito:20190515230224p:plain サイバーエージェントのWebフロントエンドチャレンジに行ってきた 概要 2019/3/10~11の二日間で開催されたサイバーエージェントのWebフロントエンドチャレンジに参加した。端的に最高だったのでシェアしたい。 チャレンジのお題は以下のようにギャラリーアプリを作成し、その出来を競うというもの。

f:id:yuyaito:20190515230235p:plain

APIを叩けるか リスト表示を実装できるか ルーティングを実装できるか

といったフロントエンドの基礎的な技術を要求されているように感じた。 びっくりしたのがサポートの手厚さで 参加人数10人なのにメンターが8人(しかも全員ガチエンジニア) 交通費、宿泊費支給 ランチ、魔剤、その他諸々支給

など、ひとりあたりN万円かかっているサポートがあった。 個人的な振り返り はじまってすぐにcreate-react-appでつくりはじめ、初日終了の時点で要件を満たすものはだいたいつくり終えていた。 初日の終わり際にみんなのものを見る機会があり、思ったのが 「みんなできてるし、あんま差がつかないやつやん(´・ω・`)」 そこで、家に帰ってから二日目の戦略を立て直した。

f:id:yuyaito:20190515230253p:plain f:id:yuyaito:20190515230304p:plain

この戦略は当たりで、実際に 昼夜関係なく見やすいようにナイトモードを導入 読み上げに対応するようにaltやaria-labelを付与 キーボード操作に対応

などを二日目に実装し、そこが評価されてアクセシビリティ賞をいただくことができた。

いっぽうで大反省なのが技術的なアプローチ部分。 アーキテクチャ

しっかりコンポーネントを分けるべきだった。すべてをAppクラスにまとめていたので、あとから読んでもよくわからない上にReactのライフサイクル的にもよろしくない。 そもそものコード

これ書いたの本当におれ。。。?(´・ω・`)

// 冗長なコード
onKeyDown={target => {
              console.log(target.keyCode, target.ctrlKey);
              if (isMobile.any) return;
              if (
                target.keyCode === 39 ||
                (target.ctrlKey && target.keyCode === 70)
              ) {
                targetIndex + 1 < images.length &&
                  this.setState({
                    targetIndex: this.state.targetIndex + 1
                  });
              }
              if (
                target.keyCode === 37 ||
                (target.ctrlKey && target.keyCode === 66)
              ) {
                targetIndex > 0 &&
                  this.setState({
                    targetIndex: this.state.targetIndex - 1
                  });
              }
              if (
                target.keyCode === 40 ||
                (target.ctrlKey && target.keyCode === 78)
              ) {
                targetIndex + 5 < images.length &&
                  this.setState({
                    targetIndex: this.state.targetIndex + 5
                  });
              }
              if (
                target.keyCode === 38 ||
                (target.ctrlKey && target.keyCode === 80)
              ) {
                targetIndex > 4 &&
                  this.setState({
                    targetIndex: this.state.targetIndex - 5
                  });
              }
            }}
            
            
// スタイル直書き
 {targetIndex !== null && (
              <img
                alt={images[targetIndex].title}
                style={{
                  objectFit: "contain",
                  width: "100%",
                  maxHeight: "500px",
                  borderStyle: "none",
                  backgroundColor: "black"
                }}
                src={images[targetIndex].url}
              />
            )}

ルーティング

React-routerをあまり理解してなく、シェアを前提としたルーティングを実装しなかった。 (ちなみに、ここらへんの反省点は終了後にちゃんとリファクタしたので、気になる方はレポジトリ見てください☆彡 (まさかり、コードレビュー大歓迎です)) アクセシビリティ せっかくアクセシビリティ賞を頂いたのだから、アクセシビリティのことも触れておく。 厚生労働省ではアクセシビリティを 年齢や身体障害の有無に関係なく、誰でも必要とする情報に簡単にたどり着け、利用できること と定義している。たしかに誰もが利用できないWebなんて、Webじゃないよね。 わたしはReactを使ったのだが、Reactでアクセシビリティを満たすような例を挙げると、 aria-labelを入れる 無駄なdivを使用せず<>を用いる フォームにはラベルをつける

などがある。より詳しくはReactドキュメントに記載されている。普段開発をしていて絶対に気を遣わない部分なので、こういうことを知れて良かった。 感想 社員さんと参加者が全員優秀だった。 キーボード操作をどう実装しようか迷った際に、すぐに公式ドキュメントにある合成イベントの存在を教えてくれたメンター@uenitty フォーカスを動かしたいと相談した際に、refを用いる手法を思いつき教えてくれた@pagu0602 (実際にベストプラクティスだった) リファクタの際にめちゃくちゃコードを参照させてもらい、メンターなのになぜかガチで優勝を狙いにきていたカメラマン@at_sushi_at フリー素材イケメンの@konojunya アルバム作成機能をなんと半日で実装し優勝した、世にも珍しいOneplus使いのどらくん@dorayaki その他書ききれないほどの方々 自分にとって今まで参加したインターンの中でいちばん楽しかった。お世話になりました。

Reactで触るPWA (Progressive Web App)

PWAが熱い!

こんにちは、生きているだけで赤字のYIPGです。

最近PWA界隈が熱いですね! Googleが(プレイストアでPWAの公開ができるようになりました😱)という記事を出してくれたおかげで、アプリ開発者界隈でPWAがバズっている様子です。

Hacker Newsでも小さいPWAアプリ-なぜPWAを作り続けるのかという記事をみかけたり、iOS12でもbeta版ですがPWAに対応しようとしています。(iOS12.2βでのPWA -良い点、悪い点、そして疑問点について)

PWAがどのような挙動をするのか知りたい方はGoogle Maps GoTwitter LiteInstagram Liteをお試しください.

とくにGoogle Maps Goは軽いカルチャーショックを受けると思います。 image.png

本題

PWAが熱いことはわかったのですが、実際にデベロッパーにどのような恩恵があるのでしょうか。このポストでは、なるべく簡単にPWAアプリをつくってみましょう。

1. 環境構築

まずはnodeを入れましょう。公式サイトから安定版をインストールします。

$ node -v
v10.12.0 //バージョンは異なる可能性があります

バージョンが出れば、nodeが入っているので大丈夫です。

2. Create-React-App

次にReactアプリの雛形を作ってみましょう。

npx create-react-app pwa-sample

現在のディレクトリにpwa-sampleフォルダがあるのがわかりますか?pwa-sampleフォルダに移動しましょう。

$ ls
pwa-sample
$ cd pwa-sample

また今回はUIのためにMaterial-UIを用います。Material-UIもインストールしときましょう。

// pwa-sampleディレクトリにて
// npmの場合
npm install @material-ui/core typeface-roboto

// yarnの場合
yarn add @material-ui/core typeface-roboto

3. npm start or yarn start

さて! いよいよ開発っぽくなってきます。npm start or yarn startで初期画面を表示してみましょう!

// npmの場合
$ npm start

// yarnの場合
$ yarn start

ブラウザ上で下記のような画面が表示できれば開発環境はバッチシです! image.png

次にページを最初のものから取り替えます。pwa-sampleディレクトリ内のApp.jsを下記のコードと交換しましょう!

import React, { Component } from 'react';
import 'typeface-roboto';
import Fab from '@material-ui/core/Fab';
import Typography from '@material-ui/core/Typography';
import PlayArrowOutlinedIcon from '@material-ui/icons/PlayArrowOutlined';
import PauseOutlinedIcon from '@material-ui/icons/PauseOutlined';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {powerOn: false, speed: 0}
  }

  componentWillUnmount(){
    this.setState({
      powerOn:false
    })
  }

  getNetwork = () => {
    const connectionInfo = navigator.connection

    this.setState({
      powerOn: true,
      speed: connectionInfo.downlink
    })

    const changeFunc = (e) => this.state.powerOn && this.setState({speed: e.target.downlink})

    navigator.connection.onchange = changeFunc;
  }

  render() {
    return (
      <div style={{
        background: 'linear-gradient(to right bottom, #ffefba, #ffffff)'
      }}>
        <div 
          style={{height: '100vh',display:'flex',flexDirection:'column', justifyContent:'center', alignItems:'center'}}
        >
          <Typography style={{marginBottom: 50}} variant="h1">{this.state.speed}Mbps</Typography>
          {!this.state.powerOn ?
          <Fab style={{
            width: 120,
            background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
            border: 0,
            boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
            color: 'white'
          }} variant="extended" size="large" onClick={this.getNetwork}><PlayArrowOutlinedIcon/>Start</Fab>:
          <Fab style={{
            width: 120,
            background: 'linear-gradient(45deg, #2196F3 30%, #21CBF3 90%)',
            border: 0,
            boxShadow: '0 3px 5px 2px rgba(33, 203, 243, .3)',
            color: 'white',
          }} color='secondary' size="large" variant="extended" onClick={()=>this.setState({powerOn:false, speed:0})}><PauseOutlinedIcon/>Stop</Fab>
          }
        </div>
      </div>
    );
  }
}

export default App;

コピペしてみると、先程のページが下記のような表示になるはずです。 image.png これで簡単なアプリケーションが完成しました。 Startボタンを押すと、現在のネットワーク速度が表示されます。ためしにChrome dev toolでネットワーク速度を変更すると、ちゃんと動作しているのが確認できますね。

pwa-sample.gif

4. PWA化

ここまででアプリケーションは完成しているのですが、まだ完全なPWAではありません。 オフラインでもアプリを起動できるよう、下記のようにindex.jsとpublic/manifest.jsonを変更しましょう!

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA

// serviceWorker.unregister()ではなく、registerしよう!
serviceWorker.register();
{
  "short_name": "ネット速度計",
  "name": "インターネット回線の速度をリアルタイムで!",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    },
    {
      "src": "speed.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ],
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#ffefba",
  "background_color": "#ffffff"
}

ホーム画面のアイコンも用意しましょう。(サイズは192×192) speed.png

5.ビルド

以上で準備は整いました。開発環境ではPWAは機能しないので、早速ビルドして公開してみましょう。

// npm
$ npm run build 
// yarn
$ yarn build

ビルドしたらnetlifyで公開しましょう。netlifyが一番ラクでいいですね。 netlifyに登録したら自身のページにとんでbuildフォルダをドラッグアンドドロップしましょう。

.
├── README.md
├── build //これ!
├── node_modules
├── package.json
├── public
├── src
└── yarn.lock

image.png

完成!

完成したサイトはこちらになります。 https://friendly-leakey-d3614a.netlify.com/

LightHouseのスコアは非常に良いです。PWA認定も頂いています。 image.png

Androidでサイトを開くと、下記のようにインストールバナーが表示されます。 image.png

アイコンも設定したものになりました。 image.png

6. Ta-da!

以上、現状もっとも簡単なPWA構築でした!今回はネット速度の計測ですが、みなさんも面白いAPI探してPWAつくってみましょう!

あの名言で締めくくりましょうか。。。

多分これが一番早いと思います。

gRPC + MongoDBでブログAPI作った

f:id:yuyaito:20190515224430p:plain

自作のアプリをつくる際、わたしはほとんどすべてをFirebaseで賄ってきました。

f:id:yuyaito:20190515224444p:plain
はじめての自作アプリ 2018/12

f:id:yuyaito:20190515224517p:plain
はじめての自作ネイティブアプリ2019/1

Firebaseでは、Javascript側で認証やデータベースを直接操作できるので、バックエンドの知識がなくともサービスを完成させることができます。

ここいくらの例を紹介します。ここいくらではFirestoreに地価情報を保存していて、以下のフローで情報を取り出しています。

componentDidMount(){
    firebase.auth().signInAnonymously()
      .catch(err => this.setState({errorMessage: err}))
  }

// Firestoreでユーザーのみ読み込み可能というセキュリティルールを設定する
service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      allow read: if request.auth.uid != null;
      allow write: if false;
    }
  }
}


getTheDetailOfLocation = async (location) => {
    try {
      // まず都道府県コードを取得
      

      // firestoreから周辺地価データを取得
      let doc = await firebase.firestore().collection('cities').doc(this.state.cityCode).get()
      this.setState({
        firestoreData: doc.data().data
      });

firebaseでは、認証機能付きのDBをすぐに利用できます。またFirestoreはリアルタイム通信に対応しているため、DBに変更があったタイミングを受け取ることができます。これによりチャットや位置情報のリアルタイム更新も可能です。

一回なれてしまうと、Firebaseを用いたアプリ制作はとても簡単で、実際にここいくらは2日でつくりました。

しかし一方でこれで良いのか?という自責の念もありました。yarnするたびにfirebaseの裏側で走るgRPCという文字、Googleのwebファンダメンタルで広がる無知の世界、firestoreの底の見えない課金金額。

自分で学ぶ必要がないというのは快適ですが、同時に恐怖でもありました。

また、自作アプリCocoikuraを作っている過程で国土交通省の不動産価格取得APIを利用した際に、API設計にも興味がわきました。(自分でもっと良いAPIを提供したいという思い)

不動産価格取得APIではURLを叩くと以下の情報が返されます。

{"Type":"中古マンション等","MunicipalityCode":"14101","Prefecture":"神奈川県","Municipality":"横浜市鶴見区","DistrictName":"朝日町",
"TradePrice":"7100000","FloorPlan":"3LDK","Area":"60","BuildingYear":"昭和55年","Structure":"RC","Use":"住宅","Purpose":"住宅",
"CityPlanning":"準工業地域","CoverageRatio":"60","FloorAreaRatio":"200","Period":"平成27年第1四半期","Renovation":"未改装"}...

ここいくらでは一坪価格、つまりPrice情報だけがほしいのですがこのデータだと、Price情報は全体の1/15バイト程度しかありません。

これは明らかにデータの無駄遣いで速度、通信量の両視点から望ましくないです。

どうすりゃええんだ。。。 解決策を求め、Udemyでこんなコースを受講しました。

www.udemy.com

gRPCがなぜ有用かについてHTTP2の恩恵、protocol buffersからJAVA, go, nodeなど一部を除くほぼすべての言語に変換可能であることなど腑に落ちる導入に始まります。

この授業の最終成果物は以下のCRUDのサーバー、クライアント側の実装です。リスト表示だけサーバーサイドストリーミングに対応しています。

service BlogService {
    rpc CreateBlog (CreateBlogRequest) returns (CreateBlogResponse);
    rpc ReadBlog (ReadBlogRequest) returns (ReadBlogResponse); // return NOT_FOUND if not found
    rpc UpdateBlog (UpdateBlogRequest) returns (UpdateBlogResponse); // return NOT_FOUND if not found
    rpc DeleteBlog (DeleteBlogRequest) returns (DeleteBlogResponse); // return NOT_FOUND if not found
    rpc ListBlog (ListBlogRequest) returns (stream ListBlogResponse);
}

gRPCのコマンドライン上クライアントであるEvansを用いると以下のようにコマンドでインタラクティブに操作できます。

f:id:yuyaito:20190515225421p:plain

tl; dr (まとめ)

Go言語、gRPCの導入としてとても良い講座だったと思います。講師に感謝です。