Gunosy Tech Blog

Gunosy Tech Blogは株式会社Gunosyのエンジニアが知見を共有する技術ブログです。

社内管理画面を Vue + Go で作る

広告技術部のUTと呼ばれている @mocyuto です。
普段は広告配信のバックエンドを主に担当しています。 今回は社内管理画面を作った話をお伝えしたいと思います。

はじめに

Gunosyの管理画面ではRailsが多いですが、社内用管理画面を新規で作ることになり、Vue + Go のSPA(Single Page Application)で作ることにしました。 管理画面をVueとGoで作る事例は最近増えてきていますが、弊社でもすでにこの組み合わせで実績はあり、2つ目となりました。

今回の社内向けの管理画面の作成意図としては、ABテスト反映の高速化が目的です。

今までは、リリースフローは以下のようになっていました。

  • 配信チームとロジックチームをまたいでファイルでPRを送って確認。
  • マージされたら全台にデプロイ。

当時は広告技術部が1チームであったため、リリースはチーム内で完結しておりGitHubのリポジトリでABテストを管理していても問題がありませんでした。

しかし、直近メンバーが増えチームが複数になったため、ABテストに関わるチームが「配信サーバを開発・管理する配信チーム」と「配信効果の最適化を開発・運用するロジックチーム」に分かれてました。 分割された結果、ロジックチームがトラフィック量変更する際、配信チームとの確認などリリースコストが大きくなり始めていることが問題となってきました。

また、リポジトリで管理しているため、毎度全台リリースが必要なことも課題となっていました。

その問題を解決するため、KaizenDayと言われる隔週で実施するサイドプロジェクトとして管理画面の開発がスタートしました。

tech.gunosy.io

進めていくうちに要望が強まったため、これをメインプロジェクト化し本番稼働して運用が始まりました。

設計

どのように管理画面を作ったか、設計に関して説明していきます。

バックエンド

バックエンドにはGoを選択しています。 理由としては弊社ではGoを使っているプロダクトが多く、既存の資産も使えるためです。

goa

フレームワークとしてはgoaを利用しています。

goaとはユーザが設計したDSLから自動でGoのHTTPのインターフェイスを作り上げてくれるライブラリです。 インターフェイス部分を自動生成してくれるため、メインのロジックに集中することが可能です。

今回はSPAを作るため、interfaceを定義すればControllerが生成されるgoaがマッチします。

構成

パッケージ構成はを以下のようにしました。

.
├ config
│   └ config.go
├ design
├ generated // goaが自動で生成するフォルダ
│   ├ app
│   ├ client
│   ├ swagger
│   ├ tool
│   └ asset.go
├ abtest // abtestのドメインロジック
│   ├ abtest.go
│   └ repository.go
├ mysql // infra層は各ミドルウェアによって切り分けている
│   ├ config.go
│   ├ xxx.go
│   ├ yyy.go
├ user // user権限周りのドメインロジック
│   └ user.go
└ main.go

全体の方針としては、境界づけられコンテキストによってpackageを切っています。 境界ごとにパッケージを切るメリットとしては、ロジックを知りたいときにそのpackageを見れば、何をしているのかがわかるためです。

Infra層に関しては、mysqlやredisなどのようにミドルウェアごとにpackageを切っています。 境界づけられたコンテキストごとにinfra層を持つのがより良いと思いますが、同一データストアを利用しているため、データアクセス部分を一元化したかったので切り出しています。

もし、goaをやめることになっても、generatedのパッケージをControllerとして作り直せば、問題ありません。

フロントエンド

フロントエンドはVueを選択し、VuexとTypeScriptを使って実装しています。 また再利用性を高くするためにAtomic Designを採用しました。

構成

.
├ config // webpackの設定ファイル
└ src
     ├ api // バックエンドにアクセスするAPIを管理
     ├ components // vueコンポーネント
     │   ├ atoms
     │   ├ molecules
     │   └ organisms
     ├ pages  // 各ページを管理するフォルダ
     ├ routes // routingの管理
     ├ store // vuexの状態管理を集約
     ├ util // どこにも依存しないutil系をまとめた(Font Awesome etc.)
     ├ App.vue
     └ main.ts

ざっくり上記のようなディレクトリ構成にしました。

Atomic Designを採用していますが、今回はtemplatesを使いませんでした。 サイズ感的にも、templateを必要とするのは、ほんとに似たページが大量にある場合だけかと思います。

TypeScript

やはりSPAを実装する以上、フロントエンドにロジックが多く介入してきます。 その際、型があるとコンパイルしたときに人間が間違えるエラーを拾って教えてくれることが多々あります。

また、データフローにおけるデータモデルがどうなっているかが可視化されるので、ここでもバグの原因を潰してくれます。

Vuex

Vuex とは、状態を管理するライブラリです。

Vueの場合、単一ファイルコンポーネントで作ることが多いと思います。 しかし、コンポーネントの組み合わせで作っていくと、各コンポーネントが細かくなってしまうため、コンポーネント横断で状態を保持してしまうと、インプットとアウトプットがごちゃごちゃになり破綻してしまいます。 ですので、Vueのコンポーネント内ではコンポーネントの画面で使う情報だけを扱い、APIからデータを取得する部分は全てVuexに委譲することで状態を分離しています。

Atomic Design

Atomic Designとは各コンポーネントをボタンなど分割できない大きさのAtom(原子)の粒度までコンポーネントを分割して、それを組み合わせていくコンポーネント設計です。 Vueの場合、単一ファイルコンポーネントで構成することができるので、Atomic Designとの相性がとてもよいです。

Atomic Designを利用したのは、再利用性もさることながら、最初に作り上げてしまえば各コンポーネントのデザインを意識することなく作れるからです。

  • atoms: ボタンなどの分割できない最小の粒度
  • molecules: 分子atomsをいくつか組み合わせてできる粒度のコンポーネント
  • organism: pagesに依存したコンポーネント。vuexなどの状態と会話するのはこのレイヤー以上。
  • pages: 各URLでアクセスしたページ全体を管理。

上記のようにディレクトリを切っています。 色々な箇所で使い回すatomsやmoleculesのコンポーネントでは状態を持たず、状態へのアクセスすらしません。 状態へアクセスするのは、organismsかpagesにいるコンポーネントからとなります。

このように粒度を順序立ててコンポーネント化することで、新しく参加した人がどこを修正すればいいのかの範囲が分割されていてすごく機能的な設計手法だと感じています。

まとめ

今回の内容は設計に寄った話が主となりました。 意識して設計した点としては、疎結合による差し替えのしやすさです。 やはり長く運用していると、ライブラリの更新が止まったり、バージョンアップなどで適合しなくなったりします。その際に、いかに差し替えをしやすくするかを考えることが設計の重要さと思っています。

また使ってみたい技術と差し替えのしやすさの両立をうまくしていくことでチャレンジングな設計ができるかなと思います。

設計に正解はないので、まだまだ自分ならこう設計するという熱く議論できるエンジニアを募集しています。

www.wantedly.com