これは何
ADRパターンを使った設計でAPIを作成する。
特にLaravelでの Routes, Action, Responder 実装方法について書く。
環境
- Laravel 6.x
- PHP 7
ADRパターンとは
Action–domain–responder (ADR) is a software architectural pattern that was proposed by Paul M. Jones[1] as a refinement of Model–view–controller (MVC) that is better suited for web applications.
ということで、MVCパターン類似のwebアプリを作るにあたりいい感じに使えるパターンです。
Paul M. Jones(pmjones)氏による説明: http://pmjones.io/adr/
Laravel でADRパターンを実装する
Action と Routing の実装
routing ではメソッド名ではなく、クラス名を指定します。
Route::get('/user_info', \App\Http\Api\User\Actions\InfoGetAction::class);
以下のように Actionを定義しておくと、メソッドとしてクラス名が呼ばれた時にはマジックメソッドの__invoke()
が呼ばれます(コードはイメージです)。
class InfoGetAction extends Controller
{
private $userDomainService;
public function __construct(
UserDomainService $userDomainService
) {
$this->userDomainService = $userDomainService;
}
public function __invoke(Request $request)
{
// user をとってくる
$user = $this->userDomainService->get($request->input('id'));
return new InfoGetResponder(
$user->getName(),
$user->getLocation()
);
}
これにより Action クラスには一つしかメソッドが生えない制約になるので、1アクション1メソッドになります。
APIにおいては、URLパスとクラス階層が対応しやすくなり管理しやすくなりますし、神Controllerが作られないメリットがあります。
Responder の実装
class InfoGetResponder extends BaseResponder
{
private $userName;
private $userLocation;
public function __construct(
Name $name,
Location $location
) {
$this->userName = $name;
$this->userLocation = $location;
}
}
BaseResponder の実装は以下のようになっています。
abstract class BaseResponder implements Responsable
{
public function toResponse($request): JsonResponse
{
return new JsonResponse(object_to_array($this));
}
}
Responsable インターフェースを implements
して toResponse()
を実装しておくと、前述の Action における return new InfoGetResponder()
により勝手に toResponse()
を呼んでレスポンスにして返してくれます。
また、BaseResponder で戻り値を JsonResponse
に固定してしまうことにより、Responder を実装した場合には Json しか返さないように縛れます。これは環境によって良し悪しだとは思いますが、自分たちしか使わないクローズなAPIの場合はデファクトのJsonに決めてしまって問題ないだろうと考えてこうなっています。
object_to_array()
は再帰的にオブジェクトを array に変換してくれるメソッドです。
レポジトリ: https://github.com/polidog/object-to-array
レスポンスの例
この object を array にする形により、 /user_info にアクセスすると以下のようなレスポンスが帰ってきます。
{
"user_name": "yamotuki",
"user_location": "tokyo"
}
レスポンスのjsonフィールドは Responder の引数で指定している name
や location
ではなくて、 Responder のクラスフィールドの以下のものを snake_case に変換したものになっています。
private $userName;
private $userLocation;
これにより、Responder を new するときにDomainを渡して、今後Domainのバリューオブジェクトの値がリファクタリングで変更されても、レスポンスフォーマットは変更されないようになっています。つまり、レスポンスの形式をドメインの実装と切り離すことができました。