受託開発の会社からvivitに入社して感じた4つのこと

こんにちは!今年12月からレンタルチーム(hinata rental)のバックエンドエンジニアとして入社した河原田です。

私はvivitに入社する前は受託開発会社のバックエンドエンジニアとして3年弱Golangを書いていました。

自社サービスを開発するのは初めての経験だったのですが、vivitに入社して感じた4つのことを紹介していこうと思います。

すごい数のマイクロサービスが動いている

vivitではキャンプに関する様々なサービスを展開しています。

各サービスのフロント・バッククエンド、それらを繋ぐBFF、ドメイン知識の関係ない機能など多数のマイクロサービスが存在します。

私が担当しているサービスは、レンタルサービスのバックエンドですが、これだけ多数のマイクロサービスが連携しているプラットフォームの開発経験はなく、とても新鮮な気持ちでいます。

体験しているメリットとしては、デプロイが容易であったり、1つのマイクロサービスをほぼ1人で担当することになるので、担当できる業務範囲が広いところでしょうか。

また一方でマイクロサービスが多いことで、管理の手間やサービスのオーナーが曖昧なものができたりと、縮小した方が良いのでは?といった議論なども行われています。

そんな経験も多くのマイクロサービスを動かしている環境ならではの話なので自分にとってはこれまた新鮮な体験です。

それぞれのサービス間の通信はgRPC+protobuf、GraphQLを使っており、スキーマ定義がきちんと書かれているので、定義を見ればどんな通信がされるのかは一目瞭然。

余分な説明も必要なく、開発にもスッと入ることができました。

vivit.hatenablog.com

ドキュメントでの業務知識・ナレッジが豊富

vivitでは基本的に各プロダクトにフロント・バックエンドのエンジニアがそれぞれ1人というチーム構成です。

そのため参画後、引き継ぎを行ったとしても細かい業務知識や実装ロジックをキャッチアップしないければいけない場面もあります。

受託開発を行っていたときは他社からの引継ぎ・ドキュメントが不十分で、何が行われているか、何が正かを理解するためにネストが深いコードを読んで、、、なんてことも多々ありました。

しかし、vivitでは全社でDocBaseというツールでドキュメント管理されています。 開発の経緯や要件すり合わせの記録、必要な業務知識など、困った時はとりあえずDocBaseで検索をかけると、必要な知識やとっかかりが得られるので、自分で考えるにしろ、人に聞くにしろ、仕事を進めるとっかかりを得やすい環境が整っていると思います。

最近だとドメイン駆動設計について理解が必要な場面があったのですが、数年前に行われた輪読会の内容まで細かに残っており、理解の手助けになりました!

マイクロサービス間でナレッジを盗める

vivitのバックエンドは主にGolangで書かれています。

そのため各サービスのバックエンド同士でコードレビューする機会があったり、リポジトリも自由に見ることができるので、他のサービスがどうやって書かれているのか気軽に参考にできます。

ちょっと前のことですが、レンタルの実装でDataLoaderを利用する機会があったのですが、お隣サービスのスポットのバックエンドをがっつり盗み見て、実装したことを覚えています。笑

vivit.hatenablog.com

心理的安全性が高い

 最後になりますが、vivitに入社してすごく感じることが心理的安全性がとても高くいなと感じています。特に自分が思っていることを発言しやすい。

いろいろな要因があると思うとのですが、私がそう思えるのはvivitとして心理的安全性を作ろうとする取り組みを、日々感じられるからだと思っています。

  例えば、開発部内では週1回、各事業の振替りを行うのですが、その目的の一つとして「 開発全員顔をあわせる場の創出」「より多くの人がこの場で話すことによる、部員間の心理的障壁の解消」ということが、しっかりと書かれています。

またskackチャネルでは個人のtimesチャンネルで自由に発言できる環境があったり、1on1に力を入れていたりと「個人が安心して働ける環境を作ろうとしてくれている!」と感じる機会が多いです。

vivit.hatenablog.com

実際に過去のアンケートでも、心理的安全性が高い!という意見が多く、納得の結果だなと思っています。

vivit.hatenablog.com

さいごに

いかがだったでしょうか?

vivitに興味を持ってもらった方に、入社したての感じたことを知ってもらおうと思いこの記事を書きました。

vivitではより多くの人が「もっと外が好きになる」ために、キャンプの楽しさを広げるお手伝いをしています。

キャンプが今好きな人も、これからキャンプを初めたい人も、一緒に働いてくれる仲間を募集中です。

気になったことがあれば、是非、お気軽に話を聞きに来てください!

