"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Creación de una aplicación multiinquilino con Honeystone/context

Creación de una aplicación multiinquilino con Honeystone/context

Publicado el 2024-08-18
Navegar:277

No debe confundirse con la nueva biblioteca de contexto de Laravel, este paquete se puede utilizar para crear aplicaciones multi-contexto y multiinquilino. La mayoría de las bibliotecas multiinquilino esencialmente tienen un único contexto de "inquilino", por lo que si necesita varios contextos, las cosas pueden volverse un poco complicadas. Este nuevo paquete resuelve ese problema.

Veamos un ejemplo, ¿vale?

Proyecto de ejemplo

Para nuestra aplicación de ejemplo, tendremos una base de usuarios global organizada en equipos y cada equipo tendrá múltiples proyectos. Esta es una estructura bastante común en muchas aplicaciones de software como servicio.

No es raro que las aplicaciones multiinquilino tengan cada base de usuarios dentro de un contexto de inquilino, pero para nuestra aplicación de ejemplo, queremos que los usuarios puedan unirse a varios equipos, por lo que es una base de usuarios global.
Diagrama de base de usuarios global versus base de usuarios inquilinos

Building a multi-tenant application with honeystone/context

Como SaaS, es probable que el equipo sea la entidad facturable (es decir, el asiento) y a ciertos miembros del equipo se les conceda permiso para administrar el equipo. Sin embargo, no profundizaré en estos detalles de implementación en este ejemplo, pero espero que proporcione algo de contexto adicional.

Instalación

Para que esta publicación sea concisa, no explicaré cómo iniciar tu proyecto Laravel. Ya hay muchos mejores recursos disponibles para eso, entre ellos la documentación oficial. supongamos que ya tiene un proyecto Laravel, con modelos de Usuario, Equipo y Proyecto, y que está listo para comenzar a implementar nuestro paquete de contexto.

La instalación es una simple recomendación del compositor:

composer install honeystone/context

Esta biblioteca tiene una función de conveniencia, contexto(), que a partir de Laravel 11 choca con la propia función de contexto de Laravel. Esto no es realmente un problema. Puedes importar nuestra función:

use function Honestone\Context\context;

O simplemente use el contenedor de inyección de dependencias de Laravel. A lo largo de esta publicación, asumiré que ha importado la función y la utilizará en consecuencia.

los modelos

Comencemos configurando nuestro modelo de Equipo:

belongsToMany(User::class);
    }

    public function projects(): HasMany
    {
        return $this->hasMany(Project::class);
    }
}

Un equipo tiene un nombre, miembros y proyectos. Dentro de nuestra aplicación, solo los miembros de un equipo podrán acceder al equipo o a sus proyectos.

Bien, veamos nuestro proyecto:

belongsTo(Team::class);
    }
}

Un proyecto tiene un nombre y pertenece a un equipo.

Determinando el contexto

Cuando alguien accede a nuestra aplicación, debemos determinar en qué equipo y proyecto está trabajando. Para simplificar las cosas, manejemos esto con parámetros de ruta. También asumiremos que solo los usuarios autenticados pueden acceder a la aplicación.

Ni equipo ni contexto del proyecto: app.mysaas.dev
Solo contexto del equipo: app.mysaas.dev/my-team
Contexto del equipo y del proyecto: app.mysaas.dev/my-team/my-project

Nuestras rutas se verán así:

Route::middleware('auth')->group(function () {

    Route::get('/', DashboardController::class);

    Route::middleware(AppContextMiddleware::Class)->group(function () {

        Route::get('/{team}', TeamController::class);
        Route::get('/{team}/{project}', ProjectController::class);
    });
});

Este es un enfoque muy inflexible, dado el potencial de conflictos de espacios de nombres, pero mantiene el ejemplo conciso. En una aplicación del mundo real querrás manejar esto de manera un poco diferente, tal vez anothersaas.dev/teams/my-team/projects/my-project o my-team.anothersas.dev/projects/my-project.

Primero deberíamos mirar nuestro AppContextMiddleware. Este middleware inicializa el contexto del equipo y, si está configurado, el contexto del proyecto:

route('team');
        $request->route()->forgetParameter('team');

        $projectId = null;

        //if there's a project, pull that too
        if ($request->route()->hasParamater('project')) {

            $projectId = $request->route('project');
            $request->route()->forgetParameter('project');
        }

        //initialise the context
        context()->initialize(new AppResolver($teamId, $projectId));
    }
}

