初めての大きなプロジェクトに挑戦してみた

みなさんこんにちは。23新卒の DJ と申します。

私はアドプラットフォームの事業部でアプリケーションエンジニアをしています。

最近はスマートホーム化に興味があり、つい先日、カーテンを自動で開け閉めできるボットを導入しました。
起床時刻になるとカーテンが自動で開くように設定しており、朝は自然光で気持ちよく目覚めることができます。
毎朝スヌーズと1時間格闘していたあの日々とはおさらばです。

本記事では、私が新卒として研修を終え、配属されてから1,2ヶ月後に、大きなプロジェクトに取り組んだ様子について話します。
アドウェイズに入ったら具体的にどんな仕事をするんだろう?と疑問に思っている学生の方に向けて、新卒エンジニアの働き方のイメージを掴んでもらえたら嬉しいです。

背景

まず、プロジェクトにアサインされるまでの背景です。
私のチームでは、タスクはバックログと呼ばれる、やることリストで管理されており、バックログにあるタスクから好きなものを選んで取り組むことができます。
今回のプロジェクトもバックログの中にあったタスクで、面白そうだなと思い選んだところから始まりました。
開発は私一人で行い、困った時はチームメンバーにサポートしてもらう形で進めました。

概要

次に、プロジェクトの概要や構成について説明します。

プロジェクトの概要

私のチームでは、自社サービスの様々な情報がまとまっている社内管理画面を運用しています。
また、営業活動を効率的に推進するために、社内管理画面以外に情報をまとめる手段として Google Spread Sheets が利用されています。
しかし、社内管理画面以外にまとめることにより、下記のような問題が発生していました。

  • 直接管理画面から見られないので情報が活用されない
  • アクセスするためには Google Drive で検索、もしくはブックマークなどから直接遷移する必要がある

これらの問題を解消するために事業企画の方から、「社内管理画面と Google Drive の紐づけができないか?」という相談がありました。
社内管理画面と Google Drive の紐付けを行うことにより、これまでに比べ Google Spread Sheets へのアクセスのしやすさが格段に向上します。
それにより、情報の共有がしやすくなり、より効率的な営業活動を行うことができるようになると期待できます。

構成

Google Drive のファイルの URL を取得するための API は、AWS Lambda (以下 Lambda) を用いて実装します。
Lambdaとは、自分でサーバーを用意・管理することなくプログラムを実行することができるサービスです。
また、Google Drive から取得したファイル情報を保存しておくため、Amazon DynamoDB (以下 DynamoDB) という AWS の NoSQL データベースを利用します。
これらの AWS リソースを AWS CDK (以下 CDK) により管理します。CDK とは、クラウドリソースをコードを用いて管理する IaC (Infrastructure as Code) を実現する手段の一つで、Lambda や DynamoDB などの AWS リソースを TypeScript や Python を用いて定義することができます。

実際の処理の流れは、以下のシーケンス図のようになります。

開発

開発は以下の流れで行っていきました。

  • API 設計
  • 実装
  • レビュー
  • リリース

CDK による基盤が用意されていたため、 AWS リソース周りにあまり労力をかける必要はありませんでした。
また、事業企画の方と要件やスケジュールを調整する必要がありましたが、これはチームに所属しているマネージャーにしていただきました。
そのため、私は開発に集中することができました。

以下では実際に行った開発について説明します。

API 設計

最初は API 設計です。
まず、1ファイルの URL を取得する API の設計を行いました。
API 設計は初めてだったため、他のサービスのAPIを参考に草案を作成し、チームメンバーと相談しながら仕様を固めていくという流れで行いました。
完成した設計をもとに実装を進めていたところ、途中で、一度に複数ファイルの URL を取得するAPIが必要であることに気がつきました。
複数ファイルの URL を取得する API の設計は、1ファイルの URL を取得する API を拡張した形になると、綺麗でわかりやすいです。
しかし、1ファイルの URL を取得する API は拡張を想定していなかったため、設計の修正が必要になりました。
これにより、すでに進めていた実装を書き直すことになってしまいました。
手戻りが発生して効率が悪くなってしまったものの、試行錯誤しながら何とかAPIを設計することができました。

実装

次に、実装です。
主に Lambda のコードを TypeScript を用いて実装しました。
TypeScript は研修で触って以来、2回目でした。
最初は型の定義が難しく、型エラーで引っかかっていました。
しかし、型をつけることで関数や変数の仕様が明確になること、間違った値を入れてしまった時にすぐに気づけることが分かり、型って便利だなと思うようになりました。

実装において一番印象に残っているのはテストコードの作成です。
あらかた機能の実装を終えて満足していた時、チームメンバーに「テストコードは書かないの?」と言われ、そこで初めてテストコードが必要なことに気づきました。
テストについて何も知らなかったので、まずはテストに関する記事を読むところから始めました。
この時読んだのは t_wada さんの以下の記事です。

gihyo.jp

テストにはユニットテストや結合テストなど種類があること、バグを見つける以外にもコードの仕様を明確にするという利点があることを知りました。
得た知識をもとに、実際にテストコードを書いてみると、丁寧に書いたはずのコードにバグがたくさん見つかり、とても驚きました。
その一方でテストコードを書くことにより、少しずつ自分のコードが堅牢になっていくのを感じました。
慣れていなかったため、テストコードを書くのにかなり時間がかかりましたが、時間をかけただけの価値はあると感じました。

レビュー

実装を終え動作確認を行った後、チームメンバーにレビューをしていただきました。 レビューでは、かなりたくさんの指摘をいただきました。 実際にいただいたレビューをいくつか紹介します。

await, then, catch の使い方

【コード】

const getData = await docClient.send(getCommand)
.then((data) => {
  return data.Item;
})
.catch((error) => {
  throw new Error(error);
});

