Vorige week schreef ik , maar ik beloofde te vertellen hoe je het gedeeltelijk nuttig kunt gebruiken. Daarom zal ik proberen te analyseren hoe dit patroon normaal gesproken in projecten wordt gebruikt. De minimaal vereiste set methoden voor een repository is:
<?php
interface PostRepository
{
public function getById($id): Post;
public function save(Post $post);
public function delete($id);
}Echter, in echte projecten, als er voor is gekozen om repositories te gebruiken, worden er vaak methoden toegevoegd voor het ophalen van records:
<?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);
}Deze methoden zouden via Eloquent scopes geĆÆmplementeerd kunnen worden, maar het verstoren van entiteitsklassen met verantwoordelijkheden voor het ophalen van zichzelf is niet de beste aanpak en het verplaatsen van deze verantwoordelijkheden naar repository-klassen lijkt logisch. Is dit zo? Ik heb deze interface bewust visueel in twee delen gesplitst. Het eerste deel van de methoden zal worden gebruikt voor schrijfoperaties.
Standaard schrijfoperaties zijn:
- het construeren van een nieuw object en het aanroepen van PostRepository::save
- PostRepository::getById, manipulaties met de entiteit en het aanroepen van PostRepository::save
- het aanroepen van PostRepository::delete
Bij schrijfoperaties worden er geen methoden voor ophalen gebruikt. Bij leesoperaties worden alleen de get*-methoden gebruikt. Als je leest over Interface Segregation Principle (de letter I in SOLID), dan wordt het duidelijk dat onze interface te groot is en minstens twee verschillende verantwoordelijkheden vervult. Het is tijd om deze op te splitsen. De methode getById is in beide nodig, maar naarmate de applicatie complexer wordt, zullen de implementaties verschillend zijn. Dit zullen we iets later zien. Over de nutloosheid van het schrijfdeel schreef ik in het vorige artikel, dus in dit artikel zal ik het gewoon vergeten.
Het leesdeel lijkt mij echter niet zo nutteloos, aangezien er zelfs voor Eloquent verschillende implementaties kunnen zijn. Hoe noem je de klasse? Je kunt het ReadPostRepository, maar dat heeft al weinig met het patroon te maken. Je kunt het gewoon Repository PostQueries <?php interface PostQueries { public function getById($id): Post; public function getLastPosts(); public function getTopPosts(); public function getUserPosts($userId); }:
De implementatie met Eloquent is vrij eenvoudig:De implementatie met Eloquent is vrij eenvoudig:
<?php
final class EloquentPostQueries implements PostQueries
{
public function getById($id): Post
{
return Post::findOrFail($id);
}
/**
* @return Post[] | Collection
* /
public function getLastPosts()
{
return Post::orderBy('created_at', 'desc')
->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();
}
}De interface moet worden gekoppeld aan de implementatie, bijvoorbeeld in AppServiceProvider:
<?php
final class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(PostQueries::class,
EloquentPostQueries::class);
}
}Deze klasse is al nuttig. Het vervult zijn verantwoordelijkheid door ofwel de controllers of de entiteitsklasse te ontlasten. In de controller kan het als volgt worden gebruikt:
<?php
final class PostsController extends Controller
{
public function lastPosts(PostQueries $postQueries)
{
return view('posts.last', [
'posts' => $postQueries->getLastPosts(),
]);
}
} Methode PostsController::lastPosts verlangt gewoon naar een implementatie PostsQueries en werkt daarmee. In de provider hebben we deze gekoppeld aan de klasse <?php interface PostQueries { public function getById($id): Post; public function getLastPosts(); public function getTopPosts(); public function getUserPosts($userId); } EloquentPostQueries en in de controller zal deze klasse worden ingevoegd. Laten we ons voorstellen dat onze applicatie erg populair is geworden. Duizenden gebruikers openen per minuut de pagina met de nieuwste berichten. De meest populaire berichten worden ook heel vaak gelezen. Databases kunnen deze belasting meestal niet goed aan, daarom gebruiken ze een standaardoplossing ā cache. Naast de database wordt er een soort snapshot van de gegevens opgeslagen in een opslag die geoptimaliseerd is voor bepaalde bewerkingen ā
memcached redis of Cachinglogica is meestal niet zo complex, maar het implementeren ervan in EloquentPostQueries is niet echt correct (tenminste vanwege de.
Single Responsibility Principle ). Het is veel natuurlijker om het decoratorpatroon te gebruikenen caching te realiseren als decoratie van de hoofdactie: <?php use IlluminateContractsCacheRepository;final class CachedPostQueries implements PostQueries { const LASTS_DURATION = 10;/** @var PostQueries * / private $base;/** @var Repository * / private $cache;public function __construct( PostQueries $base, Repository $cache) { $this->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(); }); }// andere methoden zijn praktisch hetzelfde } en implementeren caching als decoratie voor de belangrijkste actie:
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();
});
}
// andere methoden zijn vrijwel hetzelfde
}Negeer de interface Repository in de bouwer. Om onduidelijke redenen werd deze interface voor caching in Laravel zo genoemd.
Klasse CachedPostQueries implementeert alleen caching. $this->cache->remember controlleert of deze opname al in de cache staat en als dat niet het geval is, roept het de callback aan en slaat het de teruggegeven waarde in de cache op. We moeten deze klasse nu in de applicatie integreren. We hebben nodig dat alle klassen die in de applicatie om implementatie van de interface vragen <?php interface PostQueries { public function getById($id): Post; public function getLastPosts(); public function getTopPosts(); public function getUserPosts($userId); } een instantie van de klasse ontvangen. CachedPostQueriesEchter, zelf CachedPostQueries moet als parameter in de constructor de klasse ontvangen en in de controller zal deze klasse worden ingevoegd., aangezien hij niet kan functioneren zonder een 'echte' implementatie. We wijzigen AppServiceProvider:
app->bind(PostQueries::class,
CachedPostQueries::class);
$this->app->when(CachedPostQueries::class)
->needs(PostQueries::class)
->give(EloquentPostQueries::class);
}
}Al mijn wensen worden vrij natuurlijk beschreven in de provider. Zo hebben we caching voor onze queries gerealiseerd door slechts ƩƩn klasse te schrijven en de configuratie van de container aan te passen. De code van de rest van de applicatie is niet veranderd.
Natuurlijk, om caching volledig te implementeren, moet er ook invalidatie worden gerealiseerd, zodat een verwijderde artikel niet nog een tijd op de website blijft staan, maar onmiddellijk wordt verwijderd. Maar dat is al een detail.
Conclusie: we hebben niet ƩƩn, maar wel twee patronen gebruikt. Het patroon Command Query Responsibility Segregation (CQRS) biedt de mogelijkheid om lees- en schrijfoperaties op het niveau van interfaces volledig te scheiden. Ik kwam erachter via Interface Segregation Principle, wat aangeeft dat ik vaardig ben in het manipuleren van patronen en principes en het eruit halen van het een of het andere als een stelling š Natuurlijk, niet elk project heeft zo'n abstractie nodig voor entiteitsselecties, maar ik deel een truc met jullie. In de beginfase van de ontwikkeling van de applicatie kun je eenvoudig een klasse aanmaken <?php interface PostQueries { public function getById($id): Post; public function getLastPosts(); public function getTopPosts(); public function getUserPosts($userId); } met een normale implementatie via Eloquent:
<?php
final class PostQueries
{
public function getById($id): Post
{
return Post::findOrFail($id);
}
// andere methoden
}Wanneer de behoefte aan caching ontstaat, kan je eenvoudig een interface (of abstracte klasse) creƫren in plaats van deze klasse <?php interface PostQueries { public function getById($id): Post; public function getLastPosts(); public function getTopPosts(); public function getUserPosts($userId); }, de implementatie kopiƫren in de klasse en in de controller zal deze klasse worden ingevoegd. en overstappen naar het schema dat ik eerder beschreef. De rest van de applicatiecode hoeft niet te worden gewijzigd.
Al deze trucs met klassen, interfaces, Dependency Injection en CQRS worden in detail beschreven in . Daar ligt ook de oplossing van de raadsel waarom al mijn klassen in de voorbeelden van dit artikel als final zijn gemarkeerd.
Bron: habr.com
