DELISH KITCHEN開発部の福山です。 社内向けシステムとしてAPI Serverを新規に構築する機会がありました。新規開発にあたり導入してみて良かった事をいくつかご紹介したいと思います。 新規GitHubリポジトリを用意してREST APIをGoで実装する。Webフレームワークはechoを採用。インフラはAWS ECS、RDSを利用。ログ情報はfluentd経由でS3やTreasuredataに格納しています。SentryやDatadogによる監視も行っています。 『DELISH KITCHEN』のメインAPI Serverの方で途中から採用された内容とほぼ同じ内容となります。今回は実装初期段階でpre-commit を導入しました。
pre-commitを使えばローカルでのGit commit時に任意のスクリプトを実行出来ます。 以下の処理が行われる様に設定されています。 主に実装内容の静的解析を行っておりcommit前にルールから外れたコードを発見し修正を促します。
その他go generate契機でmockファイルの作成や wire でDIコードの生成処理(*後述)を実行しています。 コードレビュー時にレビュアーがコードフォーマットや生成ファイルの有無等の指摘をする必要が無くなり、仕様やバグのチェックに集中出来る様になりました。 自動テストは導入しているのですが、更に実装初期段階からGithub Actionsにて reviewdog/golangci-lint を導入しました。
golangci-lintには 様々なLinter が用意されておりプロジェクト状況に合わせて任意のLinterを利用出来ます。 今回の開発では以下のLintチェックを有効化しています。 pushした内容に指摘対象のコードが存在する時にGithub上で以下の様に表示してくれます。
既存のシステムでMVC的な構成で苦労する場面が有りました。Goで ざっくり過ぎる図解なのですが以下の様なパッケージ構成となっております。(→は依存方向)
最低限下記は意識しつつ、ルールに拘り過ぎて工数が掛かり過ぎない様に状況に応じて詳細実装を進めました。 handlerパッケージから wire で生成されたコードでDependency Injectionされる構成となっております。
動作のポイントとしては 一般的に言われている事ですがテスタブルになりました。
MVC構成だと要件ロジックのユニットテストを書く時も依存するデータベース情報等を実際に用意する必要が有りました。しかし抽象に依存しつつパッケージレイヤーを切った事により、例えば 単一パッケージにほぼ全てのロジックを詰める様な形だと肥大したり処理の責務等は考えなくなってしまう可能性が高いのですが、
要件ロジックをどのパッケージレイヤーに書くべきかを個人的にもチーム内でも意識する様になりました。
実際にPRレビューの時にチームメンバーと議論する事が有り、結果として当初より見通しの良いコードになる事が有りました。
この部分は個々人の解釈の違いも有るので工数が膨らみ過ぎないバランスで進める様にしています。 新規開発を行うタイミングで導入して良かった事としてはじめに
前提技術スタック
pre-commit、CIでのLintチェック、パッケージをクリーンアーキテクチャ構成にする
pre-commit
- go generate
- go vet
- gofmt
- goimports
- golint
- wire
良かった事
CIでのLintチェック
- bodyclose
- deadcode
- depguard
- errcheck
- goconst
- gocritic
- gofmt
- goimports
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- misspell
- nakedret
- noctx
- prealloc
- scopelint
- staticcheck
- structcheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
良かった事
pre-commit
と同じなのですがレビュアーに指摘される前に自動チェックが行われる為、コードレビュー時に仕様やバグのチェックに集中出来る様になりました。
具体的にはgosec
でセキュリティ面での指定が有ったり、gosimple
でシンプルなコードの書き方に気付かされたりとコードの品質向上のきっかけとなります。
特に実装初期段階から導入した事によりほぼ全てのコードが随時チェックされている事になるので途中から採用するよりオススメです。パッケージをクリーンアーキテクチャ構成にする
domain/model
的なパッケージとデータの永続化処理は切り離したいと考え今回はクリーンアーキテクチャ構成で実装する事にしました。
(色々なクリーンアーキテクチャの詳細解釈が存在するのであくまで一例とお考え下さい。)- 依存方向を守る(domainは他に依存しない)
- 抽象に依存する(interfaceに依存する)
domain/repository
で定義されたinterfaceの詳細実装はinfrastructure
に存在しており、注入された内容として実行される様になっています。
他にはWebフレームワークの固有処理もhandler内に閉じておりusecase
以降には影響しない様になっています。良かった事
usecase
でのユニットテスト時にパッケージ内で扱う永続化処理に対してmockを利用し任意の結果が返却出来る事になります。
従ってテスト対象パッケージ以外の状態に悩まされる事無く確認したい要件ロジックに集中してユニットテストを行う事が出来る様になりました。
まとめ
pre-commit
、CIでのLintチェック
、パッケージをクリーンアーキテクチャ構成にする
をご紹介しました。
工数面のバランスや未経験な技術要素を導入するリスクも有りますが開発工程初期に開発効率を向上させる仕組みを用意するメリットは大きいと考えています。
まだ開発は続きますので良い仕組みを活かして効率良くアウトプットしていきたいと思います。