【コメント】

async/awaitを使うなら then/catchではなくtry/catchでもいいですね。
そもそもこの場合catchでエラー吐き直しているだけなので、

const data = await docClient.send(getCommand)
return data.Item;

だけでいい気がします。 (catch使用箇所だいたい同じに見える)

await, then, catch についてあまり理解していなかっため、すべて必要だと思い、書いていました。
冗長な書き方になっていたため、then、catch は削除しました。

ファイル構成

【ファイル構成】

┗ src
   ┣ app.ts
   ┣ model
   ┃  ┗ index.ts
   ┗ useCase
      ┣ dynamoDBactions.ts
      ┣ fetchDriveFiles.ts
      ┣ getSSMParams.ts
      ┗ index.ts

【コメント】

ファイルの構成について

useCase以下には、APIが呼び出された時にユースケースとして実行される関数のみを基本的には置いてください。
元々初期のサンプルではユースケース1つだったのでindex.tsにしていましたが、今は3つあるので好みで分けてもいいです。
また、分けた上でインポートを容易にするために index.ts を用意して一括インポートできるようにすることもできます。
https://qiita.com/stin_dev/items/8bc6281dcebb289887be

例えば src/useCase/dynamoDBactions.ts は「AWS DynamoDBに関する関数」が置かれているものであるため、src/aws/dynamodb.tsなどに配置すればいいかと思います。

┗ src
   ┣ model
   ┣ useCase
   ┃  ┣ findSheetInfo.ts
   ┃  ┗ batchFindSheetInfo.ts
   ┣ aws
   ┃  ┣ dynamodb.ts
   ┃  ┗ ssm.ts
   ┗ google
      ┗ drive.ts

※コメントに出てきた記事はこちらです。

qiita.com

ファイル構成に疎かったため、追加したすべてのファイルを useCase に置いてしまっていました。
何を基準にファイルを分ければ良いのか、どのようなディレクトリ構成にすれば良いのか教えていただきとても勉強になりました。

最終的に31箇所ものレビューを頂きましたが、すべて修正し、なんとか LGTM(Looks Good To Me) をもらうことができました。(レビューしていただき本当にありがとうございました。)
1PR に全ての変更を含めていたため、かなり規模が大きくなってしまいました。
規模が大きいとレビューが大変なので useCase ごとに PR を分けた方が良かったなと今では思います。

リリース

レビューを終えたら、いよいよリリースです。
今回 CD 等は用意していないため、手動でリリースを行いました。
CDK で管理していたため、リリースはコマンドを一つ叩くだけです。
しかし、リリースをする瞬間は今でも緊張します。
コマンドは間違ってないか、慎重に確認してリリース作業を行いました。
無事に動作が確認できたとき、とても嬉しかったです。

リリース後には、事業企画の方に「こんな機能ができました!」と全体チャンネルで宣伝していただきました。
気恥ずかしくもあり、また嬉しくもありました。

全体を通して

これまで、実際に行なった開発について振り返ってきました。
ここでは全体を通して、苦労したことや学んだことを紹介します。

知識がない状態だった

開発を始めた当初、私は色々な知識が足りませんでした。
例えば、Lambda、DynamoDB、CDK、Google Drive API、TypeScript、API 設計、ユニットテストなどです。
そのため、何をするにも調べながら進めていく必要があり、とても時間がかかりました。

この時、なるべくメモを取るように心がけていました。
メモを取ることで、頭の中の情報が整理され、思考がスムーズになりました。
特に、使ったコマンドはメモに残すよう意識していました。
同じコマンドを使いたくなったとき、あれなんだっけ?となる時間が削減でき、効率よく作業を進めることができました。

迷ったら相談する

私は今回、迷った時にあまり相談せず、なるべく自分で調べて解決するようにしていました。
それは、相談するとメンバーの時間を使ってしまうのが申し訳ないという気持ちや、なるべく自分の力で頑張りたいという思いからでした。
確かにこの方法は自分の力にはなりますが、時間がかなりかかってしまいます。
実際に、今回の開発では想定よりも、1,2週間ほど開発期間が延びてしまいました。
このプロジェクト以外にも他にタスクはたくさんあり、時間をかけてしまうと他のタスクが滞ってしまいます。
そして結果的にメンバーに迷惑をかけることになってしまいます。
そのため、迷った時は一人で悩みすぎず、早めに相談した方が良いということを学びました。

技術的な知識が身につけられた

開発を通して、様々な技術を学ぶことができました。
Lambda や DynamoDB に実際に触れたことで、リソースの構築の仕方や、ユースケースなどがわかるようになりました。
また、TypeScript をずっと書いてきたことで、コードの読み書きの速度が早くなり、TypeScript の案件にスムーズに対応できるようになりました。
さらに、テストについて知ったことで、新しいコードを読むときはまずテストから読んで仕様を理解するという習慣がついたり、テストがすべてのケースをカバーしているか意識して考えたりするようになりました。

今後の課題

開発を進め、知識がついてきた今、今後の課題や取り組みたいことが見えてきました。

  • ログ出力
  • テストコードの整備
  • 監視
  • CD など

ログ出力をしていなかったり、今見るとテストコードが不十分だったり、監視ができていなかったりと、まだまだ改善できる点がたくさんあります。
開発したものは責任を持って運用・保守していく必要があるので、これらの改善に取り組んでいく予定です。

まとめ

今回は、私が新卒として研修を終え、配属されてから1,2ヶ月後に、大きなプロジェクトに取り組んだ様子について振り返ってみました。
初めての開発で、わからないことだらけのスタートでしたが、なんとか達成することができ、大きな成長を感じています。
得た知識や経験を活かし、今後も開発・改善に取り組んでいきます。

本記事を最後までお読みいただき、ありがとうございました。