【新卒採用】 www.wantedly.com

【中途: バックエンドエンジニア】 www.wantedly.com

【中途: フロントエンドエンジニア】 www.wantedly.com

gqlgenを使ったファイルアップロード機能を実装して感じたこと

キャンプ場を検索・予約できるサービス(以下hinata スポット)などの開発を担当している名嘉眞です。

hinata スポットなどvivitのいくつかのプロダクトでは、フロントエンドとバックエンドの間にBFFが存在しており、BFFではGraphQLを採用しております。 またBFFはGoで書かれていて、gqlgenというライブラリを使用しています。

今回は画像ファイルのアップロード機能をgqlgenを使って実装した話をします。

実装内容としては、gqlgenのドキュメントに記載されているファイルアップロードの例とほぼ同じです。 gqlgen File Upload このドキュメントを見てもらえれば実装はできるのですが、実装に際して感じたことを書いていこうと思います。

gqlgenのドキュメントを読む前

今回ブログとして書くきっかけになったタスクに着手する以前から、他のマイクロサービスで既に画像アップロード機能はありました。 実装内容としては、画像アップロードのmutationのパラメータに画像ファイルをbase64エンコードして含めるという方法でした。 この方法だと以下のような点で使いにくさがありました。

base64エンコードでアップロードする方法の使いにくい点

  • ファイル名はファイルとは別で渡す必要がある

  • string型で受け取るので扱いにくい(例:GCSなどにアップロードする際にio.Readerに変換する必要がある)

  • テストコードを書く際もファイルをbase64エンコードした文字列が必要

gqlgenのFile Uploadのドキュメントを読む

gqlgenのドキュメントに以下のように記載されています。

Graphql server has an already built-in Upload scalar to upload files using a multipart request. It implements the following spec https://github.com/jaydenseric/graphql-multipart-request-spec, that defines an interoperable multipart form field structure for GraphQL requests, used by various file upload client implementations. To use it you need to add the Upload scalar in your schema, and it will automatically add the marshalling behaviour to Go types.

GraphQLの様々なクライアントで運用可能なマルチパートフォームの仕様に準じて実装していて、使用するためには Upload スカラーの追加が必要とのことです。

仕様として公開されている方法でファイルアップロードの機能を実装できることはコードの保守性などを考えても良いことだと感じます。

以下のように Upload スカラーの追加することで、Upload型が定義できるようになります。

scalar Upload

type Mutation {
  uploadImage(input: UploadImageInput!): UploadImageOutput!
}

input UploadPlanImageInput {
  """
  画像データ
  """
  file: Upload!
}

生成されたGoのコードについて

gqlgenを利用してGraphQLスキーマからGoのコードを生成した結果が以下のようになります。

type MutationResolver interface {
    UploadImage(ctx context.Context, input model.UploadImageInput) (*model.UploadImageOutput, error)
}

type UploadImageInput struct {
    // 画像データ
    File graphql.Upload `json:"file"`
}

type UploadImageInput のFileフィールドは、graphql.Upload となっています。 この graphql.Upload は以下のような構造体です。

type Upload struct {
    File        io.Reader
    Filename    string
    Size        int64
    ContentType string
}

ファイル自体はio.Raader型で定義されていて、ファイル名やファイルサイズ、ファイルの種類も定義されていて使いやすそうです。

ここでこの記事の冒頭に記載したbase64エンコードでアップロードする方法の使いにくい点を振り返ってみます。

base64エンコードでアップロードする方法の使いにくい点

  • ファイル名はファイルとは別で渡す必要がある

  • string型で受け取るので扱いにくい(例:GCSなどにアップロードする際にio.Readerに変換する必要がある)

  • テストコードを書く際もファイルをbase64エンコードした文字列が必要

上記の使いにくい点がUpload型で改善できます。

  • ファイル名が構造体のフィールドとして定義されている

  • ファイルがio.Reader型なので扱いやすい。

  • io.Readerはinterfaceなのでテストコードも書きやすい(io.Readerを実装した別のオブジェクトに差し替えることができる)

使いにくい点が全て解決できました。もちろん全てユースケースにあてはまるわけではないと思いますが、基本的にはgqlgenを使う場合のファイルアップロードはUpload型を使っていきたいと思いました。

さいごに

vivit では、エンジニアを積極採用中です!

ポジションも様々用意しておりますので、GoやGraphQLを使用したプロダクト開発に興味を持って頂いた方は、是非カジュアル面談にお越しください。

新卒採用

www.wantedly.com

キャリア採用

