Patrón y composición de la puerta de enlace

7

A menudo escribo aplicaciones de base de datos intensivas y descubrí el patrón de la puerta de enlace que parecía satisfacer mis necesidades. Mi problema ahora es que muchos de mis modelos están compuestos de otros modelos.

Por ejemplo, tengo un modelo User y un modelo Order que tienen cada uno un propio Gateway .

Interfaz para 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;
}

Interfaz para 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;
}

Ahora, Order contiene una instancia de User a la que luego se hace referencia dentro de mi base de datos con un índice externo orders.user_id dentro de la tabla orders .

La pregunta ahora es cómo consultar y crear los objetos dentro de la implementación IOrderGateway .

Hasta ahora usé Dependency Injection para inyectar IUserGateway en las instancias constructor de mi implementación IOrderGateway y luego llamé a IUserGateway::findById para obtener la instancia User requerida para cada Order .

Pero esto parece una pérdida de recursos porque tengo que hacer una consulta adicional a la base de datos (MySQL en este caso) que también podría lograrse con un INNER JOIN .

Pero entonces IOrderGateway tiene que preocuparse por la creación de User s.

También he investigado un poco más y he encontrado implementaciones del Gateway Pattern que solo devuelven los datos de fila sin procesar de la capa de datos y luego los pasan a un Factory que crea el objeto.

Entonces, ¿qué método debo usar? ¿Hay mejores soluciones que las mencionadas?

Cuando no uso JOIN solo tengo user_id para cada pedido. Para obtener la instancia del usuario, tengo que llamar a IUserGateway::findById() para obtener esa instancia que provoca una consulta adicional a la base de datos.

El problema es que no sé qué solución utilizar. Soy relativamente nuevo en OO-Concepts. Para mí, usar un JOIN para consultar y luego crear una instancia de Order y User parece ser la mejor solución. Pero entonces violaría los principios de SOLID porque a IOrderGateway le importan Order y User .

    
pregunta Code Spirit 28.11.2016 - 23:22

2 respuestas

3
  

Cuando no uso JOIN solo tengo user_id para cada pedido. Para obtener la instancia del usuario, tengo que llamar a IUserGateway::findById() para obtener esa instancia que provoca una consulta adicional a la base de datos.

Todo cierto. Así es como funciona CRUD .

Si quieres unirte, algunos ORM te permiten hacer algo como esto:

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

Donde MyDataTransferObject es una clase que contiene propiedades cuyos nombres corresponden a las columnas que desea devolver de su base de datos.

    
respondido por el Robert Harvey 03.12.2016 - 16:50
1

Carga diferida de relaciones de entidad

Si está escribiendo su propia capa de acceso a datos / ORM, lo que realmente está buscando es Carga diferida de relaciones de entidad . Esto no es realmente algo que maneja explícitamente un Gateway. Una clase "proxy" podría hacer esto (lo que podría tomar otra puerta de enlace como un argumento de constructor).

Primero, algunas clases de entidades que ya tienes / parecen similares a las que tienes:

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;
    }
}

El truco con "proxies" en este caso es que deben heredar de la entidad real. Si desea cargar de forma perezosa el objeto User , cree la clase UserProxy y hágalo heredar de User . Luego, UserProxy debe admitir los mismos métodos públicos que User :

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;
    }
}

Para que las cosas estén bien conectadas, debe crear una interfaz IGatewayFactory , que le dará acceso a cualquier "puerta de enlace" que necesite:

interface IGatewayFactory
{
    public getUserGateway() : IUserGateway;
}

Ahora tenemos suficiente para integrar esto con la clase 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;
    }
}

Ahora para un caso de uso de ejemplo:

$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

Pero esto todavía no aborda el problema del viaje adicional a la base de datos. Solo retrasamos este viaje hasta que sea necesario (otra buena palabra de moda para buscar es rendimiento de consulta N + 1 ).

Realmente suena como si quisieras un objeto / Mapeador relacional (ORM). La búsqueda de php orm debería darle un buen lugar para comenzar. Las bibliotecas de ORM le ofrecen infinidad de opciones para consultar datos, así como para insertar, actualizar y eliminar datos.

Una vez que alcanza el punto de carga lenta de datos y necesita hacer JOIN, ha superado su solución de acceso a datos elaborada en casa. Un ORM no está complicando las cosas. Parece que en su caso un ORM es simplemente admitir que el problema es más grande de lo que parecía originalmente. Es, de hecho, una solución de tamaño adecuado .

    
respondido por el Greg Burghardt 01.02.2017 - 20:10

Lea otras preguntas en las etiquetas