nazolabo

フリーランスのWebエンジニアが技術情報や近況を発信しています。

2022年の仕事と近況

2022年の仕事

去年からの続きの仕事のみで、大きく話せるようなことは特にありません。仕事をしていないわけではないですが、一度区切りをつけて来年からは大幅縮小になる予定です。

近況

2022年は引っ越したりしていました。一応首都圏ではあるものの、通勤することを完全に捨てた場所にいます。その代わり仕事部屋を手に入れたので、リモートワークで会議が頻繁に発生することにも対応できることになりました。

去年今年と辛いことが続いていますが、来年は本厄らしいのでもうだめかもしれません。がんばります。

まとめ

今後はブログを中心に情報発信を行っていこうと思います。技術的な話は Zenn にも転載していますので、技術的な話のみ読みたい方はそちらをフォローしてください。

来年からの予定は今のところ未定なので、週2〜3くらいでの案件を引き続き募集しております。詳しくは https://nazo.dev/ をご覧下さい。

最近の Terraform のディレクトリ構造の分け方

概要

modules / envs / apps でディレクトリを切っています。

基礎知識: modules と envs

誰が言い出したのかはわかりませんが、modules と envs に分けるパターンはよく見かけます。

modules は文字通りモジュールを配置するところで、 envs はそれらを使う環境が入っているという感じです。

.
├── modules
│   ├── vpc
│   ├── app
│   └── db
└── envs
    ├── production
    └── staging

modules の抽象度は、 12 factor app の バックエンドサービス くらいのイメージを持っていればいいのではないかと思います。VPC みたいな基盤は特殊として、それ以外は「外しても使える程度」を意識するのが良いと思います。

一般的に envs で書かれる tf は、modules を呼び出すだけにしておくのが良いと思います。直接リソースを書くのは最終手段です。

modules と envs はわかるけど apps って何?

全環境で VPC も DB も分けるなら必要ないですが、「複数のアプリで同一の DB を参照したい」というケースは多々あります。進捗が別々の環境を同時に作りたい場合などです。また、費用を節約するために同一の DB リソースを共有するという場合もあります。

そのような状況を考えた時、「土台のバックエンドサービスと、自分で書いたアプリケーションは別の扱いにしたほうがいいのでは」となりました。この「土台のバックエンドサービス」が「envs」で、「自分で書いたアプリケーション」が「apps」になります。

「envs」は、VPC や DB などがまとまったもので、「apps」はデプロイ単位で分けることになります。これにより、1つの VPC や DB に、複数のアプリケーションを同居させることが簡単になります。

.
├── modules
│   ├── vpc
│   ├── app
│   └── db
├── apps
│   ├── staging
│   ├── production
│   └── featurexxxx
└── envs
    ├── production
    └── staging

「envs」以下より「apps」以下のほうが必ず数が多くなります。また、「apps」は「envs」の設定に依存しますが、逆の依存は必ず存在しないようにします。

git の pre-commit hook はなるべく使わないほうがいいのでは

git に pre-commit hook という、コミット直前に何かのプログラムを実行する機能があります。これを用いて lint や test を実行したりすることがありますが、この利用は極力避けたほうが良いのではと考えています。

なぜ?

  • 個人の環境で実行した結果は信用できない
  • ローカルコンピューターで待たせてはいけない
  • pre-commit の挙動や中断などで動作が怪しくなることがある

個人の環境で実行した結果は信用できない

全員が完全に同じ環境で pre-commit hook を実行しているかは誰にもわかりません。言語やライブラリのバージョンに差がある可能性もあります。また、hook をスキップする方法もあります。

それだけを信用してチェックを行ったと判断するのは危険であるため、CI で実行することを優先すべきです。

ローカルコンピューターで待たせてはいけない

チェック項目が増えると、hook の実行時間も増えます。

あくまで hook で行う処理の大半はガードレールなので、ちゃんと書いていれば一発通過するはずで、そのために毎回各自が待たされるのは無駄です。