www.wantedly.com

www.wantedly.com

www.wantedly.com

皆様とお話できるのを楽しみにしております!!

Cypress を使った E2E テストを GitHub Actions で実行する

こんにちは!!

vivit で SRE をやっている 宮本 です。

今回は hinata レンタル で行っている E2E テストを GitHub Actions で実行できるように CI パイプラインを構築したので、構成やポイントなどを紹介したいと思います!!

要件とか

  • コミットを GitHub に push したタイミングで E2E テストを実行する
  • E2E テストなので、依存する複数のマイクロサービスも立ち上げる必要がある
  • マイクロサービスのソースコードは CI を実行するリポジトリとは別のリポジトリに存在するものもある
  • 多少実行時間がかかったとしても、なるべくマイクロサービスをモックにすることなく、本番に近い環境でテストすることを優先する
  • vivit は開発環境、本番環境共に k8s を使っているので、CI上も k8s で環境を作れると嬉しい
  • E2E テストのプロセス自体は k8s 上ではなく CI 内で直に実行する
  • バックエンドマイクロサービスが使用するテストデータは簡単に変更できるようにする

vivit の技術スタックについては 、

vivit.hatenablog.com

をご参照ください 🙏

実際の構成

全体の構成を説明するのに不要な部分は削除したり、適宜内容を変えています。
そのままコピペしても動きませんので、ご了承ください。

ざっと以下のような構成となります!

name: E2E test

on:
  push:
    branches:
      - master
  pull_request:
    paths:
      - "aaa/**"

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - id: auth
        uses: google-github-actions/auth@v0.8.0
        with:
          credentials_json: secret

      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v0.6.0
        with:
          project_id: secret

      - run: gcloud --quiet auth configure-docker

      - uses: actions/checkout@v3
        name: clone aaa
        with:
          repository: aaa
          ref: master
          path: aaa
          fetch-depth: 3
          token: token

      - name: pull aaa image
        working-directory: aaa
        run: |-
          git checkout master
          tags=($(git rev-parse HEAD | cut -c 1-7) $(git rev-parse HEAD~1 | cut -c 1-7) $(git rev-parse HEAD~2 | cut -c 1-7))
          for tag in "${tags[@]}"; do
            json=$(gcloud container images list-tags asia.gcr.io/secret/aaa --format=json --filter="tags:${tag}")
            if [ "$(echo "$json" | jq '. | length')" -eq 0 ]; then
              echo "docker image(asia.gcr.io/secret/aaa:${tag}) doesn't exists."
            else
              docker pull asia.gcr.io/secret/aaa:"${tag}"
              break
            fi
          done
          image_id=$(docker images --format "{{.Repository}}:{{.Tag}}:{{.ID}}" | grep asia.gcr.io/secret/aaa | cut -f 3 -d ":")
          docker tag $image_id aaa:test

        # 他にも同じ方法で複数マイクロサービスの docker イメージを pull してくる

      - name: start minikube
        id: minikube
        uses: medyagh/setup-minikube@master

      - name: docker build bbb
        uses: docker/build-push-action@v3
        with:
          context: .
          push: false
          load: true
          tags: bbb:test
          file: bbb/Dockerfile
          cache-from: type=local,src=/tmp/.buildx-cache-bbb
          cache-to: type=local,dest=/tmp/.buildx-cache-bbb-new,mode=max

      - name: minikube image load
        run: |
          minikube image load aaa:test
          minikube image load bbb:test

      - name: apply manifest
        run: |
          kubectl apply -k e2e/kubernetes
          sleep 3
          kubectl get pods -o wide

      - name: minikube service mysql
        id: minikube-service-mysql
        run: |
          mysql_address=$(minikube service mysql --url)
          host=$(echo $mysql_address | sed "s/http:\/\///g")
          arr=(`echo $host | tr ':' ' '`)
          echo "::set-output name=mysql_host::${arr[0]}"
          echo "::set-output name=mysql_port::${arr[1]}"

      - name: minikube service bbb
        id: minikube-service-bbb
        run: |
          bbb_url=$(minikube service bbb --url)
          echo "::set-output name=BBB_URL::$bbb_url"

      - name: Use Node.js 13.14.0
        uses: actions/setup-node@v1
        with:
          node-version: 13.14.0

      - name: yarn install
        working-directory: frontend
        run: yarn

      - name: insert test data
        run: |
          mysql \
          -h ${{ steps.minikube-service-mysql.outputs.mysql_host }} \
          -P ${{ steps.minikube-service-mysql.outputs.mysql_port }} \
          -uuser -ppass < e2e/data/bbb.sql

      - name: Run E2E Test
        working-directory: frontend
        env:
          API_URL: ${{ steps.minikube-service-bbb.outputs.BBB_URL }}
        run: |
          yarn build
          yarn test:ci

