81호. 라라벨 파이프라인의 이해와 활용 free

2019-10-28

80호. 라라벨 6.4.0 새기능Pipeline이 등장하는데요. 처음보는 클래스인데 매뉴얼에도 없더군요. 궁금해서 찾아보고 정리해봤습니다.


파이프라인 패턴


파이프라인 패턴은 데이터를 한 작업에서 다른 작업으로 순차적으로 전달하면서 처리하는 것입니다. 파이프라인 보다는 공장의 컨베이어 벨트를 떠올리면 더 적절할 것 같아요.


라라벨의 파이프라인


라라벨의 파이프라인 구현체는 Illuminate\Pipeline\Pipeline 입니다. 라라벨의 대표적인 파이프라인 활용처는 미들웨어입니다.


//vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php

/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);

Facade::clearResolvedInstance('request');

$this->bootstrap();

return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}

라라벨을 다뤄보신 분들이라면 라라벨로 리퀘스트가 들어오면, 우선 일련의 미들웨어를 거친 후 라우터로 전달된다는 것을 아실 겁니다. 위의 코드가 바로 그 부분에 해당하는 코드입니다.



  • new Pipeline() : 새 파이프라인을 만듭니다. 파이프라인은 생성자 파라미터로 컨테이너를 받습니다.

  • send() : 파이프라인을 통과시켜 처리할 데이터(여기서는 $request)를 전달합니다.

  • through() : 적용할 파이프를 지정합니다. 여기선 미들웨어를 지정했네요.

  • then(): 모든 파이프를 다 거친 후 수행할 행위를 지정합니다. 여기선 $this->dispatchToRouter()를 실행합니다.


라라벨 파이프라인 활용 예


Jeff Ochoa는 Understanding Laravel Pipelines에서 사용자가 글(thread)를 작성하는 예시를 제시했습니다.


사용자가 글을 올릴 때 데이터를 저장하기 전에 몇가지 처리를 한다고 가정해봅시다.



  • 욕설을 "*"로 바꾼다.

  • 링크 태그를 평문으로 변환한다.

  • 스크립트 태그를 제거한다.


<?php
public function create(Request $request)
{
$pipes = [
RemoveBadWords::class,
ReplaceLinkTags::clas,
RemoveScriptTags::class
];
$post = app(Pipeline::class)
->send($request->input('content'))
->through($pipes)
->then(function ($content) {
return Post::create(['content' => $content]);
});
// return any type of response
}

$request->input('content')가 RemoveBadWords, ReplaceLinkTags, RemoveScriptTags를 차례로 거치며 처리되어 then() 구문으로 전달됩니다.


파이프는 기본적으로 handle() 매서드를 호출합니다. RemoveBadWords 클래스를 예를 들면 아래와 같습니다.


<?php
namespace App;
use Closure;
class RemoveBadWords
{
public function handle($content, Closure $next)
{
// 여기에서 작업을 하고 업데이트된 $content를 다음 파이프로 전달합니다.

return $next($content);
}
}

handle()이 아닌 다른 매서드를 사용하고 싶으면 파이프라인에 via() 매서드를 적용하면 됩니다.


app(Pipeline::class)
->send($content)
->through($pipes)
->via(‘customMethodName’)
->then(function ($content) {
return Post::create(['content' => $content]);
});

인터페이스를 이용하면 더 안전하게 코딩할 수 있습니다.


<?php
namespace App;
use Closure;
interface Pipe
{
public function handle($content, Closure $next); // 혹은 via()로 사용하려는 커스텀 매서드
}

<?php
namespace App;
use Closure;
class RemoveBadWords implements Pipe
{
...

Philip Brown도 라라벨 파이프라인을 소개하고, API에 활용하는 시리즈 글을 썼습니다. 두번째 글에서 아주 간단한 조건만으로도 코드가 길고 복잡해지는 과정과, 이를 파이프라인을 이용해서 해결하는 모습을 보여줍니다. 관심 있는 분들은 한 번씩 참고해보셔도 좋을 것 같아요.


미들웨어나 이벤트를 쓰면 안될까?


파이프라인 개념을 학습하고 나서 가장 먼저 든 생각이 미들웨어나 이벤트를 쓰면 되는거 아닐까? 라는 것이었습니다. 미들웨어와 이벤트가 파이프라인 구현체인 것으로 보이고(이벤트는 확인해보진 않았어요) 굳이 매뉴얼에 사용방법이 나오지 않은 이유가 있을거라 생각했습니다.


매뉴얼을 다시 한 번 읽어본 결과 앞서 살펴본 Jeff Ochoa의 예제는 미들웨어나 이벤트로 처리하긴 어색하다고 느꼈습니다. 미들웨어는 주된 목적이 애플리케이션에 도달하기 전에 HTTP 리퀘스트를 걸러내는 것이라 저장할 데이터를 순차적으로 가공하는데는 적절하지 않습니다. 심지어 미들웨어는 순서가 보장되지도 않습니다. 이벤트도 하나의 데이터를 연쇄적으로 처리한다는 면에서 적합하지 않아보입니다. 리스너들 간의 선후 관계를 설정할 순 없어 보이며, 리스너에서 이벤트를 발생시키는 식으로 연쇄처리를 할 순 있지만 파이프라인을 사용하는 것 보다 훨씬 번거로워보이고, 리스너들 간의 의존성이 생겨서 재활용성이 떨어지는 것 같습니다.


마치며


파이프라인 패턴을 간단히 살펴보고, 라라벨의 파이프라인 클래스 사용법과 사례를 알아봤습니다. 라라벨이 아닌 프로젝트에서는 thephpleague/pipeline 패키지를 사용하면 파이프라인 패턴을 손쉽게 활용할 수 있습니다. 사용법은 조금 다르지만 기본 개념은 같으니 금새 익히실 수 있을 겁니다.


1일 1식 라라벨 81호

2019년 10월 28일


이현석

메쉬 코리아 개발자. 바쁜 팀장님 대신 알려주는 신입 PHP 개발자 안내서를 쓰고, 클린 아키텍처 인 PHP를 번역했습니다. 2020년에 출간될 Laravel Up & Running 2nd Edition을 번역하고 있습니다.