Zarządzanie relacjami w Laravel, przestrzeganie wzorca repozytorium

Podczas tworzenia aplikacji w Laravel 4 Po przeczytaniu książki T. Otwella o dobrych wzorcach projektowych w Laravel znalazłem się w tworzeniu repozytoriów dla każdej tabeli w aplikacji.

Skończyłem z następującą strukturą tabeli:

  • uczniowie: id, Nazwa
  • kursy: id, name, teacher_id
  • nauczyciele: id, Nazwa
  • zadania: id, name, course_id
  • wyniki (działa jako pivot między uczniami i zadaniami): student_id, assignment_id, wyniki

Mam klasy repozytorium z metodami find, create, update I delete dla wszystkich tych tabel. Każde repozytorium ma wymowny model, który współdziała z bazą danych. Relacje są zdefiniowane w modelu dla dokumentacji Laravela: http://laravel.com/docs/eloquent#relationships .

Podczas tworzenia nowego kursu, jedyne co robię to wywołanie metody create w repozytorium kursu. Ten kurs ma zadania, więc tworząc jeden, chcę również stworzyć wpis w tabeli wyników dla każdego uczestnika kursu. Robię to przez repozytorium Zadań. Oznacza to, że repozytorium zadań komunikuje się z dwoma wymownymi modelami, z Modelem zadań i studentów.

Moje pytanie brzmi: ponieważ ta aplikacja prawdopodobnie wzrośnie i zostaną wprowadzone kolejne relacje, czy jest to dobra praktyka, aby komunikować się z różnymi Elokwentnymi modelami w repozytoriach, czy też należy to zrobić za pomocą innych repozytoriów (mam na myśli wywoływanie innych repozytoria z repozytorium zadań) czy powinno się to robić w wymownych modelach razem?

Również, jest to dobra praktyka, aby używać tabeli wyników jako osi odniesienia między zadaniami i uczniów, czy należy to zrobić gdzie indziej?

Author: Antonio Carlos Ribeiro, 2013-09-16

4 answers

Pamiętaj, że pytasz o opinie: d

Oto moje:

TL;DR: tak, w porządku.

Dobrze ci idzie!

Robię dokładnie to, co robisz często i uważam, że działa świetnie.

Często jednak organizuję repozytoria wokół logiki biznesowej, zamiast mieć repo-per-table. Jest to przydatne, ponieważ jest to punkt widzenia skoncentrowany wokół tego, jak aplikacja powinna rozwiązać twój "problem biznesowy".

Kurs jest "encją", z atrybutami (tytuł, id, etc), a nawet inne encje (przypisania, które mają swoje atrybuty i ewentualnie encje).

Twoje repozytorium "kursu" powinno być w stanie zwrócić kurs i jego atrybuty/zadania (w tym przypisanie).

Możesz to osiągnąć elokwentnie, na szczęście.

(często kończę z repozytorium na tabelę, ale niektóre repozytoria są używane znacznie częściej niż inne, a więc mają o wiele więcej metod. Twoje repozytorium "kursów" może być znacznie bardziej funkcjonalne niż Twoje repozytorium zadań, na przykład, jeśli Twoja aplikacja koncentruje się bardziej wokół kursów, a mniej na kolekcji Zadań kursów).

The tricky part

Często używam repozytoriów wewnątrz moich repozytoriów do wykonywania pewnych akcji bazodanowych.

Każde repozytorium, które implementuje elokwentne w celu obsługi danych, prawdopodobnie zwróci elokwentne modele. W tym świetle dobrze jest, jeśli model kursu wykorzystuje wbudowane relacje w celu pobierania lub zapisywania Zadań (lub inny przypadek użycia). Nasza" realizacja " zbudowana jest wokół elokwentnego.

Z praktycznego punktu widzenia ma to sens. Jest mało prawdopodobne, aby zmienić źródła danych na coś wymownego nie może obsłużyć(do źródła danych spoza sql).

ORMS

Najtrudniejszą częścią tej konfiguracji, przynajmniej dla mnie, jest określenie, czy Elokwent rzeczywiście pomaga lub szkodzi nam. Ormy są trudnym tematem, ponieważ chociaż pomagają nam bardzo z praktycznego punktu widzenia, łączą również Twój " biznes encje logiczne " kod z kodem wykonującym pobieranie danych.

Ten rodzaj zamętu zastanawia się, czy repozytorium jest rzeczywiście odpowiedzialne za obsługę danych lub obsługę pobierania / aktualizacji podmiotów (podmiotów z domeny biznesowej).

Ponadto działają one jako obiekty, które przekazujesz swoim poglądom. Jeśli później będziesz musiał zrezygnować z używania elokwentnych modeli w repozytorium, musisz upewnić się, że zmienne przekazywane do Twoich widoków zachowują się w ten sam sposób lub mają takie same dostępne metody, w przeciwnym razie zmiana źródeł danych zmieni Twoje widoki, a Ty (częściowo) straciłeś cel abstrakcji logiki do repozytoriów w pierwszej kolejności - konserwowalność twojego projektu spada.

W każdym razie, są to nieco niekompletne myśli. Są one, jak stwierdzono, tylko moją opinią, która jest wynikiem czytania Domain Driven Design i oglądania filmów takich jak "uncle bob 's" keynote W Ruby Midwest w ciągu ostatniego roku.

 67
Author: fideloper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-16 13:30:49

Kończę duży projekt za pomocą Laravel 4 i musiałem odpowiedzieć na wszystkie pytania, które teraz zadajesz. Po przeczytaniu wszystkich dostępnych książek Laravel w Leanpub i mnóstwie googlowania, wymyśliłem następującą strukturę.

  1. jedna wymowna Klasa modelu w tabeli datowalnej
  2. jedna klasa repozytorium na wymowny Model
  3. klasa usług, która może komunikować się między wieloma klasami repozytorium.