順番に説明していきます。

1. docker, gcp などが使えるようにセットアップ

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - id: auth
        uses: google-github-actions/auth@v0.8.0
        with:
          credentials_json: secret

      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v0.6.0
        with:
          project_id: secret

      - run: gcloud --quiet auth configure-docker

ここは特に言うことありません。
後々、GCP のイメージレジストリである gcr から docker イメージを pull してくる必要があります。
なので GCP プロジェクトに対して認証を行っています。

2. 必要なマイクロサービスのリポジトリを clone してくる

      - uses: actions/checkout@v3
        name: clone aaa
        with:
          repository: aaa
          ref: master
          path: aaa
          fetch-depth: 3
          token: token

依存するマイクロサービスが複数あります。
それらのマイクロサービスが存在する vivit organization の他リポジトリを fetch-deps 3 で clone してきます。

vivit の場合はコミット SHA をタグに使って docker イメージを継続的にビルドしています。
そのため、最新の docker イメージタグを取得するためにリポジトリを clone しています。

3. 必要なマイクロサービスのイメージを gcr から pull してくる

      - name: pull aaa image
        working-directory: aaa
        run: |-
          git checkout master
          tags=($(git rev-parse HEAD | cut -c 1-7) $(git rev-parse HEAD~1 | cut -c 1-7) $(git rev-parse HEAD~2 | cut -c 1-7))
          for tag in "${tags[@]}"; do
            json=$(gcloud container images list-tags asia.gcr.io/secret/aaa --format=json --filter="tags:${tag}")
            if [ "$(echo "$json" | jq '. | length')" -eq 0 ]; then
              echo "docker image(asia.gcr.io/secret/aaa:${tag}) doesn't exists."
            else
              docker pull asia.gcr.io/secret/aaa:"${tag}"
              break
            fi
          done
          image_id=$(docker images --format "{{.Repository}}:{{.Tag}}:{{.ID}}" | grep asia.gcr.io/secret/aaa | cut -f 3 -d ":")
          docker tag $image_id aaa:test

step2 でテストに使用するイメージタグが取得できたので、あとはイメージレジストリから docker pull してくるだけです。

この時、イメージのビルド中であるなどの理由で、gcr 上にイメージが存在せず pull に失敗する可能性があります。
それを防ぐために、力技ですが最新のイメージが存在しない場合には次に新しいイメージを取ってくるようにしています。

pull が完了したらタグをテスト用に書き換えて終了です。

これをテスト実行に必要なマイクロサービスの数だけやっていきます!

4. k8s クラスターを立ち上げる

      - name: start minikube
        id: minikube
        uses: medyagh/setup-minikube@master

いよいよ、CI 内で k8s クラスターを立ち上げます 🎉
今回の場合、シングルノード構成で問題無いので、お手軽にクラスターを構築する為に minikube を利用してみました!

minikube.sigs.k8s.io

公式で紹介されているaction を利用するだけで簡単に構築できました。

5. CI を実行するリポジトリに存在するマイクロサービスをビルドする

      - name: docker build bbb
        uses: docker/build-push-action@v3
        with:
          context: .
          push: false
          load: true
          tags: bbb:test
          file: bbb/Dockerfile
          cache-from: type=local,src=/tmp/.buildx-cache-bbb
          cache-to: type=local,dest=/tmp/.buildx-cache-bbb-new,mode=max

リポジトリで管理されているマイクロサービスに関しては gcr に存在するイメージを pull してきました。

しかし本リポジトリに存在するマイクロサービスに関しては当然、CI 実行時のソースコードでテストを行いたいですよね。
その為、docker イメージの build を行い、後のテストで使えるようにします。

6. 用意した Docker イメージを minikube 上で使えるようにする

      - name: minikube image load
        run: |
          minikube image load aaa:test
          minikube image load bbb:test

ここまでのステップで必要な Docker イメージは CI が動いている環境に用意できました。
しかし minikube は CI の上に VM を立て、その中で k8s クラスターを起動しています。

よってそのままでは CI 上にあるイメージを minikube 側で認識することができません。
minikube image load コマンドを利用して minikube 側でイメージが利用できるようにします。

7. k8s マニフェストを minikube に apply する

      - name: apply manifest
        run: |
          kubectl apply -k e2e/kubernetes
          sleep 3
          kubectl get pods -o wide

