Skip to Content

Módulos

Los módulos son carpetas que agrupan toda la lógica de servidor relacionada con un dominio de negocio específico. Son los “cerebros” de la aplicación que contienen la lógica de negocio, acceso a datos y reglas de dominio.

Estructura de un Módulo

src/modules/auth/ ├── actions/ # Server Actions - Puntos de entrada desde la UI ├── services/ # Lógica de negocio pura y acceso a datos ├── types/ # Tipos y esquemas específicos del dominio ├── features/ # Features modulares que pertenecen a este dominio ├── api/ # Endpoints de API específicos del módulo (opcional) ├── guards/ # Guardias de ruta o de lógica (opcional) └── adapters/ # Solo para servicios externos (opcional)

Componentes de un Módulo

1. actions/ - Server Actions

Propósito: Contiene los Server Actions, que son los puntos de entrada desde la UI para interactuar con la lógica de negocio del módulo. Cada archivo dentro de actions/ representa una acción específica.

Características:

  • Marcado con 'use server'
  • Recibe datos del formulario o cliente
  • Orquesta llamadas a servicios del módulo
  • Maneja errores y validaciones
  • Puede hacer redirects o revalidaciones

Ejemplo:

// src/modules/auth/actions/login.action.ts 'use server'; import { authService } from '../services/auth.service'; import { redirect } from 'next/navigation'; import { loginSchema } from '../types/auth.types'; export async function loginAction(prevState: any, formData: FormData) { // 1. Validar datos de entrada const result = loginSchema.safeParse({ email: formData.get('email'), password: formData.get('password'), }); if (!result.success) { return { message: 'Datos inválidos' }; } try { // 2. Orquestar lógica de negocio const user = await authService.validateLogin(result.data); // 3. Crear sesión await authService.createSession(user.id); } catch (error) { return { message: error.message }; } // 4. Redirect en caso de éxito redirect('/dashboard'); }

2. services/ - Lógica de Negocio

Propósito: Contiene la lógica de negocio pura, acceso a base de datos y reglas de dominio. Cada archivo dentro de services/ define un servicio específico.

Características:

  • Implementados como clases para facilitar la inyección de dependencias.
  • Sus métodos deberían funcionar como funciones puras cuando sea posible, facilitando el testing.
  • Reutilizables entre diferentes actions o incluso otros servicios.
  • Pueden comunicarse con otros módulos mediante la inyección de sus servicios.

Ejemplo:

// src/modules/auth/services/auth.service.ts import { db } from '@/lib/db'; import { compare, hash } from 'bcryptjs'; import { usersService } from '@/modules/users/services/user.service'; // Importamos la clase UsersService export class AuthService { constructor( private usersService: UsersService // Inyectamos UsersService ) {} async validateLogin(credentials: LoginCredentials) { const user = await db.user.findUnique({ where: { email: credentials.email } }); if (!user) { throw new Error('Usuario no encontrado'); } const isValid = await compare(credentials.password, user.password); if (!isValid) { throw new Error('Contraseña incorrecta'); } // Comunicación con otro módulo a través de un servicio inyectado await this.usersService.updateLastLogin(user.id); // Usamos el servicio inyectado a través de 'this' return user; } async createSession(userId: string) { // Lógica de creación de sesión const session = await db.session.create({ data: { userId, expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 días } }); return session; } async hashPassword(password: string) { return hash(password, 12); } } import { usersService } from '@/modules/users/services/user.service'; // Importamos la instancia de usersService export const authService = new AuthService(usersService); // Instanciamos AuthService con usersService

3. types/ - Tipos del Dominio

Propósito: Define todos los tipos, interfaces y esquemas de validación específicos del módulo. Cada archivo dentro de types/ agrupa tipos relacionados.

Características:

  • Usa Zod para validación
  • Define interfaces de datos
  • Tipos de respuesta y request
  • Enums y constantes tipadas

Ejemplo:

// src/modules/auth/types/auth.types.ts import { z } from 'zod'; // Esquemas de validación export const loginSchema = z.object({ email: z.string().email('Email inválido'), password: z.string().min(6, 'Mínimo 6 caracteres'), }); export const registerSchema = z.object({ email: z.string().email('Email inválido'), password: z.string().min(6, 'Mínimo 6 caracteres'), name: z.string().min(2, 'Mínimo 2 caracteres'), }); // Tipos inferidos export type LoginCredentials = z.infer<typeof loginSchema>; export type RegisterData = z.infer<typeof registerSchema>; // Interfaces del dominio export interface User { id: string; email: string; name: string; createdAt: Date; updatedAt: Date; } export interface Session { id: string; userId: string; expiresAt: Date; } // Tipos de respuesta export interface AuthResult { success: boolean; user?: User; message?: string; } // Enums export enum AuthProvider { EMAIL = 'email', GOOGLE = 'google', GITHUB = 'github', }

4. features/ - Features Modulares

Propósito: Contiene las features de UI que pertenecen específicamente a este módulo. Se organizan en subcarpetas para cada feature, que a su vez pueden contener components/, hooks/, schemas/, store/, types/, utils/, etc.

Características:

  • Solo contienen lógica de UI
  • Importan y usan los actions/services del módulo padre
  • Enfocadas en la presentación e interacción

Estructura:

src/modules/auth/features/ ├── login/ ├── components/ ├── hooks/ └── index.tsx ├── register/ └── reset-password/

5. api/ - Endpoints de API (Opcional)

Propósito: Contiene definiciones de endpoints de API específicos del módulo, como mutaciones y queries.

Características:

  • Define las interacciones con la API para el módulo.
  • Puede usar herramientas como react-query o swr para la gestión de datos.