チェック内容に自信がなければコミット前に手動実行すると思いますので、それでチェックが通っても pre-commit hook は実行され、さらに時間をかけてしまいます。

チェックに時間がかかりすぎるようになると、チェックを迂回するための策を考えてしまう可能性があります。

チェックが通る前提でコードを書き、素早く push して開発を効率化しましょう。

pre-commit の挙動や中断などで動作が怪しくなることがある

ライブラリの更新などで pre-commit hook がアップデートされた場合などや、極端にリビジョンに差があって pre-commit の挙動に差があるブランチに切り替えた場合などに、pre-commit スクリプトとライブラリが一致していない等でおかしな挙動をすることがあります。他にも様々な理由で予期せぬ結果を起こすことがあります。

git commit に機能を追加してしまうことで、 git commit 時に git commit 以外のことを考える必要が出てきます。

そんなこと言っても変なコードが push されたらどうするの?

CI で防げばいいです。そのための CI です。

CI と同等の動作を「任意で」ローカルで実行できることは必要ですが、最終判断はあくまで CI にやらせましょう。

CI で必ずチェックすることで、変なコードがマージされるのを確実に防ぐことができます。また、常に同じ環境である CI に実行させることによって、環境ごとで結果に違いが出るといった現象も防ぐことができます。

その割には有名なライブラリたくさんあるし使っている人もいっぱいいるよ!

Ruby の pre-commit gem や nodejs の Husky + lint-staged などがあり、特に nodejs 界隈ではボイラープレートに組み込まれていたりします。

どれも有名かつ人気があるので使うべきかと思いますが、「出来がよく実用しても問題ないから人気である」と「必ず使う必要がある」は全く関係がありません。これまでの理由により、基本的には使う必要はないものです。

どうしても忘れっぽい人やチェックする習慣がない初学者は任意で有効化して利用する、というのはありかもしれませんが、強制するものではないと思います。

リモートに push されること自体が望ましくないものを防ぐ場合は使って良いと思います。例えば git-secrets のようなものです。一般的な lint エラーやテストの失敗といったものはリモートに push されても何の問題もありません。誰もマージできないだけです。

まとめ

よほどの理由がない限り、pre-commit hookは使わないほうがいいと思ったほうが良いでしょう。各自の判断で手動実行 + 必ず CI でチェック、でほとんどの場合は十分です。絶対に使うなというわけではないですが、なぜ pre-commit hook を使う必要があるのか、何のために使うのかをよく考えて利用しましょう。

参考

www.thoughtworks.com

2021年の仕事まとめ

Go による API サーバーと React による管理画面開発

Go で gRPC サーバーを作りつつ、 React と gqlgen で 管理画面を作るというような内容でした。

都合により短期でしか関われなかったのですが、あまりお目にかかれない環境での開発で非常に楽しい経験をさせて頂きました。

株式会社ハグカム 様

GLOBAL CROWN という子ども向け英会話サービスを提供しております。今年初めに資金調達も行っており 、絶賛成長中のサービスになっております。

私も少しだけお世話になっており、主にコアドメインからは少し距離を置いたものを中心に、以下のような内容を行っておりました。

  • GCE で動いていたインフラを Cloud Run に移行
  • インフラの Terraform 化
  • linter を整備してコード品質を上げる
  • CI / CD の整備
  • 負荷チューニング
  • Docker 開発環境の整備
  • 言語やフレームワークのアップグレード

サーバーサイドの言語としては PHPRuby を利用しており、久々に長期で PHP を触っていました。

また、コーポレートサイト を Next.js + Netlify で SSG 化したり、未公開の内容についての対応なども行っておりました。

今年は仕事以外のところでいろいろあったのですが、ハグカム様にはかなり融通を利かせて頂いて助かりました。

Rails エンジニアや Flutter エンジニアなどを積極的に採用中ですので、興味があれば以下から応募するか私までご連絡下さい。