いよいよマニフェストファイルを apply してマイクロサービスを全て立ち上げます 😆

これまで用意してきたマイクロサービスのコンテナに加えて、mysql が必要なので起動します。

8. minikube service で E2E テストプロセスから k8s svc にアクセスできるようにする

      - name: minikube service mysql
        id: minikube-service-mysql
        run: |
          mysql_address=$(minikube service mysql --url)
          host=$(echo $mysql_address | sed "s/http:\/\///g")
          arr=(`echo $host | tr ':' ' '`)
          echo "::set-output name=mysql_host::${arr[0]}"
          echo "::set-output name=mysql_port::${arr[1]}"

      - name: minikube service bbb
        id: minikube-service-bbb
        run: |
          bbb_url=$(minikube service bbb --url)
          echo "::set-output name=BBB_URL::$bbb_url"

7 でも説明した通り、 minikube は VM 上で立ち上がるので普通に localhost:{ポート番号}API のエンドポイントとして E2E テストを実行しても疎通できません。。。

minikube service コマンドを使って k8s 内の Service(L4)との通信経路を確立します!
後のステップで使えるように output の処理も行います。

9. mysql にテストデータを入れる

      - name: insert test data
        run: |
          mysql \
          -h ${{ steps.minikube-service-mysql.outputs.mysql_host }} \
          -P ${{ steps.minikube-service-mysql.outputs.mysql_port }} \
          -uuser -ppass < e2e/data/bbb.sql

8 で取得した mysql のアドレスに対してテストデータを INSERT します。

テストデータ自体はシンプルな SQL が書かれたファイルです。
このファイルを変更するだけでテストデータの調整ができるようになっており、アプリケーションエンジニアが頻繁に CI を触る必要が無いようにしています。

10. E2E テストの実行!!

      - name: Run E2E Test
        working-directory: frontend
        env:
          API_URL: ${{ steps.minikube-service-bbb.outputs.BBB_URL }}
        run: |
          yarn build
          yarn test:ci

いよいよ E2E テストを実行します!

その際の API エンドポイントは先ほど minikube service で取得したものです。
環境変数経由で読み込ませることで、 これまでに構築した環境に向けて実行できます。

実行時間

テストの実行までにこれだけやっていると気になるのが実行時間ですよね 🤔
今のところ、トータルで12分程度となっています。

単体テストに比べると明らかに長いですが、ギリギリ許容範囲でしょうか?!
テスト実行の次に時間かかっているのが、 minikube の起動で1分半ほどかかっています。

毎回 k8s をダウンロードしているので、ここをキャッシュ等で早くできると10分程度にできるかもしれません 🙂

E2E テストの場合、実行時間や基盤構築などのコストが上がるのは仕方ないと受け入れているのですが、もっと良い方法があれば知りたいなと思っているところです。

さいごに

長くなってしまいましたが、今回はマイクロサービス構成の E2E テストを Github Actions で実行する方法について解説してみました。

vivit のエンジニアは今回紹介したような新しい取り組みを積極的に行っており、一緒にエンジニアリングできるメンバーを大募集しています!

少しでも興味を持って頂いた方は、是非カジュアル面談にお越しください。

新卒採用

www.wantedly.com

キャリア採用

www.wantedly.com

www.wantedly.com

www.wantedly.com

皆様とお話できるのを楽しみにしております!!

vivit の技術スタックを全て公開します!!

こんにちは!! vivit で SRE をやっている 宮本 です。

今回は vivit で使用している技術スタックを全部まとめて紹介します 🎉


全体図

vivit 技術スタック

GCPKubernetes マネージドサービスである GKE が中心となっており、マイクロサービス構成となっています。

言語、インフラ含め相当モダンな環境になっているかと思います!!
以下、分野ごとに解説します。

Frontend

TypeScript/Next.js/Vue.js

TypeScript で書かれた Next.js が基本となっています。
Jest, Cypress を使いテストも書いています。

hinata media の一部で Vue.js も使っていますが、今後使用範囲を拡大していく予定は無く、Next.js に統一していく流れです。

Backend

Go/gRPC/GraphQL/Ruby/Ruby on Rails

Go で書かれた gRPC サーバーが基本となっています。
hinata media のみ、バックエンドに Ruby on Rails を使っています。

gRPC サーバーとフロントエンドマイクロサービスの間に BFF として Go で実装された GraphQL サーバーがある構成です。

Infrastructure

Kubernetes/GCP/AWS/Terraform

