ピンポイントタイム散布(PTS)業務効率化に向けたWebアプリ・バックエンド開発インターン記録

はじめに

こんにちは。私は神戸大学2年の山田と申します。 本記事は、OPTiM TECH BLOG Advent Calendar 2025 Day 3 の記事です! 今回はKOBEインターンシップ・就業体験プログラム(ジョブ型就業体験)に参加し、インターン生として働いた様子を紹介します。

携わったサービスについて

私が今回担当したサービスはピンポイントタイム散布(PTS)になります。私は主に、Go言語を用いて、このPTSの管理業務を効率化するための社内向けWebツールのAPI開発を担当しました。 www.optim.co.jp

開発の背景と課題

PTSはドローンによって防除作業全体をDX化するものですが、従来はスケジュールなどの関連情報がExcelで管理されており、業務効率の面で課題がありました。

私は、この運用をWebシステム化し効率化を図るための社内ツールの開発を担当しました。

学びや気づき

環境構築

まず始めたのはWindowsの環境構築でした。環境構築に関しては1年前にもやったものの大学の先輩に手取り足取り手伝ってもらっていたのであまり意識せずにやっていたため、今回の環境構築において、いくつかの技術的課題に直面する部分がありました。

Docker Desktopを使わない

普段の個人利用ではDocker Desktopを使用していましたが、オプティムではDocker Desktopを採用しておらず代替手段を検討する必要がありました。 そのため、Docker CLIで直接Docker Engineを動かしました。こちらのほうがGUIがない分、メモリ効率が良いようです。

インストール手順はこちらを参考にしました。

Ubuntu | Docker Docs, Ubuntu | Docker Docs

Windows側でWSL内にアクセスしてしまっていた

開いているフォルダ名が¥¥wsl.localhost¥Ubuntu¥...のようになっている場合は、開発ツール一式はWSLの中で直接動いておらず、Windows側で動いているVSCodeがWSLのファイルシステムをネットワークドライブとして参照している状態で、この状態だとパフォーマンスが悪くなってしまったり、WindowsのツールがLinuxのファイルを操作しようとするため、ファイルの権限や改行コードの違いで予期せぬエラーが起こったりしました。

WSLの中で直接動かすためにはVSCodeの拡張機能「Remote Development」をインストールし、VSCodeの画面上左下のリモート接続インジケーター(>と<が重なったアイコン) からWSL に接続するか、WSL 内で code .コマンドを打つ必要がありました。

技術的な学び

今回の開発では、Go × GORM × クリーンアーキテクチャという構成で進めました。 この中で特に大きな学びとなったのは ORMの挙動をクリーンアーキテクチャの原則にどう適合させるかという点です。

クリーンアーキテクチャにおけるGORMの「見つからないエラー」の扱い方

GoのORMであるGORMと、クリーンアーキテクチャという設計原則を組み合わせて開発を行いました。ここで、ある典型的な壁に直面しました。

GORMで単一のレコードを取得する際、Firstメソッドは非常に便利です。しかし、レコードが見つからなかった場合、gorm.ErrRecordNotFoundというGORM固有のエラーを返します。

レコードが見つからないという事象は、単なるシステムエラーではなく、見つからなかったから、別の処理をするというビジネスロジックの判断材料になる重要な情報です。

クリーンアーキテクチャには内側(ユースケース層)は、外側(インフラ層)に依存してはいけないという大原則があります。

ユースケース層に「見つからない」と伝えたい。しかし、gorm.ErrRecordNotFoundをそのまま返すと、ユースケース層がGORMという外側の具体的な実装を知ってしまい、原則違反となります。

このジレンマを解決するため、インフラ層で対策を講じる必要があり、私は2つの対策があることを知りました。

解決策1:Firstメソッドの代わりにFindメソッドを使い、nilを返す

一つ目は、Firstの代わりにFindメソッドを使うアプローチです。

