新卒1年目がDX化を要件定義からやった話

初めまして!エージェンシー事業部でアプリケーションエンジニアをしています23新卒のいしいです。

4月を迎えてはや2ヶ月、24新卒さんとの交流が始まりフレッシュな風が流れています。あっという間の一年間。もうフレッシュな新卒ではなくなったと感慨に浸っている今日この頃です。

この記事ではそんなエンジニアが入社から1年ちょっと経過して、どのようなことをやっていったのかについて話していこうと思います!

当時の心境などを交えてお話ししますので、今年入社される方や今就活中の方に、新卒としての働き方や、「こういったことをやるのか!」などといったイメージを共有できればと思います!

実務に取り掛かるまで

2023年のアドウェイズでは、4月の初めに営業さんなども含めた新卒全員の研修を行いました。その後部署に配属され部署の新卒研修を終えたのち、それぞれの開発チームに配属されるというような流れになっています。

チーム配属後の研修は、チームによって十人十色でありさらに研修を行うようなところもあれば、OJT形式で実際にタスクをこなしていくところもあります。

僕が所属している開発チームでは主に、社内での申請やコミュニケーションの手段として活用しているkintoneの開発運用のほか、社外向けの広告受注から社内向けの会計補助など、さまざまな自社製ツールの新規機能開発や運用を行っています。

配属後、はじめに新卒研修としてはOJTよりの形で実際のタスクをこなしつつ、場数を踏んで業務へのコミットを増やしていきました。

この記事では、そんな僕が一年目で行なった経験を共有しようと思います!

背景

このように、運用・改修をこなしていた中で10月ごろからkintoneへの新機能追加を開始しました。 内容としては、電子メールでクライアントに請求書を送付できるようにするといったものです。
アドウェイズではこれまでクライアントへの請求書の送付を郵送で行っていましたが、kintoneに電子メールで請求書を送付する機能を加えました。

段階的に郵送からの移行を行い、最終的にはどのクライアントへの請求書の送付もメール送付で済ませるようにするような改修を進めていました。

このような改修に至った背景としては、2024年1月から電子取引情報の電子保存をする、電子帳簿保存法の猶予措置が終わって義務化されたことが挙げられます。

やっていこう!

要件定義

プロジェクトを開始するにあたって、プロダクトオーナーから大まかな要件が共有されます。 そこでは、扱う文書が「請求書」となるため、ただのメールを送って終わりの機能では機能不足で、クライアントにちゃんと届いたか、誤ったアドレスに送付していないかといったことを担保する必要があるといった点を共有しました。

そのため、開発の要件をまとめますと以下のようになります。

  • 請求書をメールで送信できる
  • 誤送付を防止する仕組みがある
  • 開封状況の同期ができる

それらの要件を踏まえた上で、機能を追加するにあたってまずはどんなサービスが今回の改修に適しているか、それぞれのサービスの利点や欠点は何なのかをあげて選定を行う必要があります。

そのようないわゆる上流工程にあたるような箇所に対して、新卒のうちは上長が定めた開発の流れに沿って定義通りの開発を行っていくことが多いような印象ですが、このような工程から早速フルでコミットを行なっていきました。

初めのうちは、工数見積もりや機能比較などのノウハウもわからなかった状態でしたが、開発チーム内でのレビューや意見交換を通して開発のみではなかなか得られない業務経験をすることができました。

開発

さて、内容が決まったので次は実際に開発を行います!

今回の場合、kintoneの機能である「プロセス管理」で内容の承認を得たことをトリガーとしてメール送信処理を実行します。

これを実現するために、以下のようなサービスを利用しています:

  • kintoneのJavaScriptクライアント
    • プロセスが進行した時にメールの送信を実行
  • kMailer
    • kintoneのレコード内容とテンプレートを組み合わせてメールの文面を作成するためのサービス
  • SendGrid
    • 実際にメールの送付を行うSMTPサービス

技術選定を行う際に、最も選択に悩んだ部分はどのSMTPサーバーを使うかといったところです。今回の開発では、SendGridの他にCueNoteやAWSのSimple Email Serviceを比較対象としていました。