一部 AWS も使っていますが、ほとんど GCP を利用しています。
GCP の各種サービスを組み合わせて基盤を構築しており、Kubernetes が中心です。

インフラは IaC を実践しており、 Terraform でコード化されています!

その他

  • Datadog
    • ログ管理、サービスのモニタリングなど
    • SRE だけでなく、アプリケーション開発者も使いこなしています
  • GitHub Actions
    • テスト、静的解析、Docker イメージのビルドなどの CI を行っています
  • CiecleCI
    • GitHub Actions ではカバーしきれない部分の CI として、一部 CircleCI も使っています
  • Shopify
    • hinata store は shopify をベースに作られています
  • エディタ/IDE
  • ArgoCD
    • Kubernetes の CD ツールです
    • GitOps については こちら に詳しく記事を書いています
  • Nginx
    • リバースプロキシとして、各マイクロサービスの前に配置しています

さいごに

vivit では、エンジニアを積極採用中です!

ポジションも様々用意しておりますので、今回の記事で書いたような技術スタックに興味を持って頂いた方は、是非カジュアル面談にお越しください。

新卒採用

www.wantedly.com

キャリア採用

www.wantedly.com

www.wantedly.com

www.wantedly.com

皆様とお話できるのを楽しみにしております!!

【23卒】エンジニアの就活した後に考える就活の軸

こんにちは!技術開発部の北條です。

今回は僕が改めて新卒としてエンジニア就活をするならどういったことを重要視するかを考えてみました。 インターンとして5ヶ月経過し、就活の時点で「知っておければ良かった、勉強しておければ良かった」という点があるので紹介します。

尚、この記事はメディアチームでの経験なので他のチームとは異なる部分があるかもしれませんが予めご了承ください。

掲げていた就活の軸

僕は「人生を豊かにするようなサービスを提供する」「ビジネスサイドとの距離が近い開発体制がある」の2点を就活の軸として掲げていました。 vivit.hatenablog.com

大学の授業でC言語を触っていたものの、本格的に勉強を始めたのが大学3年生からなのでWeb系を目指すには時期的に遅い分類になります。

技術力で、企業に貢献することはおろかアピールすることも難しいだろうと思っていました。

そのため、新卒のころからサービス志向のマインドを培い、将来は技術力の高いエンジニアの方々をまとめるPM、PLのような立場で企業に貢献しようと考えていました。

改めて就活するなら

「人生を豊かにするようなサービスを提供する」は変わらないと思います。

「ビジネスサイドとの距離が近い開発体制がある」は軸としては間違っていないのですが、それ以前にエンジニア内での開発体制を確認すべきだと思いました。

加えて下記2点について考慮すべきだったり、考えが変わったので紹介します。

技術について

そもそも技術に対しての知識や経験が乏しいため言語の他に何を見たらよいのかわからなかったのです。 今であれば、開発体制や技術的負債について考えると思います。

もちろんモダンな言語を選ぶに越したことはないのですが、言語のバージョンを古いままにしてないか、リファクタリングを積極的に行う文化があるかなどの技術的負債についての意識が高いかを確認します。

バージョンが古いが故に非推奨のものを使い続けたり、負債の影響でさらに負債を増やすような実装を強いられたりしてはエンジニアとしての成長速度は遅くなってしまいます。

vivitはサービス志向であるもののバージョンアップをしっかりと行ったり、技術的負債を残さない(あるいは後に解消する)という姿勢があります。 サービス志向だけを優先するのではなく、技術に関する姿勢もカジュアル面談などで確認しておきたいポイントだなと思いました。

キャリアプランについて

以前はリーダー的な立ち位置で補佐的にコードを書くというキャリアプランを掲げていました。 しかし、5ヶ月働いてみてやっぱりコードを書くこと自体が好きだなと強く感じています。

今後も変わるかもしれませんが、今はビジネス視点も持ったスペシャリストになりたいと思うようになりました。

キャリアプランとは正確に行き着くための地図ではなく、方向を示す羅針盤なのだと考えれば精神的にも余裕のある就活を送れたのではと思いました…笑

キャリアプランに正解はありませんし、大人達も答えを知っている訳ではありません。 そのため就活中はキャリアプランの内容ではなく、なぜそれを掲げているのか抽象化できていることが重要だと感じました。

最後に

技術とキャリアプランについてはvivitのインターンだからこそ感じたものです。

僕は50社程度しか会社を見ていませんし比較できるほどの経験はないのですが、大変満足のいく就活ができたと思っています。

