مستودعات مفيدة مع Eloquent؟

الاسبوع الماضي كتبت مقالة عن عدم جدوى قالب المستودع لكيانات Eloquentومع ذلك، فقد وعدني بأن يخبرني بكيفية استخدامه جزئيًا لصالحه. للقيام بذلك، سأحاول تحليل كيفية استخدام هذا القالب عادة في المشاريع. الحد الأدنى المطلوب لمجموعة الأساليب للمستودع:

<?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، لكن تحميل فئات الكيانات بمسؤولية جلب نفسها بشكل زائد ليس هو الفكرة الأفضل، ويبدو نقل هذه المسؤولية إلى فئات المستودع منطقيًا. هو كذلك؟ لقد قمت على وجه التحديد بتقسيم هذه الواجهة بصريًا إلى قسمين. سيتم استخدام الجزء الأول من الأساليب في عمليات الكتابة.

عملية الكتابة القياسية هي:

  • بناء كائن جديد والتحدي PostRepository::حفظ
  • PostRepository::getByIdوالتلاعب بالكيان والاستدعاء PostRepository::حفظ
  • دعوة PostRepository::حذف

لا تستخدم عمليات الكتابة طرق الجلب. في عمليات القراءة، يتم استخدام أساليب get* فقط. إذا قرأت عن مبدأ فصل الواجهة (خطاب I в SOLID)، فسيصبح من الواضح أن واجهتنا كبيرة جدًا وتؤدي على الأقل مسؤوليتين مختلفتين. حان الوقت لتقسيمها على اثنين. طريقة getById يعد ضروريًا في كليهما، ولكن عندما يصبح التطبيق أكثر تعقيدًا، ستختلف تطبيقاته. سنرى هذا بعد قليل. لقد كتبت عن عدم جدوى جزء الكتابة في مقال سابق، لذلك في هذا المقال سوف أنساه ببساطة.

يبدو لي أن جزء القراءة ليس عديم الفائدة، لأنه حتى بالنسبة لـ Eloquent قد يكون هناك العديد من التطبيقات هنا. ماذا اسم الفصل؟ يستطيع ReadPostRepositoryولكن إلى القالب مستودع لديه بالفعل أهمية قليلة. يمكنك فقط استعلامات لاحقة:

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

يجب أن تكون الواجهة مرتبطة بالتنفيذ، على سبيل المثال في 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 فقط أطلب بعض التنفيذ استعلامات المشاركات ويعمل بها. في المزود الذي ربطناه استعلامات لاحقة مع الطبقة EloquentPostQueries وسيتم استبدال هذه الفئة في وحدة التحكم.

لنتخيل أن تطبيقنا أصبح شائعًا جدًا. يفتح آلاف المستخدمين في الدقيقة صفحة تحتوي على أحدث المنشورات. تتم أيضًا قراءة المنشورات الأكثر شعبية كثيرًا. لا تتعامل قواعد البيانات مع هذه الأحمال بشكل جيد، لذا فهي تستخدم حلاً قياسيًا - ذاكرة التخزين المؤقت. بالإضافة إلى قاعدة البيانات، يتم تخزين لقطة بيانات معينة في وحدة تخزين مُحسّنة لعمليات معينة - أعطها أو رديس.

عادةً لا يكون منطق التخزين المؤقت معقدًا إلى هذا الحد، لكن تنفيذه في 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 ينفذ التخزين المؤقت فقط. $هذا->ذاكرة التخزين المؤقت->تذكر يتحقق مما إذا كان هذا الإدخال موجودًا في ذاكرة التخزين المؤقت، وإذا لم يكن كذلك، فإنه يستدعي رد الاتصال ويكتب القيمة التي تم إرجاعها إلى ذاكرة التخزين المؤقت. كل ما تبقى هو تطبيق هذه الفئة في التطبيق. نحتاج إلى جميع الفئات الموجودة في التطبيق أن تطلب تنفيذ الواجهة استعلامات لاحقة بدأت في تلقي مثيل للفئة 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) يقترح فصل عمليات القراءة والكتابة تمامًا على مستوى الواجهة. جئت إليه من خلال مبدأ فصل الواجهةمما يوحي بأنني أتعامل بمهارة مع الأنماط والمبادئ واشتقاق أحدهما من الآخر كنظرية :) بالطبع، ليس كل مشروع يحتاج إلى مثل هذا التجريد لاختيار الكيانات، لكنني سأشاركك الحيلة في المرحلة الأولى من التطبيق التطوير، يمكنك ببساطة إنشاء فصل دراسي استعلامات لاحقة مع التنفيذ المعتاد عبر Eloquent:

<?php
final class PostQueries
{
    public function getById($id): Post
    {
        return Post::findOrFail($id);
    }

    // другие методы
}

عندما تنشأ الحاجة إلى التخزين المؤقت، بحركة بسيطة يمكنك إنشاء واجهة (أو فئة مجردة) بدلاً من هذه الفئة استعلامات لاحقة، انسخ تنفيذه إلى الفصل EloquentPostQueries وانتقل إلى المخطط الذي وصفته سابقًا. لا يلزم تغيير باقي كود التطبيق.

كل هذه الحيل مع الطبقات، والواجهات، حقن التبعية и CQRS موصوفة بالتفصيل في كتابي "هندسة تطبيقات الويب المعقدة". يوجد أيضًا حل للغز لماذا تم وضع علامة نهائية على جميع فصولي في أمثلة هذه المقالة.

المصدر: www.habr.com

إضافة تعليق