[oop] Laravel에서 관계 관리, 저장소 패턴 준수

Laravel의 좋은 디자인 패턴에 관한 T. Otwell의 책을 읽은 후 Laravel 4에서 앱을 만드는 동안 저는 애플리케이션의 모든 테이블에 대한 저장소를 만드는 것을 발견했습니다.

나는 다음 테이블 구조로 끝났습니다.

  • 학생 : 아이디, 이름
  • 과정 : id, name, teacher_id
  • 교사 : 아이디, 이름
  • 과제 : id, name, course_id
  • 점수 (학생과 과제 간의 피벗 역할) : student_id, assignment_id, scores

이 모든 테이블에 대해 찾기, 만들기, 업데이트 및 삭제 메서드가있는 저장소 클래스가 있습니다. 각 저장소에는 데이터베이스와 상호 작용하는 Eloquent 모델이 있습니다. 관계는 Laravel 문서에 따라 모델에 정의되어 있습니다 : http://laravel.com/docs/eloquent#relationships .

새 과정을 만들 때 내가하는 일은 Course Repository에서 create 메서드를 호출하는 것뿐입니다. 이 과정에는 과제가 있으므로 하나를 만들 때 해당 과정의 각 학생에 대한 점수 표에 항목을 만들고 싶습니다. 이 작업은 할당 저장소를 통해 수행합니다. 이는 할당 저장소가 할당 및 학생 모델을 사용하여 두 개의 Eloquent 모델과 통신 함을 의미합니다.

제 질문은이 앱이 아마도 크기가 커지고 더 많은 관계가 도입 될 것이므로 저장소의 다른 Eloquent 모델과 통신하는 것이 좋은 방법입니까, 아니면 다른 저장소를 사용하여 수행해야하는지 (할당 저장소에서 다른 저장소를 호출하는 것을 의미합니다) ) 아니면 Eloquent 모델에서 모두 함께해야합니까?

또한 점수 표를 과제와 학생 사이의 중심 축으로 사용하는 것이 좋은 방법입니까, 아니면 다른 곳에서 수행해야합니까?



답변

당신이 의견을 요구한다는 것을 명심하십시오 : D

여기 내 것 :

TL; DR : 예, 괜찮습니다.

당신은 잘하고 있습니다!

나는 당신이 자주하는 일을 정확히하고 그것이 잘 작동한다는 것을 알게됩니다.

그러나 나는 종종 테이블 당 리포지토리를 갖는 대신 비즈니스 로직을 중심으로 리포지토리를 구성합니다. 이것은 응용 프로그램이 “비즈니스 문제”를 해결해야하는 방법을 중심으로하는 관점이므로 유용합니다.

코스는 속성 (제목, ID 등)과 다른 항목 (자체 속성과 가능한 항목이있는 과제)을 포함하는 “항목”입니다.

“코스”저장소는 코스 및 코스의 속성 / 과제 (과제 포함)를 반환 할 수 있어야합니다.

운 좋게도 Eloquent로이를 달성 할 수 있습니다.