Powiedzmy, że buduję film. baza danych. Chciałbym mieć co najmniej następujące wymowne klasy modelu:
  • Film
  • Studio
  • dyrektor
  • aktor
  • Recenzja

Klasa repozytorium hermetyzowałaby każdą wymowną klasę modelu i odpowiadałaby za operacje CRUD w bazie danych. Klasy repozytorium mogą wyglądać następująco to:

  • MovieRepository
  • StudioRepository
  • DirectorRepository
  • ActorRepository
  • ReviewRepository

Każda klasa repozytorium rozszerzyłaby klasę BaseRepository, która implementuje następujący interfejs:

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);
}

Klasa usług jest używana do klejenia wielu repozytoriów razem i zawiera prawdziwą "logikę biznesową" aplikacji. Kontrolery tylko komunikują się z klasami usług w celu utworzenia, aktualizacji i usunięcia działania.

Więc kiedy chcę utworzyć nowy rekord Filmowy w bazie danych, moja klasa MovieController może mieć następujące metody:

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.
}
To od Ciebie zależy, w jaki sposób przesyłasz dane do kontrolerów, ale powiedzmy, że dane zwracane przez Input::all() w metodzie postCreate() wyglądają mniej więcej tak:
$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'
)

Ponieważ Moviepository nie powinny wiedzieć, jak tworzyć nagrania aktorskie, reżyserskie lub studyjne w bazie danych, użyjemy naszej klasy MovieService, która może zobacz coś podobnego:

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;
}
Więc to, co nam zostało, to miły, rozsądny rozdział trosk. Repozytoria są świadome tylko wymownego modelu, który wstawiają i pobierają z bazy danych. Administratorzy nie dbają o repozytoria, po prostu przekazują dane, które zbierają od użytkownika i przekazują je do odpowiedniego serwisu. Usługa nie obchodzi jak otrzymane dane są zapisywane w bazie danych, po prostu przekazuje odpowiednie dane podane przez kontrolera do odpowiednich repozytoriów.
 200
Author: Kyle Noland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-10 20:35:36

Lubię myśleć o tym w kategoriach tego, co robi mój kod i za co jest odpowiedzialny, a nie "dobrze czy źle". Tak rozbijam swoje obowiązki:

    W przeciwieństwie do innych interfejsów API, procesory te nie są w stanie obsługiwać wszystkich interfejsów API.]}
  • modele reprezentują schemat bazy danych i mówią aplikacji, jak wyglądają dane, jakie mogą mieć relacje, a także wszelkie globalne atrybuty, które mogą być konieczne (takie jak nazwa metoda zwracania połączonego imienia i nazwiska)
  • repozytoria reprezentują bardziej złożone zapytania i interakcje z modelami (nie robię żadnych zapytań na metodach modelu).
  • Wyszukiwarki-klasy, które pomagają mi budować złożone zapytania.

Mając to na uwadze, sensowne jest używanie repozytorium za każdym razem (niezależnie od tego, czy tworzysz interfejsy.itd. to zupełnie inny temat). Podoba mi się to podejście, ponieważ oznacza to, że wiem dokładnie, gdzie iść, kiedy jestem muszę wykonać pewną pracę.

Mam też tendencję do budowania bazowego repozytorium, zazwyczaj abstrakcyjnej klasy, która definiuje główne wartości domyślne-zasadniczo operacje CRUD, a następnie każde dziecko może po prostu rozszerzać i dodawać metody w razie potrzeby lub przeciążać wartości domyślne. Wstrzykiwanie modelu pomaga również ten wzór być dość solidny.

 5
Author: Oddman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-16 14:00:18

Pomyśl o repozytoriach jako o spójnej szafce na dane (nie tylko ORM). Chodzi o to, że chcesz pobierać dane za pomocą spójnego, prostego w użyciu interfejsu API.

Jeśli znajdziesz się po prostu robiąc Model::all(), Model::find(), Model::create() prawdopodobnie nie będzie wiele korzyści z abstrakcji z dala repozytorium. Z drugiej strony, jeśli chcesz zrobić trochę więcej logiki biznesowej do zapytań lub działań, możesz chcieć utworzyć repozytorium, aby ułatwić w użyciu API do czynienia z data.

Myślę, że pytałeś, czy repozytorium byłoby najlepszym sposobem radzenia sobie z niektórymi bardziej zwięzłymi składniami wymaganymi do łączenia powiązanych modeli. W zależności od sytuacji, jest kilka rzeczy, które mogę zrobić:

  1. Zawieszając nowy model potomny z modelu rodzica (jeden-jeden lub jeden-wiele), dodałbym metodę do repozytorium potomnego coś w rodzaju createWithParent($attributes, $parentModelInstance), a to po prostu dodałoby $parentModelInstance->id do pola parent_id atrybutów i wywołanie twórz.

  2. Dołączając relację wiele-wiele, w rzeczywistości tworzę funkcje na modelach, aby móc uruchomić $instance- > attachChild($childInstance). Należy pamiętać, że wymaga to istniejących elementów po obu stronach.

  3. Tworząc powiązane modele w jednym uruchomieniu, tworzę coś, co nazywam bramką (może to być trochę odbiegające od definicji Fowlera). Sposób, w jaki mogę wywołać $gateway->createParentAndChild($parentAttributes, $childAttributes) zamiast trochę logiki, która może się zmienić albo to skomplikowałoby logikę, którą mam w kontrolerze lub komendzie.

 5
Author: Ryan Tablada,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-16 14:03:59