Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] @Transactional on controller level and nested transaction errors #37

Open
Choo-Xing-Yu opened this issue Apr 27, 2023 · 0 comments

Comments

@Choo-Xing-Yu
Copy link

Choo-Xing-Yu commented Apr 27, 2023

Currently I have this on my NestJS application on the controller level

Controller level:

@Controller('supplier')
export class SupplierController {
  constructor(private readonly supplierService: SupplierService) {}

  @Transactional()
  @Post()
  async create(@Body() createSupplierDto: CreateSupplierDto) {
    return await this.supplierService.create(createSupplierDto);
  }

  @Transactional({ propagation: Propagation.SUPPORTS })
  @Get()
  async findAll() {
    return await this.supplierService.findAll();
  }

  @Transactional({ propagation: Propagation.SUPPORTS })
  @Get(':id')
  async findOne(@Param('id') id: string) {
    return await this.supplierService.findOne(+id);
  }

  @Transactional()
  @Patch(':id')
  async update(@Param('id') id: string, @Body() updateSupplierDto: UpdateSupplierDto) {
    return await this.supplierService.update(+id, updateSupplierDto);
  }

  @Transactional()
  @Delete(':id')
  async remove(@Param('id') id: string) {
    return await this.supplierService.remove(+id);
  }
}

Service Level:

@Injectable()
export class SupplierService {
  constructor(
    @InjectRepository(Supplier)
    private readonly supplierRepository: Repository<Supplier>,
    @Inject(forwardRef(() => MaterialService))
    private readonly materialService: MaterialService,
  ) { }

  async create(createSupplierDto: CreateSupplierDto) {
    await this.checkNoSameName(createSupplierDto);
    const newSupplier = this.supplierRepository.create(createSupplierDto);
    return await this.supplierRepository.save(newSupplier);
  }

  async findAll() {
    return await this.supplierRepository.find();
  }

  async findOne(id: number) {
    return await this.supplierRepository.findOneBy({ id });
  }

  async findOneByName(name: string) {
    return await this.supplierRepository.findOneBy({ name });
  }

  async update(id: number, updateSupplierDto: UpdateSupplierDto) {
    await this.checkNoSameName(updateSupplierDto);
    const supplier = await this.findOne(id);
    if (!supplier) {
      throw new NotFoundException('Supplier not found!');
    }
    return this.supplierRepository.save({ ...supplier, ...updateSupplierDto });
  }

  async remove(id: number) {
    const supplier = await this.findOne(id);
    if (!supplier) {
      throw new NotFoundException('Supplier not found!');
    }
    // since many materials can't survive without supplier
    // check before deleting this supplier that there's no material belonging to it
    const taggedMaterials = await this.materialService.findTaggedSupplier(id);
    if (taggedMaterials.length > 0) {
      throw new BadRequestException(
        ERROR_MESSAGE_FORMATS.SUPPLIER.TAGGED_MATERIALS(taggedMaterials.length),
      );
    }

    return this.supplierRepository.remove(supplier);
  }

  async checkNoSameName(dto: UpdateSupplierDto) {
    if (!dto || !dto.name) return;
    const sameNameSupplier = await this.findOneByName(dto.name);
    if (sameNameSupplier) {
      throw new BadRequestException(
        ERROR_MESSAGE_FORMATS.SUPPLIER.SAME_NAME(sameNameSupplier.id),
      );
    }
  }
}

Datasource

const SQLIITE = 'sqlite';

{
    type: SQLIITE,
    database: process.env.DATABASE_PATH.endsWith(`.${SQLIITE}`)
      ? process.env.DATABASE_PATH
      : `${process.env.DATABASE_PATH}.${SQLIITE}`,
    entities: ['dist/**/*.entity.js'],
    migrations: ['dist/db/migrations/*.js'],
}

I've noticed that the examples all dictate service-level transactions. But may I ask if controller level transactions are okay?
Note: I have GET endpoints use the propagation level of SUPPORTS because for some reason when I have my FE do concurrent GET requests it'll throw a SQLITE_ERROR: cannot start a transaction within a transaction
image

tldr

  • Are controller level transactions are okay?
  • GET endpoints with transactions throw SQLITE_ERROR: cannot start a transaction within a transaction when hit concurrently, what's the proper way to fix this case? I was under the impression that REQUIRES_NEW (default) supports current transaction instead of starting new when there's one
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant