mercredi 18 janvier 2023

Is it possible to implement “column based” multi-tenancy while avoiding to add the tenantId to all methods in a NestJS/TypeORM App?

Context

We are building our backend using NestJS and TypeORM (with PostgreSQL) and have determined that the optimal multi-tenant strategy for us is to utilize a shared database/shared schema with a column (companyId) identifying the "tenant" (company) on all our tables.

Current Solution

We have implemented this strategy successfully by retrieving the companyId from the request (currently from the query parameters but it will be in a JWT token once authentication is in place) and passing the companyId to all our method calls from the controller to the service and then to the repository.

For example :

The CatsController.create method takes companyId from the @Query('companyId') and passes it to our service:

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Post()
  create(
    @Query('companyId') companyId: string,
    @Body() createCatDto: CreateCatDto,
  ) {
    return this.catsService.create(companyId, createCatDto);
  }
}

The CatsService.create method on our service also takes companyId as an argument to pass it to the repository :

@Injectable()
export class CatsService {
  constructor(
    @InjectRepository(Cat)
    private readonly catRepository: Repository<Cat>,
  ) {}

  create(companyId: string, createCatDto: CreateCatDto) {
    return this.catRepository.save({ ...createCatDto, companyId });
  }
}

I have created a sample project with a simplified version of our implementation to demonstrate how it can become complicated and lead to a significant amount of boilerplate code for managing companyId for all our class methods.

https://github.com/jmoyson/multi-tenant-example

Problem

While this is working, as we continue to develop more modules, it becomes apparent that this approach is not developer-friendly and it forces us to be extra cautious to ensure that every new module implements this strategy correctly to prevent any issues with data separation.

So my question is :
Is there a more efficient way to implement data separation without having to pass the companyId to all method calls, while still using the column-based tenant strategy we have decided to implement?

ps: If possible, we want to avoid the use of Request Scoped controllers/services as all our requests will need to implement the solution and we do not want to negatively impact performance.

Aucun commentaire:

Enregistrer un commentaire