Para comenzar, tomamos la identificación del equipo de la ruta y luego olvidamos el parámetro de ruta. No necesitamos que el parámetro llegue a nuestros controladores una vez que está en el contexto. Si se establece una identificación de proyecto, también la extraemos. Luego inicializamos el contexto usando nuestro AppResolver pasando nuestra identificación de equipo y nuestra identificación de proyecto (o nulo):

require('team', Team::class)
            ->accept('project', Project::class);
    }

    public function resolveTeam(): ?Team
    {
        return Team::with('members')->find($this->teamId);
    }

    public function resolveProject(): ?Project
    {
        return $this->projectId ?: Project::with('team')->find($this->projectId);
    }

    public function checkTeam(DefinesContext $definition, Team $team): bool
    {
        return $team->members->find(context()->auth()->getUser()) !== null;
    }

    public function checkProject(DefinesContext $definition, ?Project $project): bool
    {
        return $project === null || $project->team->id === $this->teamId;
    }

    public function deserialize(array $data): self
    {
        return new static($data['team'], $data['project']);
    }
}

Un poco más está sucediendo aquí.

El método define() es responsable de definir el contexto que se resuelve. El equipo es obligatorio y debe ser un modelo de Equipo, y el proyecto se acepta (es decir, opcional) y debe ser un modelo de Proyecto (o nulo).

resolveTeam() se llamará internamente durante la inicialización. Devuelve el Equipo o nulo. En caso de una respuesta nula, ContextInitializer generará la excepción CouldNotResolveRequiredContextException.

resolveProject() también se llamará internamente durante la inicialización. Devuelve el Proyecto o nulo. En este caso, una respuesta nula no dará lugar a una excepción ya que la definición no requiere el proyecto.

Después de resolver el equipo y el proyecto, ContextInitializer llamará a los métodos opcionales checkTeam() y checkProject(). Estos métodos llevan a cabo controles de integridad. Para checkTeam() nos aseguramos de que el usuario autenticado sea miembro del equipo, y para checkProject() comprobamos que el proyecto pertenece al equipo.

Finalmente, cada solucionador necesita un método deserialización(). Este método se utiliza para restablecer un contexto serializado. En particular, esto sucede cuando el contexto se utiliza en un trabajo en cola.

Ahora que el contexto de nuestra aplicación está configurado, debemos usarlo.

Accediendo al contexto

Como siempre, lo mantendremos simple, aunque un poco artificial. Al ver el equipo queremos ver una lista de proyectos. Podríamos construir nuestro TeamController para manejar estos requisitos de esta manera:

projects;

        return view('team', compact('projects'));
    }
}

Bastante fácil. Los proyectos que pertenecen al contexto actual del equipo pasan a nuestra vista. Imagine que ahora necesitamos consultar proyectos para obtener una vista más especializada. Podríamos hacer esto:

id)
            ->where('name', 'like', "%$query%")
            ->get();

        return view('queried-projects', compact('projects'));
    }
}

Ahora se está volviendo un poco complicado y es muy fácil olvidarse accidentalmente de "alcance" la consulta por equipo. Podemos resolver esto usando el rasgo BelongsToContext en nuestro modelo de Proyecto:

belongsTo(Team::class);
    }
}

Todas las consultas del proyecto ahora serán recopiladas por el contexto del equipo y el modelo de equipo actual se inyectará automáticamente en nuevos modelos de proyecto.

Simplifiquemos ese controlador:

get();

        return view('queried-projects', compact('projects'));
    }
}

Eso es todo amigos

A partir de ahora, solo estarás creando tu aplicación. El contexto está fácilmente a mano, sus consultas tienen un alcance y los trabajos en cola tendrán acceso automáticamente al mismo contexto desde el que fueron enviados.

Sin embargo, no todos los problemas relacionados con el contexto se resuelven. Probablemente desee crear algunas macros de validación para darle un poco de contexto a sus reglas de validación, y no olvide que a las consultas manuales no se les aplicará el contexto automáticamente.

Si planeas utilizar este paquete en tu próximo proyecto, nos encantaría saber de ti. Los comentarios y las contribuciones siempre son bienvenidos.

Puedes consultar el repositorio de GitHub para obtener documentación adicional. Si encuentra útil nuestro paquete, deje una estrella.

Hasta la próxima..


Este artículo se publicó originalmente en el Blog de Honeystone. Si le gustan nuestros artículos, considere consultar más contenido allí.

Declaración de liberación Este artículo se reproduce en: https://dev.to/piranhageorge/building-a-multi-tenant-application-with-honeystonecontext-3eob?1 Si hay alguna infracción, comuníquese con [email protected] para eliminarla.
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3