| name | myquota-service |
| description | Patrones de BaseService: inyección de dependencias, lógica de negocio, métodos heredados. Trigger: Cuando se crea un servicio, se agrega lógica de negocio, o se necesita inyección de dependencias.
|
| license | MIT |
| metadata | {"author":"myquota","version":"1.0","auto_invoke":["Creating a service","Adding business logic","Injecting dependencies"]} |
Propósito
Crear servicios en MyQuota usando BaseService<T> como base. Los servicios contienen toda la lógica de negocio.
Patrón Base
import { BaseService } from "@shared/classes/base.service";
import { MyEntity } from "./myEntity.model";
import { MyEntityRepository } from "./myEntity.repository";
export class MyEntityService extends BaseService<MyEntity> {
protected repository: MyEntityRepository;
constructor(repository: MyEntityRepository) {
super(repository);
this.repository = repository;
}
}
Métodos Heredados de BaseService
| Método | Firma |
|---|
create | (data: Omit<T, keyof IBaseEntity>) => Promise<T> |
findAll | (filters?: Partial<T>) => Promise<T[]> |
findById | (id: string) => Promise<T | null> |
findOne | (filters: Partial<T>) => Promise<T | null> |
update | (id: string, data: Partial<Omit<T, keyof IBaseEntity>>) => Promise<T | null> |
delete | (id: string) => Promise<boolean> |
softDelete | (id: string) => Promise<boolean> |
Reglas de Service
1. Constructor recibe repository
constructor(repository: MyEntityRepository) {
super(repository);
this.repository = repository;
}
constructor(userId: string) {
const repository = new MyEntityRepository(userId);
super(repository);
}
2. Declarar protected repository
Para acceder a métodos custom del repository específico:
export class MyEntityService extends BaseService<MyEntity> {
protected repository: MyEntityRepository;
constructor(repository: MyEntityRepository) {
super(repository);
this.repository = repository;
}
async findByStatus(status: string): Promise<MyEntity[]> {
return this.repository.findByStatus(status);
}
}
3. Lógica de negocio aquí
async createWithValidation(data: CreateMyEntityDto): Promise<MyEntity> {
if (!data.name) {
throw new Error("El nombre es requerido");
}
if (data.amount < 0) {
throw new Error("El monto debe ser positivo");
}
const normalized = {
...data,
name: data.name.trim().toUpperCase(),
};
return this.create(normalized);
}
Inyección de Múltiples Dependencias
Cuando un servicio necesita datos de otro módulo:
export class TransactionService extends BaseService<Transaction> {
protected repository: TransactionRepository;
constructor(
repository: TransactionRepository,
private quotaRepository: QuotaRepository,
private categoryService: CategoryService,
) {
super(repository);
this.repository = repository;
}
async createWithQuotas(data: CreateTransactionDto): Promise<Transaction> {
const transaction = await this.create(data);
for (const quotaData of data.quotas) {
await this.quotaRepository.create({
transactionId: transaction.id,
...quotaData,
});
}
return transaction;
}
}
La instanciación de todas las dependencias ocurre en routes.ts:
const transactionRepo = new TransactionRepository(userId, creditCardId);
const quotaRepo = new QuotaRepository(userId, creditCardId, transactionId);
const categoryService = new CategoryService(new CategoryRepository(userId));
const service = new TransactionService(
transactionRepo,
quotaRepo,
categoryService,
);
Servicios sin BaseService
Para servicios que no tienen entidad CRUD propia (como AuthService, StatsService):
export class StatsService {
constructor(
private creditCardRepository: CreditCardRepository,
private transactionRepository: TransactionRepository,
) {}
async getDebtSummary(): Promise<DebtSummary> {
const cards = await this.creditCardRepository.findAll();
return summary;
}
}
Servicios Especializados (Sub-servicios)
Cuando un servicio crece demasiado, extraer concerns específicos a sub-servicios dentro del mismo módulo:
src/modules/transaction/
├── transaction.service.ts # Servicio principal (orquesta)
├── emailImport.service.ts # Gmail + parsing de emails bancarios
└── manualTransaction.service.ts # CRUD de transacciones manuales
Patrón de Sub-servicio
export class EmailImportService {
async fetchBankEmails(
userId: string,
creditCardRepository: CreditCardRepository,
): Promise<{ importedCount: number }> {
}
}
Reglas de Sub-servicios
- NO extienden BaseService (no tienen entidad CRUD propia)
- Reciben dependencias explícitamente (por parámetro o constructor)
- Son instanciados desde el servicio principal
- Un archivo separado dentro del mismo módulo
- Documentar con JSDoc qué concern manejan
Anti-patterns
async create(req: Request): Promise<MyEntity> {
return this.repository.create(req.body);
}
constructor(userId: string) {
this.repository = new MyEntityRepository(userId);
}
async getAllFormatted(): Promise<string[]> {
const items = await this.findAll();
return items.map(i => `${i.name}: $${i.amount}`);
}
Checklist