Pattern e composizione del gateway

7

Scrivo spesso applicazioni intensive di database e ho scoperto il modello di gateway che sembrava adattarsi alle mie esigenze. Il mio problema ora è che molti dei miei modelli sono composti da altri modelli.

Ad esempio, ho un modello User e un modello Order ciascuno con un proprio Gateway .

Interfaccia per User Gateway:

interface IUserGateway {
  public function create(User $user) : User;
  public function query($limit = -1, $offset = 0) : Array;
  public function update(User $user) : User;
  public function delete(User $user) : boolean;
  public function findById(int $id) : User;
}

Interfaccia per Order Gateway:

interface IOrderGateway {
  public function create(Order $user) : Order;
  public function query($limit = -1, $offset = 0) : Array;
  public function update(Order $user) : Order;
  public function delete(Order $user) : boolean;
  public function findById(int $id) : Order;
}

Ora Order contiene un'istanza di User che viene quindi referenziata all'interno del mio database con un indice estero orders.user_id all'interno della tabella orders .

La domanda ora è come interrogare e creare gli oggetti all'interno dell'implementazione IOrderGateway .

Fino ad ora ho usato Dependency Injection per iniettare IUserGateway istanze nella constructor della mia implementazione IOrderGateway e quindi chiamare IUserGateway::findById per ottenere l'istanza User richiesta per ogni Order .

Ma questo mi sembra uno spreco di risorse perché devo fare una query aggiuntiva al database (MySQL in questo caso) che potrebbe anche essere eseguita da un INNER JOIN .

Ma poi IOrderGateway deve preoccuparsi della creazione di User s.

Ho anche fatto qualche ricerca in più e ho trovato implementazioni di Gateway Pattern che restituiscono solo i dati di riga non elaborati dal livello dati e poi li passiamo a Factory che crea l'oggetto.

Quindi quale metodo dovrei usare? Ci sono soluzioni migliori di quelle menzionate?

Quando non utilizzo un JOIN , ho solo il user_id per ogni ordine. Per ottenere l'istanza dell'utente devo chiamare IUserGateway::findById() per ottenere quell'istanza che causa una query aggiuntiva al database.

Il problema è che non so quale soluzione usare. Sono relativamente nuovo a OO-Concepts. Per me usare JOIN per interrogare e quindi istanziare Order e User sembra essere la soluzione migliore. Ma poi violerei i principi SOLID perché IOrderGateway si preoccupa di Order e User .

    
posta Code Spirit 28.11.2016 - 23:22
fonte

2 risposte

3

When I don't use a JOIN I only have the user_id for each order. To get the instance of the user I have to call IUserGateway::findById() to get that instance which causes an additional query to the database.

Tutto vero. Ecco come funziona CRUD .

Se vuoi il JOIN, alcuni ORM ti permettono di fare qualcosa del genere:

var result = db.ExecuteQuery<MyDataTransferObject>("[sql with join goes here]", parameters)

Dove MyDataTransferObject è una classe contenente proprietà i cui nomi corrispondono alle colonne che si desidera restituire dal proprio database.

    
risposta data 03.12.2016 - 16:50
fonte
1

Caricamento lento delle relazioni tra entità

Se stai scrivendo il tuo livello di accesso ai dati / ORM, ciò che stai veramente cercando è Caricamento lento delle relazioni delle entità . Questo non è realmente qualcosa che un Gateway gestisce in modo esplicito. Una classe "proxy" potrebbe eseguire questa operazione (che potrebbe richiedere un altro gateway come argomento del costruttore).

Innanzitutto, alcune classi di entità che hai già / assomigliano a ciò che hai:

class User
{
    public function __construct($username, $id = 0) {
        $this->username = $username;
        $this->id = $id;
    }

    private $id;
    private $username;

    public function getId() {
        return $this->id;
    }

    public function getUsername() {
        return $this->username;
    }
}


class Order
{
    private $user;

    public function setUser(User $user) {
        $this->user = $user;
    }
}

Il trucco con "proxy" in questo caso è che dovrebbero ereditare dall'entità reale. Se vuoi caricare pigro l'oggetto User , crea la classe UserProxy e eredita da User . Quindi UserProxy deve supportare gli stessi metodi pubblici che User fa:

class UserProxy : User
{
    public function __construct(IUserGateway $gateway, $userId) {
        $this->id = $id;
        $this->gateway = $gateway;
    }

    private $gateway;
    private $id;
    private $entity;

    public function getId() {
        // No need to hit the database when we already have the User Id.
        // This value does not need "lazy loading".
        return $this->id;
    }

    public function getUsername() {
        // The username is not a primary key column, so now we fetch this
        // from the database. This value is "lazy loaded".
        return this->getEntity()->getUsername();
    }

    private function getEntity() {
        if (!isset($this->entity)) {
            // This does the "lazy loading" of the User data
            $this->entity = $this->gateway->findById($this->id);
        }

        return $this->entity;
    }
}

Affinché le cose vengano cablate correttamente, dovresti creare un'interfaccia IGatewayFactory , che ti consentirà di accedere a qualsiasi "gateway" di cui hai bisogno:

interface IGatewayFactory
{
    public getUserGateway() : IUserGateway;
}

Ora abbiamo abbastanza da integrare questo con la classe OrderGateway :

class OrderGateway implements IOrderGateway
{
    public function __construct(IGatewayFactory $factory) {
        $this->factory = $factory;
    }

    private $factory;

    public function findById($id) {
        $data = // get from database...

        $order = new Order();
        $user = new UserProxy($this->factory->getUserGateway(), $data['user_id']);

        // This works, because the UserProxy object is implicitly cast to
        // its parent class: User
        $order->setUser($user);

        return $order;
    }
}

Ora per un caso d'uso di esempio:

$orderGateway = new OrderGateway(new GatewayFactory());
$order = $orderGateway->findById(3);
$user = $order->getUser(); // Returns a UserProxy object
echo $user->getId();       // Returns the cached value in memory
echo $user->getUsername(); // Now we hit the database to fetch the record

Ma questo non risolve ancora il problema del viaggio extra nel database. Ritardiamo questo viaggio solo fino a quando non è necessario (un'altra buona parola chiave da cercare è N + 1 performance della query ).

Sembra davvero che tu voglia un Object / Relational Mapper (ORM). La ricerca di php orm dovrebbe darti un buon punto di partenza. Le librerie ORM offrono una gran quantità di opzioni per l'interrogazione dei dati, nonché l'inserimento, l'aggiornamento e l'eliminazione dei dati.

Una volta raggiunto il punto di lazy loading dei dati e la necessità di fare JOIN, hai superato la tua soluzione di accesso ai dati di produzione domestica. Un ORM non sta complicando le cose. Sembra che nel tuo caso un ORM stia semplicemente ammettendo che il problema è più grande di quanto non sembrasse in origine. È, infatti, una soluzione di dimensioni giuste .

    
risposta data 01.02.2017 - 20:10
fonte

Leggi altre domande sui tag