I am writing a Spring Boot Webflux app which needs to be Multi-tenant. The underlying database is MongoDB and we plan on using tenantId field in each of the collections. Consequently, every query needs to be scoped with the requesting user's tenant.
To reduce bolierplate as well as to provide safety against classic forgotten WHERE tenantId = ? problem, I am looking to
- Have a way to intercept the queries and add the filter on accountId on all read queries.
- Automatically set the tenantId on save.
- Reuse the Spring Boot JPA magic of writing just interfaces.
So if I execute roleRepository.findByName("something") in the context of a request, it should automatically scope it to the current tenant.
Is there a way using AspectJ to achieve this? I wasn't able to find much resource on this kind of multitenancy in spring boot even though it is a fairly common approach with libraries in other stacks.
So far I have created the structure as follows.
Model Structure:
public interface TenantScopedModel {
public String getAccountId();
public void setAccountId(String accountId);
}
@Data
public abstract class OptionallyTenantScopedModel implements TenantScopedModel {
@Id
@MongoId(FieldType.OBJECT_ID)
protected String id;
@Getter
@Setter
@Field(targetType = FieldType.OBJECT_ID)
@Indexed
protected String accountId;
@Getter
@Setter
protected boolean isDeleted = false;
public OptionallyTenantScopedModel() {
this.id = new ObjectId().toString();
}
}
Example Model:
@EqualsAndHashCode(callSuper = true)
@Data
@Valid
@Document(collection = "roles")
public class Role extends OptionallyTenantScopedModel {
@NotBlank
private String roleName;
@NotBlank
private boolean systemDefined = false;
}
TenantScopedRepository
@NoRepositoryBean
public interface TenantScopedRepository<T extends TenantScopedModel, ID> extends ReactiveMongoRepository<T, ID> {
}
The best way to do this, in my opinion, is to override/intercept
ReactiveMongoTemplateenhance find or other methods query parameter. Pass tenant id in all requests using thread local by using reactive context switching libraries (detailed below).SimpleMongoRepositoryis default implementation for repository interface for MongoDB and it internally callsReactiveMongoTemplatefor all queries.So, steps to automatically scope current tenant for find query methods should be
CustomReactiveMongoTemplateextendingReactiveMongoTemplateto extend any desired methods to enhance queries with account id.CustomReactiveMongoTemplateto add account id from ThreadLocal(This is package private method called when you use find query methods like findByName etc. Since this is package private best way to intercept these is Spring aspects instead of overriding class)
In my opinion, it is not worth it, doing all this to automatically add tenant id in all queries because