そのサービスの中でSendGridが決め手となった理由としては、クライアントがメールを開封したことや送信エラー(バウンス)などのイベントの検知+通知をサポートしているかどうかといったところです。

SendGridではこれらの情報を検知した上、Webhookとして通知できるといった機能があります。

この機能を用いて、Slackへのエラーの通知やkintoneへのクライアント開封情報の同期などを実現しました。

実際に今回作成した機能の概略図はこのようになっています。

請求書メールに必要な情報を保存するアプリ(長いので履歴アプリと呼ぶことにします)を間に挟んでいる理由としては、kMailerのトリガーをレコード通知のみに済ませることができるほか、請求書ファイルの実体やクライアントの担当者情報などを別々のアプリで管理しているためです。

(結果として、メールに関する情報を一元的にまとめることで送信履歴として運用・管理といった面でも使い勝手の良いアプリとなっています。)

このシステムでは、根幹の部分が出来上がっていますがサービスを組み合わせているという関係上kintoneアプリへの同期やエラーのSlack通知などの機能がなく、最低要件としてはまだまだ満たせていないような状況です!

そこで今回はそういった糊付け部分の実装として、AWS Lambdaを利用しています。

具体的には以下のような機能の実装にLambdaを用いています:

  • SendGridのWebhookを受け取ってエラーの通知と送信成否の状況の反映をする
  • kMailerの送信成否の状況をkintoneに同期する
  • kMailerとSendGridの送信成否の状況をもとに確認アプリに現在の状態を反映する
    • 確認用アプリはアドウェイズの営業が確認する機会が多く、既存の業務フロー中に簡単にメールの状況を確認するために履歴アプリから定期的に確認用アプリに状況を反映させています

また、開発を容易にするためにアドウェイズのkintone上には実運用するアプリの他に開発環境やステージング環境用のアプリを用意しています。ステージングや本番環境へLambda関数をPRのマージをもとに自動でデプロイするためにGitHub Actionsを利用しています。 mainブランチでステージング、productionブランチで本番環境へのデプロイが行われます。

Lambda関数管理用のリポジトリは以下のような構造になっています。

lambda-repo
|- .github/workflows
|  |- staging_deploy.yml
|  |- production_deploy.yml
|
|- <関数名>/function
   |- development.yml
   |- staging.yml
   |- production.yml
   |- index.mjs
   |- package.json
   |- package-lock.json

また、workflowとしては、ステージングのものを例としてあげると以下のようになっています。

name: Staging Deploy

on:
  push:
    branches:
      - main

env:
  AWS_REGION: ap-northeast-1
  AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
  AWS_ROLE_ARN: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GitHubActions
  AWS_S3_BUCKET: s3-bucket-name
  GIT_DIFF_FILTERED:
  MATCHED_FILES:

permissions:
  contents: read
  pull-requests: read
  id-token: write