Findは、レコードが見つからなくてもエラーを返さず、空の結果を返します。この性質を利用し、0件だった場合はnilを返す、とリポジトリ側でハンドリングします。

// Infrastructure層
func (r *myRepo) GetProject(id int) (*domain.Project, error) {
    var project Project
    // Findは0件でもエラーを返さず、空の結果を返す
    result := r.db.Where("id = ?", id).Find(&project)
    if result.Error != nil {
        // DB接続エラーなど、予期せぬエラー
        return nil, result.Error
    }
    if result.RowsAffected == 0 {
        // 0件だった場合は、エラーではなく (nil, nil) を返す
        return nil, nil
    }
    return &project, nil
}

これにより、ユースケース層はGORMの存在を一切知る必要がなくなります。 errorは予期せぬシステムエラー、project == nilは、見つからなかったというビジネス上の結果、と明確に分離できます。

// Usecase層
func (u *myUsecase) GetProjectDetails(id int) (*Details, error) {
    project, err := u.repo.GetProject(id)
    if err != nil {
        // ここに来るのは本当に予期せぬDBエラーだけ
        return nil, err
    }
    
    // ビジネスロジック:「見つからない」をどう扱うか
    if project == nil {
        return nil, errors.New("案件が見つかりません")
    }

    // ...
}

解決策2:アプリケーション独自のエラーに翻訳する

もう一つのアプローチは、Firstを使いつつGORMのエラーをアプリケーション固有のカスタムエラーに翻訳することです。

インフラ層がgorm.ErrRecordNotFoundをキャッチし、それをapperror.ErrNotFoundのような内側で定義された抽象的なエラーに変換して返します。

// Infrastructure層
func (r *myRepo) GetUser(id int) (*domain.User, error) {
    var user User
    err := r.db.First(&user, id).Error
    if err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            // GORMのエラーを「独自のエラー」に翻訳する
            return nil, apperror.ErrNotFound 
        }
        return nil, err // その他のDBエラー
    }
    return &user, nil
}

ユースケース層は、GORMのことは知りません。ただapperror.ErrNotFoundが来たら、見つからなかった場合の処理をしようと、アプリケーション自身のルールにのみ依存します。

// UseCase層
func (u *myUsecase) GetUserProfile(id int) (*Profile, error) {
    user, err := u.repo.GetUser(id)
    if err != nil {
        if errors.Is(err, apperror.ErrNotFound) {
            // ビジネスロジック:「ユーザーが見つからない」のは、
            // この機能では「エラー」として扱う
            return nil, errors.New("指定されたユーザーは存在しません")
        }
        return nil, err // その他のエラー
    }
    // ... userを使った処理 ...
}

こうすることでユースケースはGORMのエラーを知ることなくアプリケーション独自のエラーでビジネスロジックを判断できるようになります。

仕事の進め方についての学びや気づき

大学で開発しているときにも感じていましたが、今回のインターン経験を通して、機能を満たすだけでは不十分ということをより強く実感しました。コードは一度書いて終わりではなく、将来の自分やチームメンバーが修正や機能追加を行う資産です。そのため、保守性や効率性を意識した読みやすいコードを書くことが非常に重要だと学びました。

また、要件や設計書が丁寧に整備されていたことで、途中参加でもスムーズに実務へと取り掛かることができました。一方で、求められている要件を正確に把握する難しさも感じました。要件が曖昧に思えたときは、設計書をしっかり読み込んだり、遠慮せずに質問したりすることの大切さを学びました。

まとめ

今回のインターンシップ・就業体験プログラムを通じて、大学での開発では決して得られない、非常に貴重な実務経験を積むことができました。

最後になりましたが、このような素晴らしい機会を提供してくださったオプティムの皆様に心から感謝申し上げます。

おわりに

オプティムは、AIやIoTなどの最先端技術で、様々な業界の課題解決に挑戦しています。 こうした取り組みに興味を持たれた方は、ぜひ下記の採用ページをご覧ください。 www.optim.co.jp