www.hugcome.co.jp

www.green-japan.com

まとめ

今年はプライベートで良くないことが続く年でしたが、来年後半からはまた良い状況が生まれてくるはずなので、そこから活動を増やしていきたいと考えています。

引き続きお仕事を募集しております。詳細は https://nazo.dev/profile をご覧下さい。

ent ( entgo.io/ent ) で ULID を使う

ent では UUID 型がデフォルトでサポートされているのですが、それとは別に ULID を使う方法を解説します。

ULID の実装は https://github.com/oklog/ulid を使用します。

ent の UUID 型は、driver.Valuer Interface を利用して値を取得しますが、 oklog/ulid の Value() の実装がバイナリを返却するようになっているため、このままでは可読性が悪いです。 Value() が String を返すようにした ULID 型を新たに定義するのが良いでしょう。そのようにすると、他の interface も実装する必要があります。

結論から言うと https://github.com/ent/contrib/blob/6623819401500db45747a2419172963217cef619/entgql/internal/todopulid/ent/schema/pulid/pulid.go にそのものの実装があります1。この実装はライブラリ化されているものではない( internal )ので、そのままコピペして利用するのが良いでしょう。

使用する場合には以下のようになります。

// Fields of the User.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("id").
            GoType(pulid.ID("")).
            DefaultFunc(func() pulid.ID { return pulid.MustNew("US") }),
        field.Int("age").Positive(),
        field.String("name").Default("unknown"),
    }
}

prefixが不要な場合はprefixの部分を削ってしまうのが良いかと思います。

このままの実装でもう少し手軽に書きたい場合は、同リポジトリmixin が存在しますので、これを持ち出すと良いと思います。

ent は各フィールドを固有の型にできますので、例えば上記例で ID を UserID 型にしたい場合は以下のようになります。

type UserID pulid.ID

// Fields of the User.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("id").
            GoType(UserID("")).
            DefaultFunc(func() UserID { return UserID(pulid.MustNew("US")) }),
        field.Int("age").Positive(),
        field.String("name").Default("unknown"),
    }
}

  1. https://github.com/tmc/pulid のように見えるのですが、こちらは Value() はバイナリを返すようです。

Infrastructure as Codeは最初からやったほうがいい

最近不慣れな GCP を触ることがあったのですが、最初から Terraform で書くようにしたほうが楽だったので、それについて解説しようと思います。

Web のコンソールを触るのが怖い

他のサービスが同居しているアカウントでインフラを操作すると、他のサービスに影響を与えてしまうのでは…という不安があります。関係のないサービスは別アカウントにするというのがベストプラクティスですが、様々な事情によりそれができない場合もあります。

最初から IaC で触っていれば、コードで生成したインフラ以外を触ることがなく、削除する時も間違えることがなく削除することができ、何度も作り直して試すことが容易になります。

手順を覚えられない

Webのコンソールでも、コマンドラインツールでも同様ですが、作業した内容を記録しておく必要があります。記録していない場合、同じ環境を再現することができません。

最初からコード化をしていれば、手順を覚えておく必要もありません。

必要な依存関係を最初から教えてくれる

コンソールで触っていると、どのサービスとどのサービスが繋がっているかというのがわかりにくいですが、例えば Terraform では必須パラメーターとして存在していることが多く、依存関係を設定していないと適用する前にエラーになってくれたりするので迷うことが少なくなります。

また、サンプルで組み合わせた書き方が充実していたり、組み合わせサンプルのコードが用意されていたりするので、手順を読んで試行錯誤するより確実にインフラ構築をすることができます。

特に AWS で CDK や Copilot といったツールを使っておくと、複雑なサービス間の依存関係を考えることがなく構成を立ち上げることができます。

設定できるパラメーターがツールのドキュメントに網羅されている

依存関係と同様に、全てのパラメーターがドキュメントに書かれているので、「このリソースではこれが設定できる」とか「このリソースはこの設定をすることができない」というのがドキュメントを読むだけで把握することができます。

