Fat Model, Skinny Controller 라는 말을 들어봤을 겁니다. 아마도 루비 온 레일즈가 개발자 커뮤니티에 크게 영향을 미친 만트라 인것으로 알고 있는데요. 컨트롤러를 최대한 가볍게 유지하라는 로직은 모델에서 처리하고 컨트롤러는 최소한의 일만 하게 하라는 것이죠.
요즘에는 모델도 가볍게 하라는 얘기가 종종 들립니다. 클린 아키텍처 인 PHP에도 MVC 패턴을 잘못 쓰는 쓰는 현상의 하나로 “비만 모델”이라는 표현이 나옵니다.
스파티의 프릭 반 더 허르텐이 “액션으로 리팩토링하기(Refactoring to Actions)”라는 글로 컨트롤러와 모델 모두 가볍게 유지할 수 있는 좋은 기법을 소개했습니다.
스파티에서는 단 하나의 로직을 처리할 목적으로 클래스를 만들며, 이 클래스를 “액션”이라고 부른다고 합니다. 액션 클래스는 execute()
라는 단 하나의 공개 메서드를 갖게 한다고 하네요. “액션”이라고 부르는 것도 메서드 이름을 “execute”라고 한 것도 단지 스파티의 정책일 뿐입니다. 따라서 다른 이름으로 부르고 다른 메서드명을 사용해도 상관없습니다. 실제로 댓글에 보면 “도메인 서비스”라는 더 보편적인 용어가 있다는 지적이 있습니다.
프릭 반 더 허르텐 글의 예제 코드를 보면 이해에 도움이 될 것입니다. 블로그에 ‘공개하기’ 기능이 필요한 경우 흔히 아래와 같이처럼 컨트롤러에 메서드를 추가합니다.
class PostsController
{
public function create()
{
// ...
}
public function store()
{
// ...
}
public function edit()
{
// ...
}
public function update()
{
// ...
}
public function delete()
{
// ...
}
public function publish(Post $post, TwitterApi $twitterApi)
{
$post->markAsPublished();
$twitterApi->tweet($post->title . PHP_EOL . $post->url);
flash()->success('Your post has been published!');
return back();
}
}
컨트롤러에는 기본 메서드 이외의 메서드를 추가하지 않는 것을 권장합니다. 대신 컨트롤러를 새로 만듭니다. 라라벨 제작자가 추천한 라라벨 코드 깔끔하게 짜는 방법 초간단 요약을 참고하세요.
공개하기를 처리하는 컨트롤러를 추가하고, “액션”을 활용한 최종 코드는 아래와 같습니다.
namespace App\Actions;
use App\Services\TwitterApi;
class PublishPostAction
{
/** @var \App\Services\TwitterApi */
private $twitter;
public function __construct(TwitterApi $twitter)
{
$this->twitter = $twitter;
}
public function execute(Post $post)
{
$this->markAsPublished($post);
$this->tweet($post->title . PHP_EOL . $post->url);
}
private function markAsPublished(Post $post)
{
$post->published_at = now();
$post->save();
}
private function tweet(string $text)
{
$this->twitter->tweet($text);
}
}
namespace App\Http\Controllers;
use App\Actions\PublishPostAction;
class PublishPostController
{
public function __invoke(Post $post, PublishPostAction $publishPostAction)
{
$publishPostAction->execute($post );
flash()->success('Hurray, your post has been published!');
return back();
}
}
PostController
에 있던 공개하기 로직이 PublishPostAction
클래스로 빠졌습니다. 컨트롤러에서는 메서드 인젝션으로 PublishPostAction
을 주입받아 사용합니다. Post
모델에 있던 markAsPublished()
메서드도 PublishPostAction
으로 뺐습니다. 컨트롤러와 모델 모두 더 가벼워졌고, 공개하기 관련 로직은 PublishPostAction
으로 모였습니다.
로직이 별도의 컨트롤러에 있지 않고 별도의 클래스에 있기 때문에 아티즌 커맨드에서도 사용할 수 있습니다.
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Actions\PublishPostAction;
use App\Models\Post;
class PublishPostCommand extends Command
{
protected $signature = 'blog:publish-post {postId}';
protected $description = 'Publish a post';
public function handle(PublishPostAction $publishPostAction)
{
$post = Post::findOrFail($this->argument('postId'));
$publishPostAction->execute($post);
$this->comment('The post has been published!');
}
}
이렇듯 로직을 액션으로 빼내면 애플리케이션 여기저기에서 사용할 수 있고, 테스트하기 쉬워집니다. 액션이 커지면 더 작은 액션들로 쪼개서 클래스를 작게 유지할 수 있습니다.
원문에서는 코드를 점진적으로 개선하고 위에 소개한 코드 이후에도 테스트하는 코드, 액션을 더 작은 액션들로 쪼개는 코드 예제도 나와있습니다. 이 기법에 관심이 있으시다면 원문을 한 번 읽어보시고 적용하면 더 좋을 것 같아요.
1일 1식 라라벨 28호
2019년 8월 7일
메쉬 코리아 개발자. 바쁜 팀장님 대신 알려주는 신입 PHP 개발자 안내서를 쓰고, 클린 아키텍처 인 PHP를 번역했습니다. 처음부터 제대로 배우는 라라벨(Laravel Up & Running 2nd Edition)을 번역했습니다.