jobs:
  staging-deploy:
    runs-on: ubuntu-22.04
    steps:
      - name: Git Checkout
        uses: actions/checkout@v4
      - name: Check Diff
        uses: technote-space/get-diff-action@v6
        with:
          PATTERNS: |
            **/index.*
            **/staging.json
          FILES: |
            package-lock.json
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v3
        with:
          role-to-assume: ${{ env.AWS_ROLE_ARN }}
          aws-region: ap-northeast-1
      - name: Install Lambroll
        uses: fujiwara/lambroll@v1
        with:
          version: v1.0.1
      - name: Deploy Lambda Functions
        id: lambroll
        if: env.GIT_DIFF_FILTERED
        run: |
          for DIFF_FILE in ${{ env.GIT_DIFF_FILTERED }}; do
            FUNCTION_NAME=$(echo $DIFF_FILE | sed -n 's/\/.*//p')
            cd $FUNCTION_NAME/function
            lambroll deploy --function="staging.json"
            cd ../..
          done
      - name: Create Module Zip
        id: module_zip
        if: env.MATCHED_FILES
        run: |
          for DIFF_FILE in ${{ env.MATCHED_FILES }}; do
            FUNCTION_NAME=$(echo $DIFF_FILE | sed -n 's/\/.*//p')
            cd $FUNCTION_NAME/function
            npm ci
            mkdir nodejs
            mv node_modules/ nodejs/node_modules
            zip -r ${FUNCTION_NAME}_module.zip nodejs
            cd ../..
          done
      - name: Deploy Lambda Layer
        if: steps.module_zip.conclusion == 'success'
        run: |
          for DIFF_FILE in ${{ env.MATCHED_FILES }}; do
            FUNCTION_NAME=$(echo $DIFF_FILE | sed -n 's/\/.*//p')
            ENV_FUNCTION_NAME="${FUNCTION_NAME}_stg"
            cd $FUNCTION_NAME/function
            aws s3 cp ${FUNCTION_NAME}_module.zip s3://${{ env.AWS_S3_BUCKET }}/${ENV_FUNCTION_NAME}_module.zip
            aws lambda publish-layer-version --layer-name $ENV_FUNCTION_NAME --content S3Bucket=${{ env.AWS_S3_BUCKET }},S3Key=${ENV_FUNCTION_NAME}_module.zip
            LATEST_LAYER_VERSION=$(aws lambda list-layer-versions --layer-name $ENV_FUNCTION_NAME --query 'LayerVersions[0].Version' --output text)
            aws lambda update-function-configuration --function-name $ENV_FUNCTION_NAME --layers arn:aws:lambda:${{ env.AWS_REGION }}:${{ env.AWS_ACCOUNT_ID }}:layer:${ENV_FUNCTION_NAME}:${LATEST_LAYER_VERSION}
            cd ../..
          done
      - name: No Change
        if: steps.lambroll.conclusion == 'skipped' && steps.module_zip.conclusion == 'skipped'
        run: echo "No deploy files!"

それぞれの関数に対して、index.mjsに差分がある場合はlambrollを使用して関数の内容をデプロイし、package-lock.jsonに差分がある場合はnpm ciを使ってnode_modulesを作成し、LambdaのLayerとしてデプロイしています。

関数全体を直接アップロードするのではなく依存モジュールをLayerとしてデプロイすることで、パッケージサイズの減少が見込めるほか、ファイル数が多くなりすぎてAWSコンソール上から関数のコードエディタを利用できなくなるといったことを防げます

テスト・リリース

機能の実装が終わったらテストを行いました。kintoneでの動作を確認したいため、ソースコードベースでのテスト実行ではなくテストケースをドキュメント化し、実際にレコードを作成してみて動作の確認を行いました。

リリースに関しても、サービスが色々あることやkintoneアプリの依存関係などが複雑であるためリリース用のドキュメントを作成し、それをもとにリリース作業を行いました。

アドウェイズではこういった社内WikiとしてConfluenceを利用しています。 スプリントタスクをJiraで管理しているため相性が良く、表やコードスニペットなども扱いやすいためJiraの各タスクに対してのレビュー用資料もConfluenceを使用して作成しています。

積極的にドキュメントを残すといった開発チームの風土のおかげで、複雑なリリース作業もある程度手際良く行うことができました。(もちろんめちゃくちゃ緊張しました!!)

全体を通して

かなり駆け足でしたが、今までに行なった実装を振り返ってみました。 これらの開発を通して思ったことや改善したいことを紹介します。

どんなタスクが残っているか、あとどれくらい掛かるかに具体性を持たせる

業務での開発と個人開発の大きな違いは、見積もりをしておおよその開発スケジュールを出す必要があることと、チーム単位でレビューを通して開発することにあると思っています。 初期の段階で工数見積もりをして終わりとするのではなく、常にどんなタスクが残っているか、そのタスクはどれくらい重いかといったことを可視化・共有し続けることで明確なリリース目標が立てられるようになると実感しました。

今回、開発中期にチーム内でMTGを行い、具体的なタスクを洗い出して、優先度を決めてタスクを実行するスプリントまで決めました。 こうすることで、次何をやればいいのか、何が残っているかといったことをスムーズに把握することができて、非常に良い効率で開発が行えるようになりました。

