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 usersService3. 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-queryoswrpara la gestión de datos.
Estructura:
src/modules/onboarding/api/
├── mutations.ts
└── queries.ts6. 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.tsComunicació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.