CRUD 작업을 수행하거나 특정 방식으로 관계를 수정할 때마다 다른 작업을 원한다고 가정 해 봅시다. 예를 들어 누군가가 게시물을 게시 할 때마다 분석을 위해 테이블에 무언가를 저장하려고합니다. 아마도 가장 좋은 예는 아니지만 일반적으로이 “그룹화 된”기능이 많이 있습니다.
일반적으로 이러한 유형의 논리가 컨트롤러에 적용됩니다. 이 기능을 많은 곳에서 재현하고 싶을 때까지는 괜찮습니다. 부분적으로 들어가고 API를 만들고 더미 컨텐츠를 생성하기 시작하면 DRY를 유지하는 데 문제가됩니다.
이를 관리하는 방법은 이벤트, 리포지토리, 라이브러리 및 모델에 추가하는 것입니다. 각각에 대한 나의 이해는 다음과 같습니다.
서비스 : 대부분의 사람들이이 코드를 넣을 수있는 곳입니다. 서비스의 주요 문제는 때로는 특정 기능을 찾기가 어려우며 사람들이 Eloquent를 사용하는 데 집중할 때 잊어 버린 것 같습니다. publishPost()
내가 할 수있을 때 라이브러리에서 메소드를 호출해야한다는 것을 어떻게 알 수 $post->is_published = 1
있습니까?
내가 잘 작동하는 유일한 조건은 서비스 만 사용하는 것입니다 (그리고 컨트롤러에서 모두 Eloquent에 액세스 할 수 없도록하는 것이 이상적입니다).
궁극적으로 요청이 일반적으로 모델 구조를 따르는 경우 불필요한 추가 파일을 많이 생성하는 것처럼 보입니다.
리포지토리 : 내가 이해 한 바에 따르면 이것은 기본적으로 서비스와 비슷하지만 인터페이스가 있으므로 ORM간에 전환 할 수 있습니다.
이벤트 : 모델 이벤트가 항상 Eloquent 메소드에서 호출되므로 일반적인 컨트롤러처럼 컨트롤러를 작성할 수 있기 때문에 이것이 가장 우아한 시스템이라고 생각합니다. 나는 이것들이 지저분 해지는 것을 볼 수 있으며 누군가 중요한 커플 링을위한 이벤트를 사용하는 대규모 프로젝트의 예를 가지고 있다면 그것을보고 싶습니다.
모델 : 전통적으로 CRUD를 수행하고 임계 커플 링을 처리하는 클래스가있었습니다. CRUD +와 관련된 모든 기능을 알고 있기 때문에 실제로는 쉽게 할 수 있습니다.
간단하지만 MVC 아키텍처에서는 일반적으로 내가 본 것이 아닙니다. 어떤 점에서는 찾기가 더 쉽고 추적 할 파일이 적기 때문에 서비스보다 이것을 선호합니다. 그래도 약간 혼란 스러울 수 있습니다. 이 방법에 대한 몰락과 대부분의 사람들이 왜 그렇게하지 않는지 듣고 싶습니다.
각 방법의 장단점은 무엇입니까? 뭔가 빠졌습니까?
답변
SOLID 원칙 을 따르는 한 모든 패턴 / 아키텍처가 매우 유용하다고 생각합니다 .
논리를 추가 할 위치에 대해서는 단일 책임 원칙 을 참조하는 것이 중요하다고 생각합니다 . 또한 제 대답은 당신이 중형 / 대형 프로젝트를 진행하고 있다고 생각합니다. 그것이 페이지에 던지기 프로젝트 인 경우,이 답변을 잊어 컨트롤러 나 모델에 모두 추가하십시오.
짧은 대답은 : 그것은 (서비스) 당신에게 의미가 어디 .
긴 대답 :
컨트롤러 : 컨트롤러 의 책임은 무엇입니까? 물론, 모든 논리를 컨트롤러에 넣을 수는 있지만 컨트롤러의 책임입니까? 나는 그렇게 생각하지 않습니다.
나에게 컨트롤러는 요청을 받고 데이터를 반환해야하며 이것은 유효성 검사를하거나 db 메소드를 호출하는 장소가 아닙니다.
모델 : 사용자가 게시물의 투표 수를 등록하거나 업데이트 할 때 환영 이메일을 보내는 것과 같은 논리를 추가하기에 좋은 장소입니까? 코드의 다른 곳에서 동일한 이메일을 보내야한다면 어떻게해야합니까? 정적 메소드를 작성합니까? 해당 이메일에 다른 모델의 정보가 필요한 경우 어떻게합니까?
모델이 엔티티를 나타내야한다고 생각합니다. Laravel으로, 나는 단지 같은 것들을 추가하는 모델 클래스를 사용 fillable
, guarded
, table
(다른 모델도있을 것입니다, 나는 저장소 패턴을 사용하기 때문에이과의 관계를 save
, update
, find
, 등의 방법).
리포지토리 (리포지토리 패턴) : 처음에 나는 이것으로 매우 혼란 스러웠습니다. 그리고 당신처럼, 저는 “저는 MySQL을 사용합니다.”라고 생각했습니다.
그러나 리포지토리 패턴 사용의 장단점을 균형있게 조정했으며 이제는 사용합니다. 나는 지금 이 순간에 MySQL 만 사용하면 된다고 생각한다 . 그러나 3 년 후 MongoDB와 같은 것으로 변경해야 할 경우 대부분의 작업이 완료됩니다. 하나의 추가 인터페이스와 $app->bind(«interface», «repository»)
.
이벤트 ( 관찰자 패턴 ) : 이벤트는 주어진 시간에 어떤 클래스에서든 던질 수있는 것들에 유용합니다. 예를 들어, 사용자에게 알림을 보내는 것을 생각하십시오. 필요할 때 응용 프로그램의 모든 클래스에서 알림을 보내도록 이벤트를 시작합니다. 그런 다음 UserNotificationEvents
사용자 알림을 위해 시작된 모든 이벤트를 처리 하는 클래스를 만들 수 있습니다 .
서비스 : 지금까지 컨트롤러 나 모델에 로직을 추가 할 수 있습니다. 나에게 서비스 내에 논리를 추가하는 것이 모든 의미가있다 . 서비스는 클래스의 멋진 이름입니다. 그리고 당신은 당신의 응용 프로그램 내에서 당신이 이해하는만큼 많은 수업을 가질 수 있습니다.
이 예를 보자. 얼마 전, 나는 구글 폼과 같은 것을 개발했다. 나는 시작 CustomFormService
과 함께 결국 CustomFormService
, CustomFormRender
, CustomFieldService
, CustomFieldRender
, CustomAnswerService
와 CustomAnswerRender
. 왜? 이해가 되었기 때문입니다. 팀과 함께 작업하는 경우 팀에 적합한 위치에 논리를 배치해야합니다.
서비스 대 컨트롤러 / 모델 사용의 이점은 단일 컨트롤러 또는 단일 모델에 의해 제한을받지 않는다는 것입니다. 응용 프로그램의 디자인과 요구에 따라 필요한만큼의 서비스를 만들 수 있습니다. 응용 프로그램의 모든 클래스 내에서 서비스를 호출하면 이점이 있습니다.
이것은 오래 걸리지 만 응용 프로그램을 어떻게 구성했는지 보여 드리고자합니다.
app/
controllers/
MyCompany/
Composers/
Exceptions/
Models/
Observers/
Sanitizers/
ServiceProviders/
Services/
Validators/
views
(...)
특정 기능을 위해 각 폴더를 사용합니다. 예를 들어, Validators
디렉토리에는 BaseValidator
검증 프로세스를 담당 하는 클래스 가 포함되어 있습니다 .$rules
$messages
특정 유효성 검사기 및 특정 유효성 검사기 (일반적으로 각 모델마다 하나씩)를 . 이 코드를 서비스에 쉽게 넣을 수는 있지만 서비스 내에서만 사용하더라도 지금은 특정 폴더를 갖는 것이 좋습니다.
다음 기사를 읽어보십시오. 내용이 조금 더 잘 설명 될 수 있습니다.
Dayle Rees (CodeBright의 저자)가 틀을 깨다
리포지토리 및 서비스를 사용하여 Laravel에서 코드 분리Chris Goosey의 를 :이 게시물에서는 서비스 및 리포지토리 패턴과 그 구성 방식을 잘 설명합니다.
라라 캐스트는 또한 리포지토리 단순화 및 단일 책임 이 있으며 실제 사례를 통해 훌륭한 리소스를 제공합니다 (비용을 지불해야하더라도).
답변
내 질문에 대한 답변을 게시하고 싶었습니다. 나는 이것에 대해 며칠 동안 이야기 할 수 있지만, 나는 그것을 빨리 올리기 위해 이것을 빨리 게시하려고 노력할 것입니다.
Laravel이 제공하는 기존 구조를 사용하여 결국 파일을 기본적으로 모델, 뷰 및 컨트롤러로 유지했습니다. 또한 실제로 모델링되지 않은 재사용 가능한 구성 요소에 대한 라이브러리 폴더도 있습니다.
서비스 / 라이브러리에서 모델을 래핑하지 않았습니다 . 제공된 모든 이유가 100 % 서비스 사용의 이점을 확신하지 못했습니다. 내가 틀릴 수도 있지만, 내가 볼 수있는 한, 모델로 작업 할 때 만들고 전환 해야하는 엄청난 양의 여분의 파일이 생성되고 웅변 사용의 이점을 실제로 줄입니다 (특히 모델 복구와 관련하여) 예를 들어 페이지 매김, 범위 등을 사용).
비즈니스 로직을 모델에 넣고 컨트롤러에서 직접 웅변 적으로 액세스합니다. 비즈니스 로직이 우회되지 않도록 여러 가지 접근 방식을 사용합니다.
- 접근 자와 뮤 테이터 : 라 라벨에는 훌륭한 접근 자와 뮤 테이터가 있습니다. 게시물을 초안에서 게시로 옮길 때마다 작업을 수행하려면 setIsPublishedAttribute 함수를 작성하고 거기에 논리를 포함하여 이것을 호출 할 수 있습니다
- 작성 / 업데이트 등 재정의 : 사용자 정의 기능을 포함하도록 모델에서 항상 Eloquent 메소드를 재정의 할 수 있습니다. 이렇게하면 모든 CRUD 작업에서 기능을 호출 할 수 있습니다. 편집 : 최신 Laravel 버전에서 create를 재정의하는 데 버그가 있다고 생각합니다 (따라서 부트에 등록 된 이벤트를 사용합니다)
- 유효성 검사 : 유효성 검사를 같은 방식으로 연결합니다. 예를 들어 CRUD 함수를 재정 의하여 유효성 검사를 실행하고 필요한 경우 접근 자 / 돌연변이를 실행합니다. 자세한 내용은 Esensi 또는 dwightwatson / 유효성 확인을 참조하십시오.
- 매직 메소드 : 모델의 __get 및 __set 메소드를 사용하여 적절한 경우 기능에 연결합니다.
- Eloquent 확장 : 모든 업데이트 / 생성 작업을 수행하려는 경우 웅변을 확장 하여 여러 모델에 적용 할 수도 있습니다.
- 사건 : 이것은 솔직한 것이며 일반적으로 이것을하기 위해 합의 된 장소입니다. 이벤트의 가장 큰 단점은 예외를 추적하기 어렵다는 것입니다 (Laravel의 새로운 이벤트 시스템에서는 새로운 경우가 아닐 수도 있습니다). 또한 전화를받을 때 대신에하는 일을 기준으로 내 이벤트를 그룹화하고 싶습니다. 예를 들어, 메일을 보내는 이벤트를 수신하는 MailSender 가입자가 있습니다.
- 피벗 / BelongsToMany 이벤트 추가 : 내가 가장 오랫동안 고민 한 것 중 하나는 belongsToMany 관계의 수정에 동작을 추가하는 방법이었습니다. 예를 들어, 사용자가 그룹에 참여할 때마다 작업을 수행합니다. 나는 이것을 위해 커스텀 라이브러리를 다듬었다. 아직 게시하지 않았지만 기능적입니다! 곧 링크를 게시하려고합니다. 편집 나는 모든 피벗을 일반 모델로 만들고 결국 내 인생이 훨씬 쉬워졌습니다 …
모델 사용에 대한 사람들의 우려를 해결 :
- 조직 : 예 . 모델에 더 많은 로직을 포함 시키면 더 길어질 수 있지만 일반적으로 모델의 75 %가 여전히 작습니다. 더 큰 것을 구성하기로 선택한 경우 특성을 사용하여 수행 할 수 있습니다 (예 : 필요에 따라 PostScopes, PostAccessors, PostValidation 등의 파일이 더 많은 모델의 폴더 만들기). 나는 이것이 특성이 반드시 필요한 것은 아니지만이 시스템은 문제없이 작동한다는 것을 알고 있습니다.
추가 참고 사항 : 서비스에 모델을 포장하는 것은 스위스 군용 칼을 가지고 많은 도구를 사용하고 기본적으로 똑같은 일을하는 다른 칼을 만드는 것과 같습니다. 예, 때로는 블레이드를 테이핑하거나 두 개의 블레이드를 함께 사용하고 싶을 수도 있지만 일반적으로 다른 방법이 있습니다.
서비스를 사용하는 경우 :이 기사는 서비스를 사용하는시기에 대한 훌륭한 예를 명확하게 설명합니다 ( 힌트 : 자주는 아님). 그는 기본적으로 객체가 수명주기의 이상한 부분에서 여러 모델이나 모델을 사용할 때 의미가 있다고 말합니다 . http://www.justinweiss.com/articles/where-do-you-put-your-code/
답변
컨트롤러와 모델 간의 논리를 만드는 데 사용하는 것은 서비스 계층 을 만드는 것 입니다. 기본적으로 이것은 내 앱 내의 모든 작업에 대한 흐름입니다.
- 컨트롤러는 사용자의 요청 된 조치를 받고 매개 변수를 전송하고 모든 것을 서비스 클래스에 위임합니다.
- 서비스 클래스는 입력 유효성 검사, 이벤트 로깅, 데이터베이스 작업 등 작업과 관련된 모든 논리를 수행합니다.
- 모델은 필드 정보, 데이터 변환 및 속성 검증 정의를 보유합니다.
이것이 내가하는 방법입니다.
이것은 무언가를 만드는 컨트롤러의 방법입니다.
public function processCreateCongregation()
{
// Get input data.
$congregation = new Congregation;
$congregation->name = Input::get('name');
$congregation->address = Input::get('address');
$congregation->pm_day_of_week = Input::get('pm_day_of_week');
$pmHours = Input::get('pm_datetime_hours');
$pmMinutes = Input::get('pm_datetime_minutes');
$congregation->pm_datetime = Carbon::createFromTime($pmHours, $pmMinutes, 0);
// Delegates actual operation to service.
try
{
CongregationService::createCongregation($congregation);
$this->success(trans('messages.congregationCreated'));
return Redirect::route('congregations.list');
}
catch (ValidationException $e)
{
// Catch validation errors thrown by service operation.
return Redirect::route('congregations.create')
->withInput(Input::all())
->withErrors($e->getValidator());
}
catch (Exception $e)
{
// Catch any unexpected exception.
return $this->unexpected($e);
}
}
작업과 관련된 논리를 수행하는 서비스 클래스입니다.
public static function createCongregation(Congregation $congregation)
{
// Log the operation.
Log::info('Create congregation.', compact('congregation'));
// Validate data.
$validator = $congregation->getValidator();
if ($validator->fails())
{
throw new ValidationException($validator);
}
// Save to the database.
$congregation->created_by = Auth::user()->id;
$congregation->updated_by = Auth::user()->id;
$congregation->save();
}
그리고 이것은 내 모델입니다.
class Congregation extends Eloquent
{
protected $table = 'congregations';
public function getValidator()
{
$data = array(
'name' => $this->name,
'address' => $this->address,
'pm_day_of_week' => $this->pm_day_of_week,
'pm_datetime' => $this->pm_datetime,
);
$rules = array(
'name' => ['required', 'unique:congregations'],
'address' => ['required'],
'pm_day_of_week' => ['required', 'integer', 'between:0,6'],
'pm_datetime' => ['required', 'regex:/([01]?[0-9]|2[0-3]):[0-5]?[0-9]:[0-5][0-9]/'],
);
return Validator::make($data, $rules);
}
public function getDates()
{
return array_merge_recursive(parent::getDates(), array(
'pm_datetime',
'cbs_datetime',
));
}
}
이 방법에 대한 자세한 내용은 Laravel 앱의 코드를 구성하는 데 사용합니다 : https://github.com/rmariuzzo/Pitimi
답변
제 생각에는 라 라벨에는 이미 비즈니스 로직을 저장하기위한 많은 옵션이 있습니다.
짧은 답변:
- laravel의
Request
객체를 사용 하여 입력을 자동으로 확인한 다음 요청의 데이터를 유지합니다 (모델 생성). 요청 에서 모든 사용자 입력을 직접 사용할 수 있으므로 여기서 수행하는 것이 좋습니다. - laravel의
Job
객체를 사용하여 개별 구성 요소가 필요한 작업을 수행 한 다음 파견하십시오. 나는Job
서비스 클래스를 포괄 한다고 생각 합니다. 비즈니스 로직과 같은 작업을 수행합니다.
긴 답변 :
필요한 경우
리포지토리 사용 : 리포지토리가 과장되어 바인딩되는 경우가 많으며 대부분의 accessor
경우 모델 로 사용됩니다 . 나는 그들이 확실히 사용하고 있다고 생각하지만 라 라벨을 완전히 버릴 수있는 유연성을 요구 하는 대규모 응용 프로그램을 개발하지 않는 한 저장소에서 멀리 떨어져 있어야합니다. 나중에 스스로에게 감사 할 것이며 코드는 훨씬 더 직설적입니다.
PHP 프레임 워크를 변경 하거나 laravel이 지원하지 않는 데이터베이스 유형 으로 변경 될 가능성이 있는지 자문하십시오.
당신의 대답이 “아마도 아닙니다”라면, 저장소 패턴을 구현하지 마십시오.
위 외에도 Eloquent와 같은 멋진 ORM 위에 패턴을 두드리지 마십시오. 필요하지 않은 복잡성을 추가하기 만하면 전혀 도움이되지 않습니다.
서비스를 조금만 활용 :
서비스 클래스는 비즈니스 로직을 저장하여 주어진 종속성으로 특정 작업을 수행 할 수있는 장소입니다. 라 라벨은이를 ‘작업’이라고 부르며, 맞춤형 서비스 클래스보다 훨씬 더 유연합니다.
라 라벨에는 MVC
논리 문제에 대한 다방면의 솔루션이 있다고 생각 합니다. 문제 나 조직 일뿐입니다.
예:
요청 :
namespace App\Http\Requests;
use App\Post;
use App\Jobs\PostNotifier;
use App\Events\PostWasCreated;
use App\Http\Requests\Request;
class PostRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required',
'description' => 'required'
];
}
/**
* Save the post.
*
* @param Post $post
*
* @return bool
*/
public function persist(Post $post)
{
if (!$post->exists) {
// If the post doesn't exist, we'll assign the
// post as created by the current user.
$post->user_id = auth()->id();
}
$post->title = $this->title;
$post->description = $this->description;
// Perform other tasks, maybe fire an event, dispatch a job.
if ($post->save()) {
// Maybe we'll fire an event here that we can catch somewhere else that
// needs to know when a post was created.
event(new PostWasCreated($post));
// Maybe we'll notify some users of the new post as well.
dispatch(new PostNotifier($post));
return true;
}
return false;
}
}
컨트롤러 :
namespace App\Http\Controllers;
use App\Post;
use App\Http\Requests\PostRequest;
class PostController extends Controller
{
/**
* Creates a new post.
*
* @return string
*/
public function store(PostRequest $request)
{
if ($request->persist(new Post())) {
flash()->success('Successfully created new post!');
} else {
flash()->error('There was an issue creating a post. Please try again.');
}
return redirect()->back();
}
/**
* Updates a post.
*
* @return string
*/
public function update(PostRequest $request, $id)
{
$post = Post::findOrFail($id);
if ($request->persist($post)) {
flash()->success('Successfully updated post!');
} else {
flash()->error('There was an issue updating this post. Please try again.');
}
return redirect()->back();
}
}
위의 예에서 요청 입력은 자동으로 확인되며, persist 메소드를 호출하고 새 Post를 전달하기 만하면됩니다. 가독성과 유지 관리 성은 항상 복잡하고 불필요한 디자인 패턴보다 우선해야한다고 생각합니다.
그런 다음 게시물이 존재하는지 여부를 확인하고 필요할 때 대체 논리를 수행 할 수 있으므로 게시물을 업데이트 할 때에도 동일한 지속 방법을 사용할 수 있습니다.