工数見積もりはバッファを十分に設けた方が良い

開発要件を決める際、おおよその開発目安となる工数を立てる事をしましたが、各々の仕様を詳しく知らないため、どうしても工数見積もりを楽観的に立ててしまっていました。

実際開発を進めていく中で要件を満たす機能がサービス単体では足りず、補強のためにLambda関数を開発する工数については初期の段階では考慮できていませんでした。

とはいっても初期調査の段階で、こういった抜けもれを見つけられればいいのですがどうしても難しいものがあります。 そのため、対プロダクトオーナー目線に立ってみると楽観的な見積もりをして先延ばしにするよりも、ある程度開発中の追加機能や補強事項が発生する事を念頭において、バッファを多めに見積もることが重要となります。

今回の件でいい教訓となりました!

20分迷ったら相談する

私が所属している開発チームでは、朝と夕方にチームレビューを行います。 開発中に生じた懸念点などはこの場で共有することが多かったですが、当時の状況を省みると 疑問点は生じた瞬間に共有することが重要と感じる場面が多かったです。

記憶の鮮度が高いうちに共有しないと、日を跨いで何が懸念だったか抜け落ちてしまったり 他の方のレビュー時間を押すことになってしまい、予定時刻をオーバーすることがままありました。 そのため、何か開発中に滞った際には、解決策がすぐに見つけられないようであれば 迷わずに相談を行うことで結果として開発をスピーディーに行えると感じました。

機能完成後は、認識の齟齬や追加開発事項がないかを他の部署を交えて現状共有する

今回の開発では最終的にできたプロダクトが、本当に求めるものにマッチしているかといったことを開発終盤に経理や営業の方を交えて共有を行いました。 Slackへのエラー通知はどういった頻度が良いのかといった利用者とのインターフェースとなる部分や、送信されるファイルにパスワードはいるかどうかなど、開発チーム内だけでは詳細に決められないようなところに対して、実際にどういった流れでメールの送信が行われるのかといったデモンストレーションを交えて認識合わせを行いました。

このMTGを行ったことで、開発が終わった後の具体的な運用フェーズでどう経理や営業に報告をすれば良いかなど、開発後の流れに対して具体的なビジョンを持つことができました。

一からシステムを構築する際の流れを身につけることができた

今回の開発では、要件定義のほか工数見積もりから稟議、アカウントの棚卸しやセキュリティ事項 の確認など、業務上で新規SaaSを導入する際に発生する業務を実際に経験することができました。

この経験を若いうちにできたことは今後の開発を行う上での非常に大きな財産となると思うので、今回学んだ懸念事項や大まかな流れをしっかり覚えておくようにしていきたいと思っています。

テストの実行の際には、実際にアプリを使うユーザーとして実行する

細かいところになりますが、リリース後アプリの権限設定がうまくなされておらず 実際に使う営業さんなどのアカウントではエラーが生じてしまうというようなことがありました。

どうしても開発中は、なんでも権限を持っている管理者アカウントを使っての開発が主になってしまい 一般ユーザー目線での権限などの観点が抜け落ちてしまってうことがあります。

こういった抜けもれを検知できるようにテストの実行時には一般ユーザーを想定して行うことが事前にエラーを検知するために重要であると感じました。 基本的かつ、とても細かいですが、今回の開発を通して最もみに染みて肝に銘じようと思ったことです。

まとめ

今回は、私が新卒として開発部署に参加してから一機能の開発を要件定義から行ってみたという経験談を実際の開発の流れなどを交えて振り返ってみました。 チーム開発自体、入社して初めてで初めはわからないことが多かったですが、一プロジェクトで大きなコミットをするという経験を通して、開発の流れや全体的な進め方など非常に多くのことを学ぶことができました。

上でも述べた通り、SaaSの決定から契約など一年目としては割とボリュームの多いことを任されて、色々と詰まることはありましたが、非常にいい経験を得ることができました。

今後の開発でもこれらの経験で得た糧を活かし、取り組んでいきたいと思っています。

最後まで閲覧いただきありがとうございました!