Estructura:

src/modules/onboarding/api/ ├── mutations.ts └── queries.ts

6. guards/ - Guardias de Lógica (Opcional)

Propósito: Contiene lógica para proteger rutas, acciones o estados, asegurando que ciertas condiciones se cumplan antes de proceder.

Características:

  • Implementados como funciones o clases que devuelven un booleano o lanzan un error.
  • Útiles para validaciones de permisos, autenticación, o estados específicos del usuario.

Estructura:

src/modules/onboarding/guards/ └── onboarding.guard.ts

Comunicación entre Módulos

Los módulos se comunican entre sí a través de sus servicios, utilizando la inyección de dependencias. Esto permite que un servicio acceda a los métodos expuestos por otro servicio de manera controlada y explícita.

Ejemplo de Inyección de Dependencias: Supongamos que tenemos un NotificationService que necesita acceder a la información del usuario a través del AuthService.

// src/modules/notifications/services/notification.service.ts import { Service } from "@/shared/utils/service"; // Asumiendo una clase base Service import { AuthService, // Importamos la clase AuthService } from "@/modules/auth/services/auth.service"; import { EmailProvider, // Asumiendo un proveedor de email } from "@/providers/email/email.provider"; interface User { id: string; email: string; name: string; } export class NotificationService extends Service { constructor( private authService: AuthService, // Inyectamos AuthService private emailProvider: EmailProvider // Inyectamos EmailProvider ) { super(); } async sendWelcomeEmail(userId: string): Promise<void> { try { // Obtener información del usuario a través del AuthService inyectado const user: User | null = await this.authService.getUserById(userId); // Usamos el método del servicio inyectado if (!user) { this.logger.warn("User not found for welcome email", { origin: "NotificationService.sendWelcomeEmail", metadata: { userId }, }); return; } const subject = "¡Bienvenido a nuestra plataforma!"; const body = `Hola ${user.name}, Gracias por registrarte.`; await this.emailProvider.sendEmail(user.email, subject, body); this.logger.info("Welcome email sent successfully", { origin: "NotificationService.sendWelcomeEmail", metadata: { userId, email: user.email }, }); } catch (error) { this.logger.error("Failed to send welcome email", { origin: "NotificationService.sendWelcomeEmail", metadata: { userId, error: error instanceof Error ? error.message : String(error), }, }); throw error; } } // ... otros métodos del servicio de notificaciones } // Instanciación del servicio con sus dependencias // En un entorno real, esto podría manejarse con un contenedor de inyección de dependencias import { authService } from "@/modules/auth/services/auth.service"; // Importamos la instancia de authService import { emailProvider } from "@/providers/email/email.provider"; // Importamos la instancia de emailProvider export const notificationService = new NotificationService( authService, emailProvider ); // Ejemplo de cómo otro servicio (AuthService) podría usarlo: // src/modules/auth/services/auth.service.ts // import { notificationService } from "@/modules/notifications/services/notification.service"; // // export class AuthService { // // ... constructor y otros métodos // // async registerUser(userData: RegisterData) { // // ... lógica para crear usuario // const newUser = { id: "some-id", email: userData.email, name: userData.name }; // Suponiendo que se crea el usuario // await notificationService.sendWelcomeEmail(newUser.id); // Usamos el servicio de notificaciones // return newUser; // } // // ... otros métodos // }

Principios de los Módulos

1. Un Módulo = Un Dominio

Cada módulo representa un área específica de negocio (auth, users, payments, etc.).

2. Servicios Puros y Clases

Los servicios deben ser implementados como clases y sus métodos deberían funcionar como funciones puras cuando sea posible, facilitando el testing y la inyección de dependencias.

3. Actions como Orquestadores

Los actions coordinan servicios y manejan la comunicación con el cliente.

4. Comunicación por Inyección de Dependencias

Los módulos se comunican mediante la inyección de dependencias, de manera que cada módulo exponga solo los servicios que desea que se usen por fuera, promoviendo un acoplamiento bajo y una alta cohesión.

5. Separación de Responsabilidades

  • actions/: Puntos de entrada desde la UI, orquestan servicios.
  • services/: Lógica de negocio, acceso a datos, reglas de dominio.
  • types/: Definiciones de tipos, interfaces y esquemas de validación.
  • features/: Componentes de UI y lógica de presentación.
  • api/: Definiciones de endpoints de API.
  • guards/: Lógica de protección y validación.

Ejemplos de Módulos Comunes

Módulo de Autenticación

src/modules/auth/ ├── actions/ # login.action.ts, register.action.ts, logout.action.ts ├── services/ # auth.service.ts, session.service.ts ├── providers/ # authProvider.ts (ej. para Clerk, NextAuth) ├── types/ # auth.types.ts, user.types.ts └── features/ ├── login/ ├── register/ └── reset-password/

Módulo de Usuarios

src/modules/users/ ├── actions/ # updateProfile.action.ts, deleteUser.action.ts ├── services/ # user.service.ts ├── types/ # user.types.ts, profile.types.ts └── features/ ├── profile/ ├── settings/ └── avatar/

Módulo de Pagos

src/modules/payments/ ├── actions/ # processPayment.action.ts, refund.action.ts ├── services/ # payment.service.ts, tax.service.ts ├── types/ # payment.types.ts, invoice.types.ts ├── adapters/ # stripe.adapter.ts, paypal.adapter.ts └── features/ ├── checkout/ ├── invoices/ └── subscriptions/

Los módulos son la base de una arquitectura escalable y mantenible, proporcionando una separación clara de responsabilidades y facilitando el desarrollo en equipo.

Last updated on