Minulý týden jsem napsal , ale slíbil jsem, že povím, jak ji lze částečně využít prospěšně. Pokusím se zanalyzovat, jak se obvykle tato šablona používá v projektech. Minimální potřebná sada metod pro repositář:
<?php
interface PostRepository
{
public function getById($id): Post;
public function save(Post $post);
public function delete($id);
}Nicméně, v reálných projektech, pokud se rozhodlo repositáře použít, často se do nich přidávají metody pro výběr záznamů:
<?php
interface PostRepository
{
public function getById($id): Post;
public function save(Post $post);
public function delete($id);
public function getLastPosts();
public function getTopPosts();
public function getUserPosts($userId);
}Tyto metody by bylo možné implementovat pomocí Eloquent scopes, ale zatěžovat třídy entit povinnostmi vyhledávání samy sebe — není nejlepší nápad a přesunout tuto povinnost do tříd repositářů se zdá logické. Je to tak? Úmyslně jsem vizuálně rozdělil tento rozhraní na dvě části. První část metod bude použita v operacích zápisu.
Standardní operace zápisu je:
- konstruování nového objektu a volání PostRepository::save
- PostRepository::getById, manipulace s entitou a volání PostRepository::save
- volání PostRepository::delete
V operacích zápisu nejsou používány metody pro výběr. V operacích čtení jsou používány pouze metody get*. Pokud si přečtete o Principu segregace rozhraní (písmeno I v SOLID), pak pochopíte, že naše rozhraní se ukázalo jako příliš velké a vykonávající minimálně dvě různé povinnosti. Je čas ho rozdělit na dvě. Metoda getById je potřebná v obou, ale při složitosti aplikace budou její implementace různé. To uvidíme o chvíli později. O zbytečnosti write části jsem psal v minulém článku, proto na ni v tomto prostě zapomenu.
Část Read se mi však nezdá tak zbytečná, protože i pro Eloquent může existovat několik implementací. Jak pojmenovat třídu? Můžeme ji nazvat ReadPostRepository, ale k šabloně Repository už má málo co společného. Můžeme jednoduše PostQueries:
<?php
interface PostQueries
{
public function getById($id): Post;
public function getLastPosts();
public function getTopPosts();
public function getUserPosts($userId);
}Jeho implementace pomocí Eloquent je docela jednoduchá:
limit(/*some limit*/)
->get();
}
/**
* @return Post[] | Collection
*/
public function getTopPosts()
{
return Post::orderBy('rating', 'desc')
->limit(/*some limit*/)
->get();
}
/**
* @param int $userId
* @return Post[] | Collection
*/
public function getUserPosts($userId)
{
return Post::whereUserId($userId)
->orderBy('created_at', 'desc')
->get();
}
}Rozhraní by mělo být propojeno s implementací, například v AppServiceProvider:
app->bind(PostQueries::class,
EloquentPostQueries::class);
}
}Tato třída je již užitečná. Implementuje svou odpovědnost, což ulehčuje buď kontrolerům, nebo třídě entity. V kontroleru ji lze použít takto:
$postQueries->getLastPosts(),
]);
}
} Metoda PostsController::lastPosts jednoduše si žádá nějakou implementaci PostsQueries a pracuje s ní. V poskytovateli jsme ji propojili PostQueries s třídou EloquentPostQueries a v kontroleru bude vložena tato třída.
Představme si, že se naše aplikace stala velmi populární. Tisíce uživatelů za minutu otevírají stránku s posledními příspěvky. Nejoblíbenější příspěvky jsou také velmi často čteny. Databáze tyto zátěže nezvládají dobře, proto se používá standardní řešení — keš. Kromě databáze je nějaká podoba dat uchovávána ve skladu optimalizovaném pro určité operace — memcached nebo redis.
Logika kešování obvykle není tak složitá, ale implementovat ji v EloquentPostQueries není zrovna správné (alespoň kvůli principu jednoho odpovědnosti)). Mnohem přirozenější je použít vzor Dekorátor a implementovat kešování jako dekoraci hlavní funkce:
base = $base;
$this->cache = $cache;
}
/**
* @return Post[] | Collection
*/
public function getLastPosts()
{
return $this->cache->remember('last_posts',
self::LASTS_DURATION,
function(){
return $this->base->getLastPosts();
});
}
// další metody jsou prakticky stejné
}Nevšímejte si rozhraní Repository v konstruktoru. Z neznámého důvodu se tak rozhodli nazvat rozhraní pro cachování v Laravel.
Třída CachedPostQueries implementuje pouze cachování. $this->cache->remember kontroluje, zda daný záznam není v cache, a pokud není, vyvolá callback a uloží do cache vrácenou hodnotu. Zbývá jen implementovat tuto třídu do aplikace. Potřebujeme, aby všechny třídy, které v aplikaci žádají implementaci rozhraní PostQueries začaly dostávat instanci třídy CachedPostQueries. Samotný CachedPostQueries jako parametr v konstruktoru musí dostat třídu EloquentPostQueries, protože nemůže fungovat bez "skutečné" implementace. Měníme AppServiceProvider:
app->bind(PostQueries::class,
CachedPostQueries::class);
$this->app->when(CachedPostQueries::class)
->needs(PostQueries::class)
->give(EloquentPostQueries::class);
}
}Všechna má přání jsou poměrně přirozeně popsána v poskytovateli. Tímto jsme implementovali cachování našich dotazů pouze napsáním jedné třídy a změnou konfigurace kontejneru. Kód zbytku aplikace se nezměnil.
Samozřejmě, pro plnohodnotnou implementaci cachování je ještě nutné realizovat invalidaci, aby odstraněný článek na webu nezůstal ještě nějakou dobu, ale byl smazán hned. Ale to jsou už maličkosti.
Výsledek: použili jsme ne jeden, ale hned dva vzory. Vzor Command Query Responsibility Segregation (CQRS) navrhuje zcela oddělit operace čtení a zápisu na úrovni rozhraní. K tomuto závěru jsem došel prostřednictvím Principu segregace rozhraní, což naznačuje, že dovedně manipuluji s vzory a principy a odvozuji jeden z druhého jako teorém 🙂 Samozřejmě, ne každému projektu je taková abstrakce pro výběry entit nutná, ale podělím se s vámi o trik. V počáteční fázi vývoje aplikace můžete jednoduše vytvořit třídu PostQueries s běžnou implementací přes Eloquent:
<?php
final class PostQueries
{
public function getById($id): Post
{
return Post::findOrFail($id);
}
// další metody
}Když nastane potřeba cachování, lze jednoduše vytvořit rozhraní (nebo abstraktní třídu) místo této třídy PostQueries, její implementaci zkopírovat do třídy EloquentPostQueries a přejít na schéma, které jsem popsal dříve. Zbytek kódu aplikace není třeba měnit.
Všechny tyto triky s třídami, rozhraními, Závislostní injekce a CQRS jsou podrobně popsány v Zde je také řešení záhady, proč jsou všechny mé třídy v příkladech k tomuto článku označeny jako final.
Zdroj: habr.com