vivitも会社として完璧ではありません。しかし、インターン生でありながら組織の改善に携わることができるのはベンチャーならではだと思うので、より早く貢献できるように引き続き頑張ります!

【Next.js】Firebase Authentication でパスワードの変更を実装する

はじめに

フロントエンドエンジニアの関(@kur0buchi)です。

hinataレンタルでは先日会員機能をリリースいたしました。 当記事は会員機能実装にあたり調査していく上で適当なパスワード変更のサンプルを探しても現在の環境に丁度よく流用できそうなものが見当たらなかったため執筆に至りました。

hinata-rental.me

前提

  • Next.js v9 以降
  • TypeScript v4.2.3
  • Firebase v9

この記事で利用している各技術の詳細な説明は省きます。

利用例

パスワード変更機能にも様々あると思いますが、今回は「現在のパスワード」と「新しく設定するパスワード」を渡す形式を採用することとします。

使い方としては旧パスワードと新パスワードを渡すと Promise<void> が返ってきて何かあったら catch できるくらい簡単な使用感を目指します。

バリデーションやエラーチェックは適宜行ってください。

import { updatePassword } from "path/to/logic";

export const Form = () => {
  const handleSubmit = async (oldPassword: string, newPassword: string) => {
    // パスワード更新処理
    await updatePassword(oldPassword, newPassword)
      .catch((e) => {
        // エラー処理
      })
      .then(() => {
        // 成功時処理
      });
  };

  return (
    <div>
      <button
        onClick={() => {
          handleSubmit("hoge", "fuga");
        }}>パスワード変更</button>
    </div>
  );
};

実装

下記の記事を参考にして、同じ使用感でパスワード変更のメソッドを追加する方針にしています。

zenn.dev

ディレクトリ名は一例です

// auth/app.ts
import { initializeApp } from "firebase/app";

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};

const firebaseApp = initializeApp(firebaseConfig);

export default firebaseApp;
// auth/index.ts
import {
  EmailAuthProvider,
  getAuth,
  reauthenticateWithCredential,
  updatePassword as firebaseUpdatePassword,
} from "firebase/auth";
import firebaseApp from "./app";

export const updatePassword = (
  oldPassword: string,
  newPassword: string
): Promise<void> => {
  return new Promise((resolve, reject) => {
    const auth = getAuth(firebaseApp);
    if (auth.currentUser == null) {
      return reject();
    }
    
    // クレデンシャルの取得
    const credential = EmailAuthProvider.credential(
      auth.currentUser.email || "",
      oldPassword
    );
    
    // メールアドレスの再認証
    reauthenticateWithCredential(auth.currentUser, credential)
      .then((userCredential) => {
      
        // パスワードの更新
        firebaseUpdatePassword(userCredential.user, newPassword)
          .then(() => resolve())
          .catch((error) => reject(error));
      })
      .catch((error) => reject(error));
  });
};

特筆するようなことはありませんが、クレデンシャルの取得、メールアドレスの再認証、パスワードの更新を順に処理しています。

firebase/auth/updatePassword をインポートする際のエイリアス命名の都合なので必須ではありません、お好みでどうぞ。

終わりに

vivit ではモダンな開発環境で活躍したいエンジニアを大募集中です。

www.wantedly.com

参考

最高の Kubernetes ダッシュボードを求めて ~2022春~

こんにちは。 vivit で SRE をやっている 宮本 です!

素晴らしい Kubernetes(以降 k8s) ライフを送るために欠かせないのがダッシュボードです!

ダッシュボードには見やすさ、網羅性、操作性など沢山のものが求められます。

k8s の場合はさらに、多くのコンポーネント(Deployment、それに紐づく ReplicaSet 、 Pod など)が絡み合っており、使いやすいダッシュボードを作るのは結構難しいです。

一般的なダッシュボードと違って、k8s ではリソース(特に Pod)の更新やオートスケーリングが頻繁に行われるため、今現在の Cluster の状態を自動更新で即座に表示してくれるものだと嬉しいです 🙆‍♂️

「普通に kubectl で良くない?」と思っていましたが、やはりダッシュボード使わないと冗長に感じてしまいます。


そんなこんなで、以下のざっくりとした観点から、(個人的)最高の k8s ダッシュボードを検討してみたいと思います!!

項目 説明
見やすさ 色、アイコン、レイアウトなどで見やすいものになっているか
操作性 操作は直感的か
ショートカットキーが十分使えるか
反映の速度・安定性 リアルタイムに自動更新されるか
反映速度が十分で、変に遅延したり不安定だったりしないか
セットアップの手間 どれだけセットアップの労力がかからないか