ただし GCP の Cloud Run のようなメタデータでパラメーターを指定するような類のものの場合や、AWS RDS のパラメーターグループのような任意のものを指定するようなものはドキュメントには書かれていないので、そこはオフィシャルのドキュメントやコンソールの画面を併せて読む必要があります。

まとめ

コンソールで試行錯誤するより、最初からコードで書いて試行錯誤したほうが良いことが多いです。手作業を後からコード化するのは二度手間にもなるので、学習目的とかでない限りは最初からコードを書きましょう。

もちろんある程度の IaC ツールに慣れておく必要や、クラウドインフラの使い方に慣れておく必要がありますので、そこは基礎教養として素振りしておきましょう。

Webエンジニアが新規でプロジェクトに入った時に確認すること

フリーランスとしてWebエンジニアをしておりますが、プロジェクトに新規に入った時にどういう点を確認しているのかをまとめておきたいと思います。

プロジェクトの目標と直近の目標が問題ないか

プロジェクトの最終目標(理想)はとにかく大きくあるべきで、一方で直近の目標は具体的かつ十分に実現可能なサイズになっているべきです。もちろんその間くらいの目標(いわゆるマイルストーン)も存在しているはずです。

フリーランスの立場だと目標そのものに直接口を出すことはないですが、直近の目標が大きすぎると手を動かす時に困ってしまいますので、どこを削ぎ落とすのが良いのかこちらから提案することはあります。

開発プロセスが問題ないか

プロジェクト参加直後はベロシティ(スプリント期間内の平均作業量)が不安定ですので、そこを許容できてちゃんと

個人的には常にスクラムである必要はないと思いますが、マネージャーがいる場合はマネージャーが各メンバーのベロシティをちゃんと把握している、不在の場合は自分でベロシティが把握できていてタスクの見積もりができる、という状況になっていれば良いかと思います。「何だかよくわからないタスクをよくわからない期間で対応している」という状況では、前述の直近の目標の提案と同時に対処方法の提案を行うこともあります。

デプロイが安定かつ高速に行えているか

デプロイ速度は修正速度に直接繋がりますし、安定したデプロイができなければデプロイする単位が大きくなりがちですので、デプロイの安定性は最重要になります。

当然デプロイは完全自動化されていて、誰でも気軽に(承認フローなどは別として)デプロイできることが前提です。

誰でも簡単にデプロイできない場合、誰かにデプロイをお願いするというようなことが発生します。これでは自分と誰かの作業と、それを連絡する作業が増えてしまい、当然作業効率が悪くなってしまいますので、「誰でも」は重要です。「コマンド一発でデプロイできる」と見えても「(コマンドを実行する前にアクセスキーを取得して手元でデプロイ準備をして…)コマンド一発でデプロイできる」という場合がよくありますので、デプロイ環境自体が CI/CD 環境に移されていて手元の環境に影響せずデプロイできる( CI サーバーが落ちていた場合は手元でも実行できる)というのが理想です。

デプロイ方法が複雑になっていないか

安定して高速にデプロイできていても、それを処理している方法が複雑だとメンテナンスの時に困ります。

メインのコードは綺麗にできてもインフラ系のスクリプトは長くなりがちみたいなケースはよく見かけますが、インフラ系のスクリプトもメインのコード同様に読みやすく構造化されているべきです。またインフラ系はツールが充実していることが多いので、なるべく自分で書くスクリプトは最小限にするのが良いと思います。

デプロイ自体が安定高速動作していればとりあえずは問題ないので、ここは後回しにすることも多いです。

CI でコード品質が十分に守られているか

CI の整備は後になればなるほど大変です。1これは CI で行うことの多くはコード品質そのものを一定の品質にする( lint など)もののため、コード量が増えた後からそれを適用しようとすると修正点が膨大になるためです。 lint はとにかくコード量が増える前に対処することが大事なので、ここは真っ先にチェックを行います。リリース前プロジェクトであればすぐ導入しますが、リリース後だと手を入れにくいので、最小限の設定にして少しずつ厳しくするという手法を採ります。

