はじめに
2024年の開発内容を振り返りをしてみようと思います。筆者の簡単な自己紹介はこちら
- 27歳、都内在住
- エンジニア歴3年半
- 3月まで正社員で勤めていた会社を対象しフリーランスになりました
- 現在は2案件掛け持ちしており、週7で稼働しています
2024年の開発振り返り
全部振り返るとキリがないので印象的だったものだけピックアップしていきます。正社員時代を合わせると2024年は合計3社で開発していたので会社ごとに振り返ります。
P社
2024年開始時は正社員としてスタートアップで勤務しており、管理画面のリプレイスが進んでいました。リーダー的なポジションを任せていただき、設計、開発からマネジメントまで幅広く担当していました。
フルスクラッチ→ローコードツールへ置換
元々管理画面をフルスクラッチで開発していたのですがベースマキナというローコードツールにリプレイスしました。リプレイスの決断を下したのは経営陣なのですが、
- 操作ログが取れること
- 各操作に対して権限を指定できること
などの要件が上場にあたって必要らしく、大きな決め手となったようです。
詳しいことはベースマキナのサイトを見ていただければと思います。
このリプレイスプロジェクトなのですが、端的にいうと
- ベースマキナ自体は素晴らしいものである
- ただし私がベースマキナの利点を活かしきれなかった
という結果に終わりました。
よかった点
- ベースマキナ自体にデフォルトでいい感じのviewコンポーネントが用意されており、JSONを流し込むだけでいい感じのUIを構築してくれる
- ちょっとしたカスタマイズであれば生のJavascriptを書くことでカスタマイズできる
反省点
社内向け管理画面のため社内メンバーの希望をできるだけ聞き入れていたのですが、全てに答えようとした結果、そこまで開発工数を削減できませんでした(むしろ魔改造する必要がありフルスクラッチよりも時間がかかってしまった)。ローコードはあくまでローコードであり、複雑な要望には応えられないので、エンジニア側でベースマキナに合わせた提案すべきだったなあと反省しております。
フロントエンドの通信形式を GraphQL に統一
こちらも管理画面の話です。元々の管理画面ではフロントエンドとバックエンドの通信が Firestore, grpc-web, REST API と複数の形式でやりとりされており、カオスな状態になっていました。そこで管理画面とバックエンドの間にBFFを立て、 GraphQLで統一するようにしました。
よかった点
BFFサーバーにはGo言語、gqlgen を採用したのですがこちらは大正解でした。採用の理由としては
- すでに他のプロジェクトで採用された事例があったこと
- 強力なコード生成
- メンバーのGo言語モチベが高かったこと
があったのですが、開発体験が圧倒的にいい。GraphQLサーバーの開発はNestjsやgql-codegen, appolo など色々試してきたのですが、今までで一番開発体験が良かったです。またスキーマ駆動開発が強制されるため、開発順序の秩序が保たれるのも推しポイントでした。
反省点
そもそも本当にGraphQLを採用すべきだったのか?は要検討かなと思いました。というのもベースマキナはBFFサーバーを立てずとも複数のデータソースと直接連携することができるんですね。
このおかげでサーバーの実装工数を減らせることもベースマキナの推しポイントではあったのですが、利点を殺してしまったような気もします。GETはデータソースを直接参照し、POSTで複雑なロジックが必要になる場合のみREST APIをはやしてあげればベースマキナの利点も活かしつつ、実装工数の削減もできるのかなと思いました。
S社
セールステックのSaaS開発に携わっています。システムの機能要件をざっくり述べると
- 営業さんって Slack/Chatwork とかメールとか色々コミュニケーション手段があるよね
- それらをシステムで一元管理したいよね
- 営業先の情報も管理したいよね
っていうのを実現するためのSaaSを開発しています。
Slack/Chatwork連携
このシステムの肝であるチャット関連の機能を実装を担当しました。SlackやChatworkと連携し、メッセージやチャンネル情報を取得し、システム内で活用できるようにしました。連携の基本的な流れは以下の通り。
- FE側でSlackの認証画面(OAuth URL)に遷移
- 連携対象のSlackワークスペースを選択
- 連携対象のチャンネルやDMを収集
選択したチャンネルやDMのメッセージは定期バッチで収集され、FE側で閲覧できる仕組み。Chatworkについても同様のフローで実装しています。
工夫した点
-
レートリミットへの対応: Slack APIはリクエスト回数に厳しい制限があるため、リクエスト間に適切な
sleep処理
を導入。これにより、API制限に引っかからないように調整 - TypeScriptでのAPIレスポンス管理: Slack APIライブラリのレスポンス型定義が不完全だったため、カスタムの型定義を作成。安全なレスポンス処理ができるように、予期しないデータ構造を根絶
- トークン管理: ユーザーごとに発行されるOAuthトークンを安全に管理し、APIリクエストに使用。これにより、ユーザーごとの認証情報を活用した処理が可能になった
反省点
-
データベース設計の問題: Slack APIの仕様に合わせて設計した結果、DBの構造が複雑化した。特に以下の点で課題がある
-
team
やchannel
情報を取得してDBに保存する際、Slack APIの仕様に依存したテーブル設計を採用してしまった - 例えば、Slackメッセージの場合、
ts
(タイムスタンプ)とchannel_id
を複合主キー(Composite Primary Key)として採用したが、この設計が煩雑さを招いている - PK(主キー)をUUIDで発行していれば、データベース設計がシンプルになり、処理の可読性や保守性が向上したはず。今後の改善点として検討すべき
-
-
取得処理の速度
- 連携対象のチャンネル・DMを取得する処理に時間がかかる点が課題。Slack APIのレスポンス速度やデータ量に依存するため、非同期処理の最適化やデータ取得方法の改善が必要
メール収集ジョブ
Lambda関数とAWS SESを使用して、メールの収集・処理システムを構築しました。SESで受信したメールをS3バケットに保存し、その保存イベントをトリガーとしてLambda関数が起動するようにしています。Lambda関数内では、メールの内容を解析し、必要な情報をデータベースに保存するフローを実現しました。
工夫した点
-
非同期処理を活用した効率化
メール受信からDB保存までのフローを非同期処理で実装。SESが受信したメールを即座にS3に保存し、そのイベントをトリガーとしてLambda関数が動作するため、処理が効率的 -
メール内容の解析ロジック
Lambda関数内でメールのヘッダーや本文、添付ファイルを正確に解析するロジックを実装。特に添付ファイルについては、ファイル形式をチェックし、必要に応じてDBにメタデータを保存する仕組みを構築した -
コスト最適化
AWS Lambdaの使用時間を最小化するように、メール内容解析を必要最低限のリソースで完了させるよう工夫。また、S3のストレージクラスを適切に設定し、コストを削減
反省点
-
エラー処理の改善
Lambda関数内で発生するエラーに対するハンドリングが不十分なケースがあった。特に、メール解析中に発生するフォーマット不一致エラーやS3へのアクセスエラーなどに対するロジックが不足している -
DB保存処理のトランザクション不足
メール内容や添付ファイルをDBに保存する際、処理が部分的に失敗するとデータの不整合が発生する可能性がある。トランザクション管理を導入して、処理全体を一貫して成功させる仕組みを取り入れるべき -
スケール時の課題
メール受信の頻度が増加した際に、Lambdaの同時実行制限やDBの負荷が問題になる可能性がある。現状では大量のメールを処理するスケーラビリティに関して十分な検証が行われていない
Cognito を用いた認証、認可の実装
Cognito を用いてアプリケーションに認証・認可機能を導入。JWT トークンを使用し、ユーザーが正しい資格情報を持っているかを確認する仕組みを構築しました。
工夫した点
- NestJS の「モジュール」という単位を使って認証機能を独立させ、アプリケーションの他の部分と簡単に統合できる設計にした
- トークンの検証ロジックは
AuthService
に集約し、複数箇所で再利用可能にした - Cognito が提供する鍵情報(JWKS)をキャッシュする仕組みを実装。ネットワーク遅延を回避し、トークン検証の効率を向上させた
- 環境ごとに異なる設定(例えば開発と本番のトークン検証設定)を簡単に切り替えられるように設計した
反省点
- Cognito の JWT トークンの構造やルールを正しく理解するのに時間がかかった。特に
kid
を使った鍵の選択ロジックや JWKS の取得方法が複雑 - 開発、ステージング、本番環境でそれぞれ異なる設定を切り替える設計に手間がかかった
- 新しい認証機能を追加することで、既存のコードが動かなくなる問題が発生。例えば、サービスやデコレーターに修正を加える必要があった
手動作成したインフラのterraform化
手動で作成されていたインフラをTerraformを使用してコード化しました。既存のリソースをTerraformにインポートし、管理可能な状態に変換しました。これにより、インフラの再現性を確保し、今後のメンテナンスや変更の容易化を図りました。
工夫した点
-
既存リソースのインポート:
terraform import
を活用し、手動で作成されたリソースをTerraformの管理下に置くための手順を詳細に設計 - モジュール化: 各リソースを論理的なグループごとにモジュール化し、再利用性を向上
- バージョン管理: TerraformのstateファイルをS3に保存し、DynamoDBでロックを管理する構成を導入。これにより複数人での作業でも競合を防止
- *タグ付けの標準化
反省点
- 初期計画の不備: 手動作成されたリソースの全容を正確に把握せずに作業を開始したため、再作業が発生した
- ドキュメントの不足: インポート手順やコード化のルールを詳細に記録していなかったため、他のメンバーが引き継ぎづらい状態に
testcontainer の導入
テスト環境での実行速度と信頼性を向上させるために、Testcontainersを導入しました。これにより、実際のコンテナ化された環境でユニットテストや統合テストを実行できるようになり、本番環境に近い形でのテストが可能になりました。テスト用のコンテナを事前に起動する必要もなく、テストを実行するたびにdockerコンテナが起動→DB起動→テスト終了時のクリーンアップまで行うようにすることで開発体験を向上させました。
工夫した点
- 動的ポート割り当て: コンテナのポートを動的に割り当てる設定を採用し、テストの並列実行が可能に
- セットアップの簡略化: Testcontainersのライブラリを使用してDockerコンテナの起動から終了までを自動化し、手動でのセットアップ作業を排除
- 本番環境に近い設定: 本番と同じDBなをテストコンテナ内で利用するように設定
- 再利用可能なテストユーティリティ: 共通のテストセットアップを抽象化し、複数のテストケースで再利用できるように設計
反省点
- CI実行時間の増加: テストケースごとにコンテナの起動を行うためテスト実行にどうしても時間がかかってしまう。いい感じにキャッシュとか効かせたい
- リソースの競合: 複数のテストを並行実行した際に、リソース競合やポートの競合が発生する問題を解決するのに時間を要した
- テストのスピード: コンテナの起動と終了にかかる時間が長く、一部のテストで実行速度が低下
- 依存関係の管理: Testcontainersが必要とするDockerやJava環境のバージョン互換性に問題があり、環境設定に手間取った
GitHub Actionsを利用してCI/CDパイプラインを作成
NestJSで構築したアプリケーションをGitHub Actionsを利用してCI/CDパイプラインを作成し、AWS App Runnerに自動デプロイする仕組みを実装しました。
工夫した点
- 手動トークン管理の簡略化: OIDCが設定できなかった代わりに、AWS CLI用の短期認証トークンをSecrets Managerで安全に管理し、自動更新を行うプロセスを追加
- セキュリティの強化: GitHub ActionsのSecretsを適切に設定し、AWS認証情報のリークを防止
- App Runnerの自動スケール設定: アプリケーションの負荷に応じて自動スケールを行うApp Runnerの設定を活用し、効率的なリソース管理を実現
- YAMLのパイプラインテンプレート化: GitHub Actionsのワークフローを再利用可能なテンプレートとして管理し、他のプロジェクトにも適用可能な構成に
反省点
- OIDCの実現への優先度不足: OIDCを使用することで、長期的に手動トークン管理の手間が減りセキュリティが向上するため、再度チャレンジするべき
- ドキュメントの不足: OIDC導入に向けた手順や障害に関する詳細な記録を残さなかったため、再トライ時に手間がかかる可能性が高い
- デプロイフローの改善余地: OIDCが使用できればトークン管理をさらに簡略化できるため、現在のパイプラインを見直し、OIDCへの切り替えを計画すべき
U社
2024年6月から参画。6月~11月まではワークフローシステムの開発、11月から現在は新規の法人カードのシステムの開発に携わっている。言語的には Go/Typescript/Kotlinを行ったり来たりしている。
Typescriptのコードベース改善
BFFサーバーにTypescriptを採用しており、チーム内でサーバーサイドTypescriptに知見のあるメンバーが少なかったため改善提案を行ないました。特に評価いただいた改善提案は下記の通り。
Snapshotテストの導入
- テスト実装工数が大幅に削減できた
vitest を watch モードで起動できるようにした
- 開発体験の向上
then/catch→async/awaitに統一
- ネストするとthen/catchだと可読性が下がるので
自前で作成・定義していたモックを createRouterTransport
を使用するようにした
外部会計システムとの連携
ワークフローで稟議に通った経費申請の仕訳を外部の会計システムにAPI経由で連携できる仕組みを構築しました。Goを使用してマイクロサービスアーキテクチャを採用し、データの管理にはCloud Spannerを、非同期処理にはPub/Subを用いて開発を進めました。
工夫した点
- マイクロサービスを適切に分割し、各サービス間でトランザクションが確実に担保されるよう設計した
- ユーザーが仕訳連携を実行した際、非同期処理としてPub/Subを使用してデータを送信。これにより、リクエストのレスポンス速度を向上させた
- 処理が失敗した場合でもPub/Subのack/nack機能を活用してリトライ可能な設計にした
- 非同期処理の実行ステータスを管理するために
async_processes
テーブルを作成。ステータスを可視化し、ログを追いやすくすることでトラブルシューティングを効率化した - Cloud Spannerを初めて扱ったが、Googleのベストプラクティスに基づきスキーマ設計を行った。特に、外部会計システムのAPIレスポンスに依存しない設計とし、IDにはUUIDを採用して分散性能を向上させた
- 外部APIがOpenAPI仕様に基づいて提供されていたため、oapi-codegenを使用してGoのコードを自動生成。手作業を減らし、実装ミスを防いだ
- Spannerのストリーミング機能を利用し、CDC(Change Data Capture)の仕組みを実現。これにより、データ変更がリアルタイムで反映され、最新の状態を保持しやすくした
- 複数のマイクロサービスが関与するシステムであったため、全サービスをローカル環境で起動するのではなく、開発時にはテストカバレッジを100%担保。結合テストはSTG環境で実施し、効率的に開発を進めた
反省点
- マイクロサービスの分割の仕方、命名はもう少し改善の余地があったかもしれない
- 複数のサービスが絡み合う作りなのでトランザクションを厳密に管理することが難しく、データ不整合が起きてしまうことがあった
マイナンバーカードを用いた本人確認の実装
マイナンバーカードを用いた本人確認を行うための機能を実装
Go 言語を使用し、gRPC を介したリクエストに応じて、ドキュメントの取得、本人確認結果の取得、本人確認の実行を行う処理を構築した
工夫した点
- 各リクエストに対して明確なバリデーションロジックを実装し、不正なデータが処理に進まないようにした
- 外部検証サービス(Verify API)のレスポンスがクセが強く、データの扱いに苦戦したが、必要な項目を適切にマッピングして対応した
- Cloud Spanner のトランザクション処理を使用し、本人確認のデータを一貫して保存する設計にした
反省点
- 外部サービスのエラーハンドリングが単純で、詳細なログやリトライ戦略が不足している
- バリデーションロジックが手続き的で、新しい要件への拡張性に課題がある
決済金額に応じてポイントを付与する仕組みを実装
GoとKotlinを用いて、決済金額に応じたポイントを付与する仕組みを実装しました。gRPC を通じてポイント付与リクエストを受け取り、データベースに記録し、ポイント残高を更新する一連の処理を構築しました。また、ポイント履歴の検索や残高取得の機能も実装し、Kotlinでは非同期処理を活用したバッチ処理を初めて取り入れました。
工夫した点
- Goではリクエストのバリデーションを細かく行い、不正なデータが処理に進まないようにした
- 非同期処理を取り入れ、ポイント付与のバッチ処理を効率化
- Kotlinでの開発では、デビュー戦ながらもコルーチンを活用し、並列処理を活かしてポイント付与処理のスループットを向上
- エラーハンドリングを明確化し、ポイント付与に失敗したケースをログやメッセージで追いやすくした
- Goではトランザクション管理を取り入れ、データの整合性を保証
- Kotlin側では、失敗時にSlack通知を送信する仕組みを実装し、運用時の障害対応をサポート
反省点
- Kotlinでの開発に不慣れなため、設計段階でリファクタリングを要する箇所が多かった
- Goではテストカバレッジが不足しており、特にバリデーションエラーや境界値のケースを網羅できていない
- 処理全体のパフォーマンスに関する測定が不十分であり、大量データの処理時のボトルネックが未検証
k8s/ArgoCD/GitOpsを用いたインフラ環境構築
複数のマイクロサービスが頻繁に作成される環境で、Kubernetes、ArgoCD、GitOpsを活用したインフラ環境の構築手順を整理。PRがマスターにマージされると、GCRにイメージがプッシュされ、デプロイ用リポジトリにPRが作成されるフローを整備した。また、ArgoCDを活用して、ステージング環境への自動反映を実現。新規サービスのデプロイ手順が複雑でドキュメント化されていなかったため、ドキュメントとしてまとめた。
工夫した点
(というかこの辺はすでにフローが出来上がっていたんだけれども)
- KubernetesのSecretを管理する際にSealed Secretを利用し、セキュリティを向上させつつデプロイの自動化を実現
- 複雑な設定手順を簡略化するため、リポジトリ命名規則やGitOpsのフローを明文化
- 過去のPRや参考リンクをドキュメント内に記載し、新しいチームメンバーでも手順を追いやすくした
- GitHub ActionsとArgoCDの連携フローを再構築し、PRマージからステージング反映までの一連の流れを自動化
反省点
- そもそもこの辺苦手すぎて完全に理解してない()
- 何が課題で何を反省すべきかいまいち把握できていないですが、結構複雑な気がするのでもう少し簡略化できないかなぁとは思う
2025の目標
思い返せば2024年も色々やったなぁという印象です。そんな中で課題感も見えてきたので、来年の目標を掲げてみます。
技術の本質を理解する
冒頭にも書いた通り私は週7で働いています。別に働きすぎとも思っていないし長時間働き続けることはそこまで苦ではないのですが、「日々の業務に忙殺されてインプットする機会が激減してるなぁ」と感じます。
2024年は今まで経験したことのない分野の開発も多かったことに加え、複数プロジェクトを掛け持ちしていたので脳にかかる負荷も高かったです。なぜそんなハードな状況を乗り切れたかというと、生成AIのおかげです。
ここ数年でエンジニアの開発手法は大きく変わりました。ChatGPTに「こんなこと実現したいからサンプルコード書いてや」と指示すればあっという間にそれっぽいコードを書いてくれる。そのコードをもとにちょっと修正すれば動く。便利な時代になったものです。
それ自体は問題ではないと思うのですが、「なんかわからんけど動いてるからヨシ!!」という状態になりつつあることに危機感を感じています。時間がある時は生成されたコードに対して「なんで動くんだろう?」と理解した上で扱っていたのですが、時間がないことを言い訳に1つ1つの技術への理解を疎かにしてしまっている感が否めません。
2025年は1つ1つの技術を深く理解する時間を確保したいなあと思っています。
フリーランス→正社員に戻る準備
どこかのタイミングで正社員に戻ろうと思っています。金額面や働き方の柔軟性を考慮するとフリーランスに軍配が上がるのですが、未経験の分野へのチャレンジやリーダーの経験、30歳を目前に色々ライフイベントを控えてることを考えると正社員の方が何かと都合いいかなと。正社員に戻るとしたら下記のような会社に行きたいと思っています。
1. インフラエンジニアの経験ができること
バックエンドエンジニアとしてはかなり成長した実感があります。一方でインフラ周りはまだまだ伸び代かなという印象です。業務でも積極的に「インフラタスクやりたいです!」と手を挙げたり、休日に個人でキャッチアップしたりしてるのですが、どうしてもバックエンドの片手間でやるインフラタスクだとそこまで大きなことはできないし、個人で構築するインフラにも限界がある。そこで一度どこかでインフラエンジニアとかSRE的なポジションを経験してみるのはアリかなと考えています。
2. テックリード的な経験が詰めること
テックリードの役割はチームや会社の文化によって異なると思いますが、技術的な意思決定やチームの技術的な方向性を導く経験を積みたいと考えています。これまでは技術的な課題解決に取り組むことが多かったですが、チーム全体を引っ張る立場としての視点をさらに強化したいです。コードレビューの品質向上、技術選定、後輩エンジニアのメンタリングなどに積極的に関わることで、より責任感のある役割を果たしていきたいです。
3. 英語環境であること
これは「あったらいいな」くらいなのですが、英語環境で働ける機会があると嬉しいです。「英語が話せる」ことは今後のキャリアの選択肢も増えてくるし、何より「英語が話せるとカッケェ」からです。学生時代に得意だった英語も、最近は使用機会が減っているため、強制的に使う機会を設けたいなと思ってます。
で、準備って何すんねん、って感じですが
- 今まで経験した開発を言語化できること
- 経験した技術を人に教えられるくらい理解すること(1つ目とも被りますが)
- コーディング試験対策
- 英語の勉強
こんな感じで進めていこかなと考えています。
まとめ
この記事は、誰かのためというよりは、自分自身の振り返りとして書いたものです。「2024年はこんなことをやって、こんな目標を持っていたなあ」と、数年後の自分が振り返るための記録です。
未来の自分がこの記事を見返して、「この時の努力や考え方が今の自分を支えているな」と思えるように、これからも一歩ずつ前に進んでいきたいと思います。