Closed htamagnus closed 1 month ago
@emerson-oliveira @felipesouza91 @clintonbrito Sintam-se a vontade para questionar e fazer perguntas, isso é uma visão MINHA, baseado em estudos e tendo em vista o que acho que vai ser melhor para todos e pro projeto. Se vocês acham que é necessário manter, comentem aqui também. Isso aqui não é uma verdade absoluta, e temos outras alternativas para esse refactor, que envolveria migrarmos para DDD também, se acharem necessário.
@htamagnus Poderíamos então usar direto o padrão MVC ( Model - Entidades, View - Controller, Controller-Services, Regas de Negocio e Repositórios ) do próprio NestJs, utilizando o sistema de injeção de dependência dele. Dessa forma ficaríamos muito próximo do que é apresentado no Start Guid do NestJS.
@htamagnus Concordo com o que o @felipesouza91 mencionou. Quanto mais nos mantivermos próximos da "arquitetura original" do Nest, mais fácil será manter o projeto, especialmente quando novas pessoas, possivelmente com menos experiência (como eu), entrarem na equipe. Isso reduz a curva de aprendizado e permite que todos foquem no mais importante: entregar valor para o usuário final.
Como você explicou muito bem, essas camadas extras de abstração fazem mais sentido em projetos maiores do que o TVM. Assino embaixo acerca de iniciativas que reduzam a quantidade de pastas e arquivos, tornando a arquitetura mais simplificada.
Uma dúvida, @htamagnus : como ficaria em relação à sprint atual? Essa refatoração seria algo mais pra frente? Sigo com o modelo atual assim mesmo nessa sprint?
@htamagnus Concordo com o que o @felipesouza91 mencionou. Quanto mais nos mantivermos próximos da "arquitetura original" do Nest, mais fácil será manter o projeto, especialmente quando novas pessoas, possivelmente com menos experiência (como eu), entrarem na equipe. Isso reduz a curva de aprendizado e permite que todos foquem no mais importante: entregar valor para o usuário final.
Como você explicou muito bem, essas camadas extras de abstração fazem mais sentido em projetos maiores do que o TVM. Assino embaixo acerca de iniciativas que reduzam a quantidade de pastas e arquivos, tornando a arquitetura mais simplificada.
Uma dúvida, @htamagnus : como ficaria em relação à sprint atual? Essa refatoração seria algo mais pra frente? Sigo com o modelo atual assim mesmo nessa sprint?
SObre a priorização: primeiro as tasks da sprint, depois podemos seguir com as issues
Então podemos seguir assim, removendo essas camadas complexas de abstração e refatorando pra seguir o padrão MVC, o que acham?
@felipesouza91 @clintonbrito
Perfeito...
Minha sugestão: @felipesouza91 @clintonbrito
Com a refatoração ficaria mais ou menos assim:
Controller:
Controller (ProductsController): Define rotas para obter detalhes do produto e verificar a disponibilidade. Utiliza o serviço ProductsService para a lógica de negócios.
Vamos separar pelos contextos e as operações correspondentes, assim:
import { Controller, Get, Param } from '@nestjs/common';
import { ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ProductDto } from './dto/product.dto';
import { ProductsService } from './products.service';
@ApiTags('products')
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
@Get('/details/:productId')
@ApiOperation({ summary: 'Get Product Details' })
@ApiParam({ name: 'productId', required: true, description: 'The ID of the product' })
@ApiResponse({ status: 200, description: 'Product details retrieved successfully', type: ProductDto })
@ApiResponse({ status: 400, description: 'Invalid input data or Product not found' })
@ApiResponse({ status: 404, description: 'Product not found' })
async getProductDetails(@Param('productId') productId: string): Promise<ProductDto> {
return await this.productsService.getProductDetails(productId);
}
@Get('/availability/:productId')
@ApiOperation({ summary: 'Check Product Availability' })
@ApiParam({ name: 'productId', required: true, description: 'The ID of the product' })
@ApiResponse({ status: 200, description: 'Product availability status retrieved successfully' })
@ApiResponse({ status: 400, description: 'Invalid input data' })
@ApiResponse({ status: 404, description: 'Product not found' })
async checkProductAvailability(@Param('productId') productId: string): Promise<{ available: boolean }> {
return await this.productsService.checkProductAvailability(productId);
}
}
Service:
O service contém a lógica de negócios e a interação com o banco de dados.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Product } from '../common/entities/product.entity';
import { ProductDto } from './dto/product.dto';
import ProductNotFound from '../exceptions/HC005.product-not-found.error';
import { ProductRepository } from './repositories/product.repository';
@Injectable()
export class ProductsService {
constructor(
@InjectRepository(Product, 'store_database')
private readonly productRepository: Repository<Product>,
private readonly productRepository: ProductRepository
) {}
async getProductDetails(productId: string): Promise<ProductDto> {
const product = await this.productRepository.findOne({
where: { id: productId }
});
if (!product) {
throw ProductNotFound;
}
return {
id: product.id,
name: product.name,
description: product.description,
price: product.price,
category: product.category,
imageUrl: product.imageUrl,
stockQuantity: product.stockQuantity,
updatedAt: product.updatedAt
};
}
async checkProductAvailability(productId: string): Promise<{ available: boolean }> {
const product = await this.productRepository.findOne({
where: { id: productId }
});
if (!product) {
throw ProductNotFound;
}
return { available: product.stockQuantity > 0 };
}
}
DTO:
com validaçÃO Zod:
import { createZodDto } from 'nestjs-zod';
import { z } from 'zod';
const productSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string(),
price: z.number(),
category: z.string(),
imageUrl: z.string().nullable(),
stockQuantity: z.number(),
updatedAt: z.date()
});
export class ProductDto extends createZodDto(productSchema) {}
Module:
Configura o módulo para incluir o controlador e o serviço, além do repositório do produto.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Product } from '../common/entities/product.entity';
import { ProductsService } from './products.service';
import { ProductsController } from './products.controller';
import { ProductRepository } from './repositories/product.repository';
@Module({
imports: [
TypeOrmModule.forFeature([Product], 'store_database')
],
controllers: [ProductsController],
providers: [ProductsService, ProductRepository]
})
export class ProductsModule {}
Pastas:
src/
│
├── common/
│ └── entities/
│ └── product.entity.ts
│
├── exceptions/
│ └── HC005.product-not-found.error.ts
│
├── products/
│ ├── dto/
│ │ └── product.dto.ts
│ │
│ ├── repositories/
│ │ └── product.repository.ts
│ │
│ ├── products.controller.ts
│ ├── products.service.ts
│ └── products.module.ts
│
└── main.ts
Como vocês sabem, o projeto já passou por um refactor nas minhas mãos uma vez, mas eu tentei fazer de forma rápida pra resolver uma dor que existia naquele momento, que era a complexidade enorme pra mexer no projeto. Por conta disso, foquei em "tirar o grosso" e resolver o resto depois. E esse momento chegou.
Minha proposta é seguir essa mesma arquitetura, porém bem refinada e simplificada, vamos aos pontos:
PROPOSTA:
POR QUE?
Factories e contracts adicionam camadas extras de abstração ao código. Para projetos menores ou de médio porte, essas camadas são desnecessárias e apenas aumentam a dificuldade de compreensão do fluxo do sistema (que é o que está acontecendo).
Ao remover essas camadas, o código fica mais direto e mais fácil de navegar, especialmente para quem não está familiarizados com o padrão.
Com menos arquivos e abstrações, o tempo gasto para implementar novas funcionalidades ou corrigir bugs diminui. Isso torna o desenvolvimento mais rápido por que podemos ir diretamente ao ponto. Teremos menos "saltos" entre arquivos (de um contract para um factory, depois para um use-case) facilita o trabalho no dia a dia.
A remoção dessas estruturas torna o código mais direto, onde é fácil entender o que cada parte faz sem a necessidade de navegar por várias camadas de abstração.
COMO fazer isso?
Com a refatoração ficaria mais ou menos assim:
Controller:
Controller (ProductsController): Define rotas para obter detalhes do produto e verificar a disponibilidade. Utiliza o serviço ProductsService para a lógica de negócios.
Vamos separar pelos contextos e as operações correspondentes, assim:
Service:
O service contém a lógica de negócios e a interação com o banco de dados.
DTO:
com validaçÃO Zod:
Module:
Configura o módulo para incluir o controlador e o serviço, além do repositório do produto.
Pastas:
Sintam-se a vontade para questionar e fazer perguntas, isso é uma visão MINHA, baseado em estudos e tendo em vista o que acho que vai ser melhor para todos e pro projeto. Se vocês acham que é necessário manter, comentem aqui também