なお、評価は以下のように行います。

絵文字 評価
🤩 完璧!言うこと無し!
🙆‍♂️ 完璧じゃないけど、良いね
🙅‍♂️ 改善の余地が大きい

検討対象

GKE ダッシュボード

項目 評価 コメント
見やすさ 🤩 GKE に限らずですが、GCP のコンソール画面は本当に見やすいです!
完結にまとめられていて、美しいです。
操作性 🙆‍♂️ 操作は直感的で迷うことはありません。
GCP だからこそ、LB や証明書の画面にすぐ移動できるのも素晴らしいです。
ブラウザなので仕方ないですが、ショートカットキーがほとんど使えないのが難点です。
反映の速度・安定性 🙅‍♂️ 安定していますが、リアルタイム更新機能はありません。
また、UI のためなのかページの読み込み速度がちょっと遅く感じることが多いです。
セットアップの手間 🤩 GKE を使うなら、正真正銘の手間0です!

Lens

項目 評価 コメント
見やすさ 🤩 情報が完結にまとめらています
落ち着いたデザインで個人的にもすごく好みです!
操作性 🙅‍♂️ 画面操作は分かりやすいのですが、自分が触った限りだとショートカットキーがあまり充実してなさそうでした(知らないだけだったらすみません)
デスクトップアプリなので、ここは残念なポイントでした。
反映の速度・安定性 🤩 リアルタイム更新が出来るのが素晴らしいです!
速度も問題無く、言うこと無しです。
セットアップの手間 🙆‍♂️ デスクトップアプリなので、別途インストールが必要です。
ただし homebrew で入れられますし、kubectl 以外の設定も特に不要なので簡単です。

k8slens.dev

IDE と記載がありますが、これで何か開発するわけではありません。
普通のダッシュボードだと思って頂ければOKです。

補足ですが、Prometheus を使って Pod メトリクスを見る機能も付いています!
vivit では Datadog を使っていて Prometheus は未使用なので恩恵は特に無いです。

IntelliJ IDEA k8s プラグイン

私は IntelliJ を使っていますが、VSCode でも同じようなことが出来ると思います!

項目 評価 コメント
見やすさ 🙆‍♂️ 割と最低限で、特に見やすいということはありません。
必要な情報はしっかりまとまっているので問題はありません。
操作性 🤩 普段と同じ IDE の操作感でショートカットキーが使えます!
Pod の検索などもお手の物で、操作性は素晴らしいです。
反映の速度・安定性 🤩 リアルタイム更新はできません。
ただし Refresh をショートカットキーで行えるので問題無く、反映速度も早いです。
セットアップの手間 🤩 プラグインを有効にするだけなのでほぼ0です。

Pod の中に入る、ライブマニフェストを出力してくれるなど便利な機能もサクサク使えます。

Datadog

項目 評価 コメント
見やすさ 🤩 流石は Datadog で、情報のまとめ方、 UI が抜群です。
操作性 🙆‍♂️ 一部しかショートカットキーが使えません。
が、メトリクスやログなどへのジャンプが強力です。
反映の速度・安定性 🙅‍♂️ リアルタイム更新に対応しています!
しかし反映までの速度が遅いのと、削除済の Pod が表示されたり、Complete ステータスになっている Job が runnning のまま表示されるなど安定感がいまいちです。
セットアップの手間 🙅‍♂️ ダッシュボードの枠を超えているので当然ですが、k8s cluster にエージェントを入れる必要があります。
料金との相談も必要で、手間はそれなりにかかります。

「Datadog は k8s ダッシュボードツールじゃないだろ!」と突っ込まれるかもしれませんが、ダッシュボードとしても使えます。
以下のように k8s タブが存在しており、マニフェストファイルを見たり Pod の状態を確認したりと一通りのことはできます!

Datadog の強みは何と言ってもメトリクス、ログとの連携です! ここは他では再現できないレベルで素晴らしいですが、やはり料金面で選択肢に入らない場合もあると思います。

優勝決定 & 総括

優勝は、、、

IntelliJ IDEA k8s プラグイン です🥇🥇🥇

やはりショートカットキーの充実とブラウザへ切り替えなくて良い手軽さが優勝のポイントです!!
メトリクスはどうしても見られないので、必要であれば Pod 名などをコピーして Datadog にペーストするという感じです。


vivit では一緒に働くエンジニアを大募集しています 🎉

www.wantedly.com

少しでも興味を持って頂いた方は、是非カジュアル面談の応募をお待ちしております!