Implementación de un servicio genérico
Se me ocurrió implementar un servicio genérico que contenga todos los métodos CRUD utilizando los generics de TypeScript.
Con esto ganamos que solo debemos implementar una clase genérica y todas los demás servicios de esta clase deberán extender de esta clase genérica, así tendrán todos los métodos CRUD implementados de forma automática y con el mínimo esfuerzo.
Para comenzar implementamos una nueva clase dentro de la carpeta common
, la llamaré GenericService.service.ts
Esta clase recibirá 3 Generics Types:
- ENTITY: Representa la entidad del servicio
- ID: Representa el tipo del Primary Key del Repositorio (no siempre es un number, por eso lo coloco como dinámico)
- DTO: Representa la clase del DTO (necesaria para recibir los datos desde el body)
- PARTIAL_DTO: Representa la misma clase del DTO pero con sus atributos como opcionales
La clase es abstracta para evitar que la implementen por error.
export abstract class GenericService<ENTITY, ID, DTO, PARTIAL_DTO> {}
Además, esta clase recibe en su constructor una instancia de un repositorio genérico (esta será enviada desde la implementación de esta clase)
export abstract class GenericService<ENTITY, ID, DTO, PARTIAL_DTO> {
private readonly genericRepository: Repository<ENTITY>
constructor(genericRepository: Repository<ENTITY>) {
this.genericRepository = genericRepository;
}
}
Una vez echo esto ya podemos crear los métodos CRUD genéricos:
async create(data: DTO): Promise<ENTITY> {
const newItem = this.genericRepository.create(data);
await this.genericRepository.save(newItem);
return newItem;
}
El código completo sería el siguiente:
import { NotFoundException } from '@nestjs/common';
import { Repository } from "typeorm";
export abstract class GenericService<ENTITY, ID, DTO, PARTIAL_DTO> {
private readonly genericRepository: Repository<ENTITY>
constructor(genericRepository: Repository<ENTITY>) {
this.genericRepository = genericRepository;
}
async create(data: DTO): Promise<ENTITY> {
const newItem = this.genericRepository.create(data);
await this.genericRepository.save(newItem);
return newItem;
}
async update(id: ID, data: PARTIAL_DTO): Promise<ENTITY> {
const product = await this.genericRepository.findOne(id);
if (!product) {
throw new NotFoundException(`Product with id ${id} not found`);
}
return this.genericRepository.merge(product, data);
}
async delete(id: ID): Promise<boolean> {
const foundItem = await this.findOne(id);
await this.genericRepository.delete(id);
return true;
}
async findAll(): Promise<ENTITY[]> {
return this.genericRepository.find();
}
async findOne(id: ID): Promise<ENTITY> {
const product = await this.genericRepository.findOne(id);
if (!product) {
throw new NotFoundException(`Product with id ${id} not found`);
}
return product;
}
}
Ahora para utilizar esta clase genérica solo debemos extender nuestros servicios normalmente e indicamos los valores de los tipos genéricos, por ejemplo, el servicio de Productos sería solo así:
@Injectable()
export class ProductsService extends GenericService<Product, number, CreateProductDto, UpdateProductDto> {
constructor(@InjectRepository(Product) private productRepository: Repository<Product>) {
super(productRepository);
}
}
El servicio de usuarios sería igual:
@Injectable()
export class UsersService extends GenericService<Client, number, CreateUserDto, UpdateUserDto>{
constructor(
@InjectRepository(Client) private clientRepository: Repository<Client>,
private productsService: ProductsService,
private configService: ConfigService,
) {
super(clientRepository);
}
//all code here
}
De esta forma como se puede observar el código se reutiliza muy óiptimamente y no tenemos que esta repitiendo las operaciones CRUD una y otra vez en cada servicio de la aplicación.
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?