(自動)テストはそもそもテストが書けているかというのをまず確認しますが、その上で「テストに再現性があるか」「テストの内容が十分か」「Test Sizes の分類が十分にできているか」といった内容を確認します。

数回に1回落ちるようなテストがある場合で、それがテストの実行順に影響している場合はテストの書き方そのものに問題がある場合がほとんどですので、その場合は削除してしまったほうが良いことが多いです。一方で、時間に関わるテストや、乱数や数値計算の誤差などが影響するようなテストは、それ自体が重要なケースが多いのでちゃんと調査したほうがいいと思います。

Test Sizes は、テストの分類を「単体テスト」「結合テスト」といった曖昧な呼び方ではなく、「我々のプロジェクトでは3段階の分類があり、それぞれのレベルではこうなっている」というものをプロジェクト固有で定義しようというものです。

ローカルでの環境構築が問題なく行えるか

自分が受け入れられる側になるのですが、この時に受け入れ準備が十分にできているか確認し、できていない場合は改善しながら作業することになります。

一番ありがちなのは環境構築手順が整備されていないということで、これは自分がそのまま作業するのですぐわかります。

ローカルの開発に外部のサーバーが必要な場合はまずそれがローカルで再現できないのか確認します。単なる MySQLPostgreSQL などをサーバーに繋いでいる場合はローカルで立ち上げるように変更します。エミュレーターが用意されているサービスであればそちらを使うようにします。この際に設定が直接書かれていたり ENV などでの切り替えしか対応していない場合は、設定を環境変数に出して変更可能にします。2 最近だと自分は M1 (Apple Silicon) Mac を使用しているので、この環境で動作しないものを使っているという場面に遭遇することもあります。この場合は Docker 化を真っ先に行います。

手順はあるけど手順通りにやっても上手く行かないという場合、「使っているライブラリのアップデートで動かなくなった」というケースと「プログラムの構造の変化に対応していない」というケースが多いです。どちらにしても変更を把握できていないということが原因ですので、メンテナンス体制に難があることが多いです。外部ライブラリの更新などは把握しておく、自分で修正した場合は環境構築手順もアップデートするフローにしておく、ということが必要になります。

受け入れタスクが十分に実施可能な内容になっているか

ここでの障害は「ライブラリやツールの使い方が独自性の強いものになっていないか」「暗黙の知識が多用されていないか」といったものがあります。もちろん前述までの内容がクリアになっていないと作業に入ることもできませんので、それらを取り除いた後にようやく作業ができるということになります。

「ライブラリやツールの使い方が独自性の強いものになっていないか」というのは、自作ライブラリとかを使っている場合は当然ですが、ベストプラクティスに即していないようなライブラリの使い方をしているとか、魔改造されているというような場合には、なるべく本来の使い方に戻すよう指摘することがあります。「ライブラリやツールの公式のチュートリアルを見た上でそれを使っている部分を読んだ時にすぐ理解できるようになっているか」というのが指標になります。

「暗黙の知識が多用されていないか」はそのままですが、プロジェクト固有の暗黙の知識がある場合は当然手を入れることができません。暗黙の知識が登場する場合はコメントなどで把握できるようになっていること、仕様と実態の乖離がないことなどを確認します。

まとめ

これらの点がクリアされているプロジェクトであれば、新しく人を入れることも容易ですし、引き継ぎの時にも困らないのではないかと思います。

書き漏れがあるかもしれないので、思い出したら追記する可能性があります。

今回はインフラの話は除外しましたが、インフラでチェックする点もまた機会があればまとめてみたいと思います。

これらの点でお悩みの方は是非私までご相談をお願いします。詳細は https://nazo.dev/profile をご覧下さい。