Wiz テックブログ

Wizは、最新のIoTやICTサービスをお客様に届ける「ITの総合商社」です。

【Laravel】DDDでセッションを取り扱う際の妥協した実装

こんにちは、バックエンドエンジニアの青山です。
先日LaravelでDDDするときのセッション取り扱いに困った末、ある程度いい感じに妥協した実装に辿り着きました。今回はそちらについて書こうと思います。

はじめに

「LaravelでDDD〜」と書きましたが、本記事の内容は正確には「Laravelでレイヤードアーキテクチャを採用する際のセッションの取り扱いの一例について」です。 DDDを行う中で出会った問題に対する解決策の一例なのでタイトルは上記のようにしました。

仕様

セッションの取り扱いと一言に言っても、その用途は多岐にわたります。今回は「Webサイトの多言語化」という仕様を例に実装します↓

  • サイト上にドロップダウンメニューがあり、そこから使用言語(日本語 / 英語 / 中国語 / ベトナム語 / ポルトガル語など)を選択できる
  • 使用言語を選択したらサイト上の一部のテキストが選択した言語に翻訳されて表示される
  • サイトには管理者用ページとユーザー用ページがあり、翻訳はユーザー用ページ全体でのみ行う

実装

タイトルにある通り今回の趣旨は妥協です。困ったらLaravelの機能を利用したりして、いい感じに手を抜きます。
Laravelにもともと実装されている多言語化機能はこちらを参照してください。

前提

レイヤー

既存の部分は Domain層(Domain modelやFactoryクラス、Repositoryのinterfaceなど)
Infrastructure層(RepositoryやQueryServiceの実装クラスなど)
UseCase層(UseCaseクラス、QueryServiceのinterfaceなど)
Presentation層(ControllerやFormRequest、Middlewareなど)
の4層に分割して実装されています(その前段階で文脈ごとに大きく分割されています。詳しくは以降のディレクトリ図参照)。

今回の設計では依存の向きは所謂レイヤードアーキテクチャのようにInfrastructure層に向かうものではなく、図の矢印で示すようにDomain層へ向かうものとなっています。 多言語化のためのセッションの取り扱いも、既存の作りと同じように層を分けて実装します。

全体の流れ

1.サイトのトップページのメニューから使用言語を選択
2./lang/:localeにGETリクエスト送信。localeには"ja"や"en"など文字列のパラメーターが入る想定
3.セッションにlocale値設定
4.もとのページにリダイレクト(このときmiddlewareでlocale設定)

もう少し詳細に書くと
リクエスト -> Controllerでパラメーター取得 -> UseCaseクラスに渡して色々する(この"色々"の内部でセッションに値を保存したりする)
みたいな感じです。

ディレクトリ構成

localeはユーザー側ページ全体で取り回される共通のものなので、どこに置くべきか迷いました。
現在以下のように分割されています

│   ├── Packages
│   │   ├── AdminPage //管理者用ページ
│   │   ├── FrontPage //ユーザー用ページ
│   │   │   ├── Appointment
│   │   │   ├── Contact
│   │   │   ├── Home
│   │   │   └── StaticPages
│   │   └── Shared

考えた結果、FrontPage全体で共通のものということでSharedというディレクトリを作成しました。
もはや何も考えていないのと同じですが、いい感じに妥協していくのが今回の趣旨です。どんどん妥協しましょう。

│   ├── Packages
│   │   ├── AdminPage
│   │   ├── FrontPage
│   │   │   ├── Appointment
│   │   │   ├── Contact
│   │   │   ├── Home
│   │   │   ├── Shared //<-追加
│   │   │   │   ├── Domain
│   │   │   │   ├── Infrastructure
│   │   │   │   ├── Presentation
│   │   │   │   └── UseCase
│   │   │   └── StaticPages
│   │   └── Shared

Domain model

今回の場合localeを司るモデルを作るのがいいかなと判断しました。ということでlocale用のDomain modelを作成します。以下のようにモデリングしました。

gistc00482616b954c51babb350dc151ad9d

│── Shared
├── Domain
│   └── Models
│       └── Locale.php
├── Infrastructure
├── Presentation
└── UseCase

セッションを取り扱うクラス

このクラスをShared以下のどの層に配置するかを考えます。そもそもどういうクラスなのかというと

  • localeの値をセッションに保存する
  • セッションに保存されているlocaleの値を確認する

この2つを行うクラスです。localeの値とセッションを使ってxxをする、という役割なのでUseCase層が適切かと思います。interfaceを作成してUseCase層に置きます。

