Χρήσιμα αποθετήρια με το Eloquent;

Την περασμένη εβδομάδα έγραψα άρθρο σχετικά με την αχρηστία του προτύπου Repository για 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::save
  • PostRepository::getById, χειραγώγηση και κλήση οντοτήτων PostRepository::save
  • την πρόκληση PostRepository::delete

Οι λειτουργίες εγγραφής δεν χρησιμοποιούν μεθόδους ανάκτησης. Στις λειτουργίες ανάγνωσης, χρησιμοποιούνται μόνο μέθοδοι get*. Αν διαβάσετε για Αρχή διαχωρισμού διεπαφής (γράμμα I в SOLID), τότε θα γίνει σαφές ότι η διεπαφή μας είναι πολύ μεγάλη και εκτελεί τουλάχιστον δύο διαφορετικές αρμοδιότητες. Ήρθε η ώρα να το διαιρέσουμε στα δύο. Μέθοδος getById είναι απαραίτητο και στα δύο, αλλά καθώς η εφαρμογή γίνεται πιο περίπλοκη, οι υλοποιήσεις της θα διαφέρουν. Αυτό θα το δούμε λίγο αργότερα. Έγραψα για την αχρηστία του μέρους εγγραφής σε προηγούμενο άρθρο, οπότε σε αυτό θα το ξεχάσω απλώς.

Το μέρος Read μου φαίνεται όχι και τόσο άχρηστο, αφού ακόμα και για το Eloquent μπορεί να υπάρχουν αρκετές υλοποιήσεις εδώ. Πώς να ονομάσετε την τάξη; Μπορώ ReadPostRepository, αλλά στο πρότυπο ΑΠΟΘΕΤΗΡΙΟ έχει ήδη μικρή σημασία. Μπορείτε απλά PostQueries:

<?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 απλά ζητώντας κάποια εφαρμογή Ερωτήματα αναρτήσεων και συνεργάζεται με αυτό. Στον πάροχο που συνδέσαμε PostQueries με τάξη 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->remember ελέγχει εάν αυτή η καταχώρηση βρίσκεται στην κρυφή μνήμη και εάν όχι, τότε καλεί επανάκληση και γράφει την τιμή που επιστρέφεται στη μνήμη cache. Το μόνο που μένει είναι να εφαρμοστεί αυτή η κλάση στην εφαρμογή. Χρειαζόμαστε όλες τις κλάσεις που στην εφαρμογή να ζητούν υλοποίηση της διεπαφής PostQueries άρχισε να λαμβάνει ένα παράδειγμα της τάξης 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) προτείνει τον πλήρη διαχωρισμό των λειτουργιών ανάγνωσης και εγγραφής σε επίπεδο διεπαφής. Ήρθα σε αυτόν μέσω Αρχή διαχωρισμού διεπαφής, που υποδηλώνει ότι χειρίζομαι επιδέξια μοτίβα και αρχές και αντλώ το ένα από το άλλο ως θεώρημα :) Φυσικά, δεν χρειάζεται κάθε έργο μια τέτοια αφαίρεση για την επιλογή οντοτήτων, αλλά θα μοιραστώ μαζί σας το κόλπο.Στο αρχικό στάδιο της εφαρμογής ανάπτυξη, μπορείτε απλά να δημιουργήσετε μια τάξη PostQueries με τη συνήθη εφαρμογή μέσω του Eloquent:

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

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

Όταν προκύψει η ανάγκη για προσωρινή αποθήκευση, με μια απλή κίνηση μπορείτε να δημιουργήσετε μια διεπαφή (ή αφηρημένη κλάση) στη θέση αυτής της κλάσης PostQueries, αντιγράψτε την υλοποίησή του στην τάξη EloquentPostQueries και πηγαίνετε στο σχήμα που περιέγραψα προηγουμένως. Ο υπόλοιπος κωδικός εφαρμογής δεν χρειάζεται να αλλάξει.

Όλα αυτά τα κόλπα με κλάσεις, διεπαφές, Ενεση εξάρτησης и CQRS περιγράφεται αναλυτικά στο το βιβλίο μου «Αρχιτεκτονική σύνθετων διαδικτυακών εφαρμογών». Υπάρχει επίσης μια λύση στο αίνιγμα γιατί όλες οι τάξεις μου στα παραδείγματα αυτού του άρθρου επισημαίνονται ως τελικές.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο