Անցյալ շաբաթ ես գրեցի
<?php
interface PostRepository
{
public function getById($id): Post;
public function save(Post $post);
public function delete($id);
}
Այնուամենայնիվ, իրական նախագծերում, եթե որոշվել է օգտագործել շտեմարաններ, ապա դրանցում հաճախ ավելացվում են գրառումների առբերման մեթոդներ.
<?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);
}
Այս մեթոդները կարող են իրականացվել Eloquent scopes-ի միջոցով, սակայն էության դասերի ծանրաբեռնվածությունը՝ իրենց բերելու պատասխանատվությամբ, լավագույն գաղափարը չէ, և այս պատասխանատվությունը պահեստային դասեր տեղափոխելը տրամաբանական է թվում: Այդպե՞ս է։ Ես հատուկ տեսողականորեն բաժանեցի այս ինտերֆեյսը երկու մասի: Մեթոդների առաջին մասը կօգտագործվի գրելու գործողություններում:
Ստանդարտ գրելու գործողությունը հետևյալն է.
- նոր օբյեկտի կառուցում և մարտահրավեր PostRepository::save
- PostRepository::getById, սուբյեկտի շահարկում և կանչում PostRepository::save
- մարտահրավերը PostRepository::ջնջել
Գրելու գործողությունները չեն օգտագործում առբերման մեթոդներ: Ընթերցման գործողություններում օգտագործվում են միայն get* մեթոդները: Եթե դուք կարդաք մասին Ինտերֆեյսի տարանջատման սկզբունքը (նամակ I в SOLID), ապա պարզ կդառնա, որ մեր ինտերֆեյսը չափազանց մեծ է և կատարում է առնվազն երկու տարբեր պարտականություններ։ Ժամանակն է բաժանել այն երկուսի: Մեթոդ getById անհրաժեշտ է երկուսում էլ, բայց քանի որ հավելվածը դառնում է ավելի բարդ, դրա իրականացումները կտարբերվեն: Սա կտեսնենք մի փոքր ուշ։ Գրելու մասի անօգուտության մասին գրել էի նախորդ հոդվածում, ուստի այս մեկում ես պարզապես կմոռանամ դրա մասին:
«Կարդալ» հատվածն ինձ այնքան էլ անօգուտ չի թվում, քանի որ նույնիսկ Eloquent-ի համար այստեղ կարող են լինել մի քանի իրականացումներ։ Ի՞նչ անվանել դասարանը: Կարող է ReadPostRepository, բայց կաղապարին Պահոց նա արդեն իսկ քիչ արդիականություն ունի: Դուք կարող եք պարզապես PostQuery:
<?php
interface PostQueries
{
public function getById($id): Post;
public function getLastPosts();
public function getTopPosts();
public function getUserPosts($userId);
}
Eloquent-ով այն իրականացնելը բավականին պարզ է.
<?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();
}
}
Ինտերֆեյսը պետք է կապված լինի իրականացման հետ, օրինակ՝ in AppServiceProvider:
<?php
final class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(PostQueries::class,
EloquentPostQueries::class);
}
}
Այս դասն արդեն օգտակար է։ Նա գիտակցում է իր պատասխանատվությունը՝ ազատելով կա՛մ վերահսկիչներին, կա՛մ կազմակերպության դասին: Կարգավորիչում այն կարող է օգտագործվել այսպես.
<?php
final class PostsController extends Controller
{
public function lastPosts(PostQueries $postQueries)
{
return view('posts.last', [
'posts' => $postQueries->getLastPosts(),
]);
}
}
մեթոդ PostsController::lastPosts պարզապես որոշակի իրականացում խնդրելով Գրառումների հարցումներ և աշխատում է դրա հետ: Մեր կապած մատակարարում PostQuery դասի հետ EloquentPostQueries և այս դասը կփոխարինվի վերահսկիչի մեջ:
Պատկերացնենք, որ մեր հավելվածը շատ տարածված է դարձել։ Րոպեում հազարավոր օգտատերեր բացում են էջը վերջին հրապարակումներով: Ամենատարածված հրապարակումները նույնպես շատ հաճախ են կարդում։ Տվյալների բազաները այնքան էլ լավ չեն կառավարում նման բեռները, ուստի օգտագործում են ստանդարտ լուծում՝ քեշ: Տվյալների բազայից բացի, տվյալների որոշակի նկարը պահվում է որոշակի գործողությունների համար օպտիմիզացված պահեստում. հուշում կամ redis.
Քեշավորման տրամաբանությունը սովորաբար այնքան էլ բարդ չէ, բայց այն կիրառելը EloquentPostQueries-ում այնքան էլ ճիշտ չէ (եթե միայն այն պատճառով, որ Միայնակ պատասխանատվության սկզբունք) Կաղապար օգտագործելը շատ ավելի բնական է դեկորատոր և իրականացնել քեշավորումը որպես հիմնական գործողության ձևավորում.
<?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();
});
}
// другие методы практически такие же
}
Անտեսեք ինտերֆեյսը Պահոց կոնստրուկտորում։ Անհայտ պատճառով նրանք որոշել են անվանել Laravel-ում քեշավորման ինտերֆեյսը այսպես.
Դաս CachedPostQueries իրականացնում է միայն քեշավորումը: $this->cache->հիշիր ստուգում է, թե արդյոք այս գրառումը գտնվում է քեշում, և եթե ոչ, ապա զանգում է հետադարձ զանգ և գրում վերադարձված արժեքը քեշում: Մնում է միայն այս դասը ներդնել հավելվածում: Մեզ անհրաժեշտ են բոլոր դասերը, որոնք հավելվածում պահանջում են ինտերֆեյսի իրականացում PostQuery սկսեցին ստանալ դասի օրինակ CachedPostQueries. Այնուամենայնիվ, նա ինքը CachedPostQueries կոնստրուկտորը պետք է ստանա դաս՝ որպես պարամետր EloquentPostQueriesքանի որ այն չի կարող աշխատել առանց «իրական» իրականացման։ Մենք փոխվում ենք AppServiceProvider:
<?php
final class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(PostQueries::class,
CachedPostQueries::class);
$this->app->when(CachedPostQueries::class)
->needs(PostQueries::class)
->give(EloquentPostQueries::class);
}
}
Իմ բոլոր ցանկությունները միանգամայն բնական կերպով նկարագրված են մատակարարում: Այսպիսով, մենք իրականացրել ենք քեշավորում մեր հարցումների համար՝ գրելով միայն մեկ դաս և փոխելով կոնտեյների կոնֆիգուրացիան։ Դիմումի ծածկագրի մնացած մասը չի փոխվել:
Իհարկե, քեշավորումն ամբողջությամբ իրականացնելու համար անհրաժեշտ է իրականացնել նաև անվավերացում, որպեսզի ջնջված հոդվածը որոշ ժամանակով չկախվի կայքում, այլ անմիջապես ջնջվի։ Բայց սրանք չնչին բաներ են։
Ներքևի գիծ. մենք օգտագործել ենք ոչ թե մեկ, այլ երկու ձևանմուշ: Նմուշ Հրամանի հարցումների պատասխանատվության տարանջատում (CQRS) առաջարկում է ամբողջությամբ առանձնացնել կարդալու և գրելու գործողությունները ինտերֆեյսի մակարդակում: Ես նրա մոտ եմ եկել Ինտերֆեյսի տարանջատման սկզբունքը, ինչը հուշում է, որ ես հմտորեն շահարկում եմ օրինաչափություններն ու սկզբունքները և մեկը մյուսից բխում եմ որպես թեորեմ :) Իհարկե, ոչ բոլոր նախագծին պետք է նման վերացականություն սուբյեկտներ ընտրելու համար, բայց ես ձեզ հետ կկիսվեմ հնարքով: Կիրառման սկզբնական փուլում զարգացում, դուք կարող եք պարզապես ստեղծել դաս PostQuery Eloquent-ի միջոցով սովորական իրականացմամբ.
<?php
final class PostQueries
{
public function getById($id): Post
{
return Post::findOrFail($id);
}
// другие методы
}
Երբ քեշավորման անհրաժեշտություն է առաջանում, մի պարզ քայլով այս դասի փոխարեն կարող եք ստեղծել ինտերֆեյս (կամ վերացական դաս): PostQuery, պատճենեք դրա իրականացումը դասարանում EloquentPostQueries և անցեք ավելի վաղ նկարագրածս սխեմային: Դիմումի ծածկագրի մնացած մասը փոփոխության կարիք չունի:
Այս բոլոր հնարքները դասերի, ինտերֆեյսերի հետ, Կախվածության ներարկում и CQRS մանրամասն նկարագրված է
Source: www.habr.com