(종종 테이블 당 리포지토리로 끝나지만 일부 리포지토리는 다른 리포지토리보다 훨씬 더 많이 사용되므로 더 많은 방법이 있습니다. 예를 들어 “강좌”리포지토리는 할당 리포지토리보다 훨씬 더 완전한 기능을 제공 할 수 있습니다. 응용 프로그램은 코스의 과제 모음보다는 코스를 중심으로합니다.

까다로운 부분

데이터베이스 작업을 수행하기 위해 종종 내 저장소 내부의 저장소를 사용합니다.

데이터를 처리하기 위해 Eloquent를 구현하는 모든 저장소는 Eloquent 모델을 반환 할 것입니다. 그런 점에서 코스 모델이 과제 (또는 다른 사용 사례)를 검색하거나 저장하기 위해 기본 제공 관계를 사용하는 것은 괜찮습니다. 우리의 “구현”은 Eloquent를 중심으로 구축되었습니다.

실용적인 관점에서 이것은 의미가 있습니다. 데이터 소스를 Eloquent가 처리 할 수없는 (비 SQL 데이터 소스로) 변경하지 않을 것입니다.

ORMS

이 설정에서 가장 까다로운 부분은 Eloquent가 실제로 우리를 돕거나 해치는지를 결정하는 것입니다. ORM은 실제적인 관점에서는 우리에게 큰 도움이되지만 “비즈니스 로직 엔티티”코드와 데이터 검색을 수행하는 코드를 결합하기 때문에 까다로운 주제입니다.

이러한 종류의 경우 저장소의 책임이 실제로 데이터를 처리하는지 아니면 엔터티 (비즈니스 도메인 엔터티)의 검색 / 업데이트를 처리하는지 여부를 혼란스럽게합니다.

더욱이, 그것들은 당신이 당신의 뷰에 전달하는 바로 그 객체의 역할을합니다. 나중에 저장소에서 Eloquent 모델을 사용하지 않으려면 뷰에 전달 된 변수가 동일한 방식으로 작동하는지 또는 동일한 메서드를 사용할 수 있는지 확인해야합니다. 그렇지 않으면 데이터 소스를 변경하여 뷰, 그리고 당신은 (부분적으로) 당신의 논리를 리포지토리로 추상화하는 목적을 잃어 버렸다. 프로젝트의 유지 보수 가능성이 떨어졌다.

어쨌든 이것들은 다소 불완전한 생각입니다. 언급했듯이 그들은 단지 내 의견 일 뿐이다. 이는 도메인 중심 디자인 을 읽고 작년에 Ruby Midwest에서 “삼촌 밥의”기조 연설 과 같은 비디오를 본 결과입니다 .


답변

저는 Laravel 4를 사용하여 대규모 프로젝트를 마무리하고 있으며 지금 당장 질문하는 모든 질문에 답해야했습니다. Leanpub에서 사용 가능한 모든 Laravel 책과 수많은 인터넷 검색을 읽은 후 다음 구조를 생각해 냈습니다.

  1. 데이터 가능한 테이블 당 하나의 Eloquent 모델 클래스
  2. Eloquent 모델 당 하나의 리포지토리 클래스
  3. 여러 저장소 클래스간에 통신 할 수있는 서비스 클래스입니다.

그래서 제가 영화 데이터베이스를 만들고 있다고 가정 해 봅시다. 최소한 다음과 같은 Eloquent Model 클래스가 있습니다.

  • 영화
  • 사진관
  • 감독
  • 배우
  • 리뷰

저장소 클래스는 각 Eloquent Model 클래스를 캡슐화하고 데이터베이스에서 CRUD 작업을 담당합니다. 저장소 클래스는 다음과 같습니다.

  • MovieRepository
  • StudioRepository
  • DirectorRepository
  • ActorRepository
  • ReviewRepository

각 저장소 클래스는 다음 인터페이스를 구현하는 BaseRepository 클래스를 확장합니다.

interface BaseRepositoryInterface
{
    public function errors();

    public function all(array $related = null);

    public function get($id, array $related = null);

    public function getWhere($column, $value, array $related = null);

    public function getRecent($limit, array $related = null);

    public function create(array $data);

    public function update(array $data);

    public function delete($id);

    public function deleteWhere($column, $value);
}

서비스 클래스는 여러 저장소를 함께 연결하는 데 사용되며 응용 프로그램의 실제 “비즈니스 논리”를 포함합니다. 컨트롤러 는 만들기, 업데이트 및 삭제 작업을 위해 서비스 클래스와 통신합니다.

따라서 데이터베이스에 새 Movie 레코드를 만들려면 MovieController 클래스에 다음 메서드가있을 수 있습니다.

public function __construct(MovieRepositoryInterface $movieRepository, MovieServiceInterface $movieService)
{
    $this->movieRepository = $movieRepository;
    $this->movieService = $movieService;
}

public function postCreate()
{
    if( ! $this->movieService->create(Input::all()))
    {
        return Redirect::back()->withErrors($this->movieService->errors())->withInput();
    }

    // New movie was saved successfully. Do whatever you need to do here.
}

컨트롤러에 데이터를 POST하는 방법은 사용자가 결정하지만 postCreate () 메서드에서 Input :: all ()이 반환 한 데이터가 다음과 같다고 가정 해 보겠습니다.

$data = array(
    'movie' => array(
        'title'    => 'Iron Eagle',
        'year'     => '1986',
        'synopsis' => 'When Doug\'s father, an Air Force Pilot, is shot down by MiGs belonging to a radical Middle Eastern state, no one seems able to get him out. Doug finds Chappy, an Air Force Colonel who is intrigued by the idea of sending in two fighters piloted by himself and Doug to rescue Doug\'s father after bombing the MiG base.'
    ),
    'actors' => array(
        0 => 'Louis Gossett Jr.',
        1 => 'Jason Gedrick',
        2 => 'Larry B. Scott'
    ),
    'director' => 'Sidney J. Furie',
    'studio' => 'TriStar Pictures'
)

MovieRepository는 데이터베이스에서 Actor, Director 또는 Studio 레코드를 생성하는 방법을 몰라 야하므로 MovieService 클래스를 사용할 것입니다. 다음과 같은 모습 일 수 있습니다.

public function __construct(MovieRepositoryInterface $movieRepository, ActorRepositoryInterface $actorRepository, DirectorRepositoryInterface $directorRepository, StudioRepositoryInterface $studioRepository)
{
    $this->movieRepository = $movieRepository;
    $this->actorRepository = $actorRepository;
    $this->directorRepository = $directorRepository;
    $this->studioRepository = $studioRepository;
}

public function create(array $input)
{
    $movieData    = $input['movie'];
    $actorsData   = $input['actors'];
    $directorData = $input['director'];
    $studioData   = $input['studio'];

    // In a more complete example you would probably want to implement database transactions and perform input validation using the Laravel Validator class here.

    // Create the new movie record
    $movie = $this->movieRepository->create($movieData);

    // Create the new actor records and associate them with the movie record
    foreach($actors as $actor)
    {
        $actorModel = $this->actorRepository->create($actor);
        $movie->actors()->save($actorModel);
    }

    // Create the director record and associate it with the movie record
    $director = $this->directorRepository->create($directorData);
    $director->movies()->associate($movie);

    // Create the studio record and associate it with the movie record
    $studio = $this->studioRepository->create($studioData);
    $studio->movies()->associate($movie);

    // Assume everything worked. In the real world you'll need to implement checks.
    return true;
}

그래서 우리가 남긴 것은 멋지고 현명한 관심사 분리입니다. 리포지토리는 데이터베이스에서 삽입하고 검색하는 Eloquent 모델 만 인식합니다. 컨트롤러는 리포지토리에 신경 쓰지 않고 사용자로부터 수집 한 데이터를 전달하여 적절한 서비스에 전달합니다. 서비스는 수신 한 데이터가 데이터베이스에 저장되는 방식을 신경 쓰지 않고 컨트롤러가 제공 한 관련 데이터를 적절한 저장소로 넘깁니다.


답변

나는 그것을 “옳고 그름”보다는 내 코드가하는 일과 그에 대한 책임이라는 관점에서 생각하고 싶다. 이것이 제가 책임을 분리하는 방법입니다.

  • 컨트롤러는 HTTP 계층이며 기본 API를 통해 요청을 라우팅합니다 (즉, 흐름을 제어 함).
  • 모델은 데이터베이스 스키마를 나타내며, 데이터가 어떤 모양인지, 어떤 관계가 있는지, 그리고 필요할 수있는 모든 전역 속성 (예 : 연결된 성과 이름을 반환하기위한 이름 메서드)을 애플리케이션에 알려줍니다.
  • 리포지토리는 모델과의 더 복잡한 쿼리 및 상호 작용을 나타냅니다 (모델 메서드에 대한 쿼리는 수행하지 않음).
  • 검색 엔진-복잡한 검색 쿼리를 작성하는 데 도움이되는 클래스입니다.

이 점을 염두에두고 저장소를 사용할 때마다 의미가 있습니다 (interface.etc를 만드는지 여부는 완전히 다른 주제입니다). 이 접근 방식이 마음에 듭니다. 특정 작업을해야 할 때 어디로 가야하는지 정확히 알 수 있기 때문입니다.

또한 기본 저장소, 일반적으로 기본 기본값 (기본적으로 CRUD 작업)을 정의하는 추상 클래스를 빌드하는 경향이 있습니다. 그런 다음 각 자식은 필요에 따라 메서드를 확장하고 추가하거나 기본값을 오버로드 할 수 있습니다. 모델을 삽입하면이 패턴이 매우 강력 해집니다.


답변

리포지토리를 ORM뿐만 아니라 데이터의 일관된 파일 캐비넷으로 생각하십시오. 아이디어는 일관되고 사용하기 쉬운 API로 데이터를 수집하려는 것입니다.

Model :: all (), Model :: find (), Model :: create () 만 수행하는 경우 리포지토리를 추상화해도 많은 이점을 얻지 못할 것입니다. 반면에 쿼리 나 작업에 좀 더 많은 비즈니스 로직을 수행하려면 데이터를 처리하기 위해 API를 더 쉽게 사용할 수 있도록 저장소를 만들 수 있습니다.

관련 모델을 연결하는 데 필요한 더 자세한 구문을 처리하는 가장 좋은 방법이 리포지토리인지 묻는 것 같습니다. 상황에 따라 몇 가지 조치를 취할 수 있습니다.

  1. 상위 모델의 오프 새 자식 모델을 매달려 (일대일 또는 한 많은), 내가 좋아하는 아이 저장소 뭔가에 메서드를 추가 createWithParent($attributes, $parentModelInstance)하고이 단지를 추가 $parentModelInstance->idparent_id속성과 전화 생성의 필드.

  2. 다 대다 관계를 연결하여 실제로 $ instance-> attachChild ($ childInstance)를 실행할 수 있도록 모델에 함수를 만듭니다. 이를 위해서는 양쪽에 기존 요소가 필요합니다.

  3. 한 번의 실행으로 관련 모델을 생성하고 게이트웨이라고하는 무언가를 생성합니다 (Fowler의 정의에서 약간 벗어난 것일 수 있음). 변경 될 수 있거나 컨트롤러 또는 명령에있는 논리를 복잡하게 만드는 논리 무리 대신 $ gateway-> createParentAndChild ($ parentAttributes, $ childAttributes)를 호출 할 수있는 방법입니다.


답변