gist5eb0257e22b7ceca7a2af9df60f1d2dc

│── Shared
├── Domain
│   └── Models
│       └── Locale.php
├── Infrastructure
├── Presentation
└── UseCase
   └── LocaleSessionInterface.php

次にこのinterfaceの実装クラスを作成し、Presentation層に配置します。

gist1150419297fcfac1fdc7c5d5c1728fed

getLocaleSession()から返すDTOは以下のようになっています。

gistfbe39c56e70a119b97fb9faad28038df

├── Shared //<-追加
│   ├── Domain
│   │   └── Models
│   │       └── Locale.php
│   ├── Infrastructure
│   ├── Presentation
│   │   ├── Dto
│   │   │   └── GetLocaleSessionResponse.php
│   │   └── LocaleSession.php
│   └── UseCase
│      └── LocaleSessionInterface.php

interfaceと実装クラスはAppServiceProviderでbindするのを忘れないでください。

実装クラスをPresentation層に置く理由

最初はinterfaceと同じUseCase層に置こうと考えました。しかし実装クラスではLaravelのsession()ヘルパーを利用して「具体的にどうするか」を記述しています。UseCase層にそこまで具体的なものを置くのはよくなさそうです。
残るはDomain、Infrastructure、Presentationの三つの層ですが、そもそも何のためにセッションにlocaleの値を保存したりするのかというと、サイト上に表示されるテキストをlocaleに応じて変更するためでした。それを考えるとPresentation層に実装クラスを置くことが適切だと思われます。

locale値を変更するUseCaseクラス

Controllerから呼び出されるクラスです。リクエストがきたらControllerからこのクラスにパラメーターを渡して後続の処理を行います。 パラメーターからLocaleのインスタンスを作成し、パラメーターの値をセッションに保存します。セッション保存の具体的な処理は上記のLocaleSessionクラスで行われるので、ここには抽象的な手続きのみ記述します。

gist05956c45ab6f0dcd4531d471c5fc6d28

こちらのクラスはトップページから使用言語を選択した時のUseCaseなので、Shared以下ではなく該当するコンテキストのディレクトリ以下に配置します。

├── Home
│   ├── Domain
│   ├── Infrastructure
│   ├── Presentation
│   └── UseCase
│       └── SetLocaleUseCase.php

Controller

こちらは特筆すべき部分はありません。素直に実装します。

gist2a5bade46210839f30a9c987b32f1ff0

├── Home
│   ├── Domain
│   ├── Infrastructure
│   ├── Presentation
│   │   ├── Controllers
│   │       └── LocaleController.php
│   └── UseCase
│       └── SetLocaleUseCase.php

現在のlocaleを設定するmiddleware

Laravelのlocale設定は

app()->setLocale('locale_value')

で実現できます(Facadeを使用する方法もあります)。この部分はLaravelのmiddlewareで行い、ユーザー側の全ページに対して適用します。

gist860ff2f0cdde0dc7d3d2433b83decf6e

app/Http/Kernel.phpの$routeMiddleware配列に忘れず追加しておきます。

['set.locale' => \App\Packages\FrontPage\Home\Presentation\Middleware\SetLocaleMiddleware::class,]
├── Home
│   ├── Domain
│   ├── Infrastructure
│   ├── Presentation
│   │   ├── Controllers
│   │    │    └── LocaleController.php
│   │   ├── Middleware
│   │         └── SetLocaleMiddleware.php
│   └── UseCase
│       └── SetLocaleUseCase.php

ルーティング

gistfd3a74cc91d52ece76a6a23a2fa23c83

これで一通りの実装が完了しました。後は公式ドキュメントに従って言語ファイルを作成すれば、@langディレクティブや__()ヘルパーを使用して翻訳文字列が取得可能です。

メリット

この実装のメリットは、「ある文脈におけるセッションの取り扱い方法が限定されているので、コード内に散乱しがちなヘルパーやFacadeの乱用を抑制できたり、セッションのキーにする文字列の管理がしやすくなる」ということだと考えています。また、文脈ごとに区切ってセッションの取り扱いを限定するという実装は、セッションを取り扱う他の様々な場面でも応用できるのではと思います。粒度も場合によりけりだと思いますので、もっとスコープを広くして汎用的なセッション取り扱いクラスを作ってもいいかもしれません。

最後に

Wizではエンジニアを募集しております。
興味のある方、ぜひご覧下さい!

careers.012grp.co.jp

参考図書: ドメイン駆動設計 モデリング/実装ガイド - little-hands - BOOTH