はじめに
初めまして!2025年10月の1ヶ月間 “Booost!!! Excite Internship 2025” に参加させていただいた、加藤といいます!
この記事では、このインターンシップで私が取り組んだ業務内容や、そこから得た学び等について紹介していければと思います。
自己紹介
私は情報工学専攻の修士1年生です。研究室の方では、AIの応用領域の研究を行っています。
私が “Booost!!! Excite Internship 2025” に参加しようと思った理由は、リアルなITエンジニアの業務を体験して、エンジニアとして働くことの解像度を高めたかったためです。
インターンシップでの業務内容
概要
「お悩み相談室」というサービスの中の「コラム詳細ページ」の、旧実装から新実装への移植作業を行いました。旧実装と新実装では、データ取得方法やアーキテクチャの構造などが異なるため、旧実装の実装内容を理解した上で、新実装のアーキテクチャの形式に変換していくことが求められる業務でした。
以降の説明では、新実装のアプリケーションを「本実装」として、新実装の説明を行っていきます。
本実装のアーキテクチャ構造
レイヤードアーキテクチャ
本実装では、レイヤードアーキテクチャ(層構造の設計)を採用しています。具体的には、以下のように役割ごとに層を分けて、それぞれが連携して動作するアプリケーションとなっています。
【リクエストの流れ】
ユーザのブラウザ
↓
① Controller層(入力の受付、案内係)
↓
② UseCase層(ビジネスロジック係)
↓
③ Repository層(データ取得係)
↓
④ ViewModel層(表示用データ整形係)
↓
⑤ View層(HTML表示係)
↓
ユーザのブラウザに表示
Controller層
リクエストを受け付け、UseCase と ViewModel を呼び出し、最終的に View に表示を指示する、司令塔の役割を果たします。ここにはビジネスロジック(複雑な計算や判断)は書かず、処理の振り分けに専念します。具体的には以下のような処理を行うことが考えられます。
public function index(Request $request): View
{
$userDomainData = $this->fetchUserListUseCase->execute('active');
$viewModel = $this->userListViewModel->build($userDomainData);
return view('users.index', [
'viewModel' => $viewModel
]);
}
UseCase層
アプリケーション固有のビジネスロジックを実行します。具体的には以下のように、Repository からデータを取得し、必要な処理を加えます。
public function execute(string $status): array
{
$allUsers = $this->userRepository->findByStatus($status);
$highScorers = [];
foreach ($allUsers as $user) {
if ($user->score >= 100) {
$user->name = strtoupper($user->name);
$highScorers[] = $user;
}
}
return $highScorers;
}
Repository層
データベースやJSONファイルなど、具体的なデータ保存場所とのやり取りを記述します。「どこから」「どうやって」データを取るかはこの層だけが知っている、すなわち、データ取得方法が隠蔽(抽象化)されているため、Repository層のデータ取得方法が変わったとしても、呼び出し側(UseCase)は影響を受けません。具体例は以下のようになります。
public function findByStatus(string $status): array
{
$users = User::where('status', $status)
->where('deleted_at', null)
->get();
return $users->toArray();
}
ViewModel層
UseCase から受け取ったドメインデータ(ロジック適用済みのデータ)を、View がそのまま表示できる形式に加工します。ViewModelで事前に整形を行うことによって、Viewファイル内で複雑な処理を書かないようにしています。具体例は以下のようになります。
public function build(array $userDomainData): self
{
$this->userCount = count($userDomainData);
$this->pageTitle = "ユーザ一覧 (" . $this->userCount . "名)";
$processedUsers = [];
foreach ($userDomainData as $user) {
$joinedDate = Carbon::parse($user['created_at'])->format('Y年m月d日');
$isPremium = $user['plan_type'] === 'premium';
$processedUsers[] = [
'displayName' => $user['name'] . '様',
'joinedFormatted' => $joinedDate,
'isPremiumUser' => $isPremium,
];
}
$this->displayUsers = $processedUsers;
return $this;
}
View層
Controller から渡された ViewModel のデータを使って、HTMLを組み立てます。複雑なロジックは書かず、「表示するだけ」に徹します。具体例は以下のようになります。
{{--
$viewModel (UserListViewModelのインスタンス) を
Controllerから受け取る
--}}
なぜこんなに複雑な構造にするのか?(アーキテクチャの設計思想)
各層が1つの役割だけを持って独立することで、どこを修正すればいいかが明確になり、個別にテストが行いやすくなり、将来の変更に強くなります。これを「責任の分離」といいます。
例えば、
- 表示が崩れた → View層だけ修正
- データ取得方法を変更 → Repository層だけ修正
- ビジネスルール変更 → UseCase層だけ修正
- Laravelから別のフレームワークに移行 → Controller, ViewModel, View だけ変更
のように修正すべきファイルを明確にし、将来の変更に強くなります。
アーキテクチャの設計思想についてより詳しく知りたいと思った方は、以下のWebページ等を参考にしてみてください。
Model View ViewModel – Wikipedia
UseCase層、Repository層に見られる3ファイル構成について
この設計では、UseCase層、Repository層が3種類のファイルで構成されています。これは契約(Interface)、データ転送(Output)、実装(Implementation)を明確に分離する設計パターンです。
Interface
この機能(UseCase、Repository等)が「何を受け取り」「何を返すか」だけを定義します。つまり、チーム内の「合意」であり、使う側はこの契約書だけを見て開発できるようになります。具体例は以下のようになります。
interface FetchUserListUseCaseInterface
{
@param
@return
public function handle(string $status): FetchUserListOutput;
}
Output
返すデータの型と構造を厳密に定義します。ロジックを持たず、型がすべてを語ります。これは Data Transfer Object (DTO) と呼ばれます。具体例は以下のようになります。
final class FetchUserListOutput
{
@param
@param
public function __construct(
public readonly array $users,
public readonly int $totalCount,
) {
}
}
final class UserOutputData
{
public function __construct(
public readonly string $id,
public readonly string $displayName,
public readonly string $joinedDateFormatted,
public readonly bool $isPremiumUser,
) {
}
}
実装クラス
Interface で約束した機能を実際に実装します。ロジック、データ加工、データ取得など、具体的な処理はすべてここで行います。具体例は以下のようになります。
class FetchUserListUseCase implements FetchUserListUseCaseInterface
{
private UserRepositoryInterface $userRepository;
public function __construct(UserRepositoryInterface $userRepository)
{
$this->userRepository = $userRepository;
}
{@inheritdoc}
public function handle(string $status): FetchUserListOutput
{
$domainUsers = $this->userRepository->findByStatus($status);
$filteredUsers = array_filter($domainUsers, function ($user) {
return Carbon::parse($user->created_at)->diffInYears(Carbon::now()) >= 1;
});
$outputUsers = [];
foreach ($filteredUsers as $user) {
$displayName = $user->name . '様';
$joinedDate = Carbon::parse($user->created_at)->format('Y/m/d');
$isPremium = $user->plan_type === 'premium';
$outputUsers[] = new UserOutputData(
id: (string) $user->id,
displayName: $displayName,
joinedDateFormatted: $joinedDate,
isPremiumUser: $isPremium
);
}
return new FetchUserListOutput(
users: $outputUsers,
totalCount: count($outputUsers)
);
}
}
なぜ3ファイルに分けるのか?
UseCase層がRepository層を呼び出す実装において、普通の実装を行うと、高レベルのモジュール(UseCase)が、低レベルのモジュール(Repository)の実装を知っている状態、すなわちUseCase層がRepository層に依存する状態になります。これは、ビジネスロジック(UseCase)が、インフラ(DB)の都合によって変更されてしまう、脆い(もろい)設計といえます。このような状態を解消するためには、抽象(Interface)を用いた設計が有効です。高レベルモジュールが低レベルモジュールのInterfaceのみを見て実装を行うことによって、高レベルモジュールが低レベルモジュールの実装に依存することを避けることができます。この時そのInterfaceを実装するのは低レベル層であるため、普通の実装における「高レベル層→低レベル層」の実装の依存関係から、「低レベル層→抽象(高レベル層が参照)」の実装の依存関係に変わります。このことを「依存性の逆転」と呼びます。
達成した業務内容
コラム詳細ページについて、Controller層、UseCase層、Repository層、ViewModel層、View層をすべて実装し、テストの作成・実行を行いました。
学んだこと・得られた経験
PHP、Laravelの業務経験
私は、本インターン参加前の時点で他の言語の開発経験はあったのですが、PHPは完全未経験でした。そのため、一からキャッチアップしながら業務に従事しました。業務で扱ったアプリケーションは規模が大きく、使用しているアーキテクチャも複雑な構造を持っていたので、キャッチアップに時間がかかってしまい、序盤のうちはなかなか思ったような実装を行うことができませんでした。そんな中でも担当社員の方が丁寧にアプリケーションについて教えてくださったおかげもあり、少しずつ理解が進んでいき、実装を行うことができました。
この、完全未経験の言語での開発を一からキャッチアップして、大規模アプリケーションへの実装を行うことができた経験というのは、自分の中で非常に大きなものになったのではないかと感じています。
リアルなエンジニア業務経験
本インターンではかなりエンジニアのリアルに近い実務経験を積めたのではないかと感じています。その理由は以下の通りです。
- 実際に社内で開発しているアプリケーションの実装を行えた
- 社内規定に従ってプルリクエストを出し、社員の方からレビューをいただいて修正する経験を積めた
- 開発チームの週次ミーティングに参加して、自分も開発チームの一員として進捗報告を行い、他の人の進捗報告を聞くという経験を積めた
本インターンでの業務を経て、「エンジニアとして働く」ということの自分の中のイメージがより具体化されたと考えています。
おわりに
1ヶ月の間、 “Booost!!! Excite Internship 2025” で貴重な経験を積ませていただきありがとうございました!PHP未経験の私を一から導いて下さった担当社員の方をはじめ、私を受け入れてくださった社員の皆様に感謝申し上げます。