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
Support Multitenancy via discriminator column #2577
Comments
@graemerocher @dstepanov @radovanradic thoughts? |
To implement it, first, we need to support query parameters resolved outside of the method scope. |
|
wouldn't it be better to support the expression language? You could then at an expression context to resolve the current tenant |
Maybe, but that is a bit more complex and can be added later. |
Ok, for me personally it doesn't make sense to introduce another overloaded syntax |
I agree with @graemerocher I think we should aim to support the expression language in Micronaut Data queries. I created an issue to support the expression language for tenant resolution. |
I'm a bit afraid to support all that |
@graemerocher Do we know at the compilation time what is the return type of the expression? |
good question, looking at the code it is possible, but might require changes to the element API |
the type is available here:
But we would probably need to expose |
We added support for expression language in multi tenancy. Given an application such as: Entity package example.micronaut;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
import io.micronaut.serde.annotation.Serdeable;
@Serdeable
@MappedEntity
public record Book(@Nullable @Id @GeneratedValue Long id,
String title,
String framework) {
} Controller package example.micronaut;
import io.micronaut.data.runtime.multitenancy.TenantResolver;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import java.util.List;
@Controller("/books")
class BookController {
private final TenantResolver tenantResolver;
private final BookRepository bookRepository;
BookController(TenantResolver tenantResolver, BookRepository bookRepository) {
this.tenantResolver = tenantResolver;
this.bookRepository = bookRepository;
}
@Get
List<Book> index() {
return bookRepository.findAllByTenant(tenantResolver.resolveTenantIdentifier().toString());
}
} and: package example.micronaut;
import io.micronaut.data.annotation.Query;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;
import java.util.List;
@JdbcRepository(dialect = Dialect.H2)
public interface BookRepository extends CrudRepository<Book, Long>{
@Query(value = "SELECT * FROM book WHERE framework = :tenant")
List<Book> findAllByTenant(String tenant);
} The following tests passes: package example.micronaut;
import io.micronaut.context.annotation.Property;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@Property(name = "datasources.default.schema-generate", value = "CREATE_DROP")
@Property(name = "datasources.default.url", value = "jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE")
@Property(name = "datasources.default.username", value = "sa")
@Property(name = "datasources.default.password", value = "")
@Property(name = "datasources.default.dialect", value = "H2")
@Property(name = "datasources.default.driver-class-name", value = "org.h2.Driver")
@Property(name = "micronaut.multitenancy.tenantresolver.httpheader.enabled", value = StringUtils.TRUE)
@MicronautTest(transactional = false)
class BookControllerTest {
@Test
void multitenancyRequest(@Client("/") HttpClient httpClient,
BookRepository bookRepository) {
bookRepository.save(new Book(null, "Building Microservices with Micronaut", "micronaut"));
bookRepository.save(new Book(null, "Introducing Micronaut", "micronaut"));
bookRepository.save(new Book(null, "Grails 3 - Step by Step", "grails"));
bookRepository.save(new Book(null, "Falando de Grail", "grails"));
bookRepository.save(new Book(null, "Grails Goodness Notebook", "grails"));
BlockingHttpClient client = httpClient.toBlocking();
HttpRequest<?> request = HttpRequest.GET("/books").header("tenantId", "micronaut");
Argument<List<Book>> responseArgument = Argument.listOf(Book.class);
HttpResponse<List<Book>> response = assertDoesNotThrow(() -> client.exchange(request, responseArgument));
assertEquals(HttpStatus.OK, response.getStatus());
List<Book> books = response.body();
assertNotNull(books);
assertEquals(2, books.size());
response = assertDoesNotThrow(() -> client.exchange(HttpRequest.GET("/books").header("tenantId", "grails"), responseArgument));
assertEquals(HttpStatus.OK, response.getStatus());
books = response.body();
assertNotNull(books);
assertEquals(3, books.size());
}
} Supporting expression language in package example.micronaut;
import io.micronaut.data.runtime.multitenancy.TenantResolver;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import java.util.List;
@Controller("/books")
class BookController {
private final BookRepository bookRepository;
BookController( BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@Get
List<Book> index() {
return bookRepository.findAllByTenant);
}
} Using the EL in the package example.micronaut;
import io.micronaut.data.annotation.Query;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;
import java.util.List;
@JdbcRepository(dialect = Dialect.H2)
public interface BookRepository extends CrudRepository<Book, Long>{
@Query(value = "SELECT * FROM book WHERE framework = :#{tenanId}")
List<Book> findAllByTenant();
} |
I will try to integrate the expression language. I'm thinking to avoid embedding the language into the SQL query (to simlify parsing, checking for brackets etc) by introducing an annotation that will bind the query parameter to the expression instead of the method parameter: package example.micronaut;
import io.micronaut.data.annotation.Query;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;
import java.util.List;
@JdbcRepository(dialect = Dialect.H2)
public interface BookRepository extends CrudRepository<Book, Long>{
@Query(value = "SELECT * FROM book WHERE framework = :tenanId")
@ParameterExpression(name = "tenanId", value = "#{...}")
List<Book> findAllByTenant();
} And similar notation: package example.micronaut;
import io.micronaut.data.annotation.Query;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;
import java.util.List;
@JdbcRepository(dialect = Dialect.H2)
@ParameterExpression(name = "tenanId", value = "#{...}")
public interface BookRepository extends CrudRepository<Book, Long>{
@Query(value = "SELECT * FROM book WHERE framework = :tenanId")
List<Book> findAllByTenant();
} WDYT @sdelamo @graemerocher ? |
|
seems reasonable, what about for generated queries? We probably need a way to include a |
btw I would probably call it |
Why would you use it without an expression? |
for example you might want to use a fixed tenancy: @ParameterValue(name = "tenantId", value = "default") |
Micronaut Data currently supports multi-tenancy via datasource or schema. Not sure how the API should look like for column discriminator multi tenancy .
But I assume something like:
and a JdbcRepository
for
bookRepository.findAll()
we should doselect * from book where publisher = 'xxx'
.We should leverage Micronaut Multitenancy for tenant resolution.
See GORM Multi-tenancy API for ideas.
The text was updated successfully, but these errors were encountered: