Open vietk opened 3 years ago
Hi @vietk,
I have worked with Jakarta EE/MicroProfile-based multi-tenancy aware applications in the past. There was no real support from either Jakarta EE or MicroProfile available, so we implemented "current tenant" stuff ourselves. I must say that this was kind of straight-forward and relatively easy for the requirements at hand. Therefore, I've never thought about shaping this as a specification. I do like your way of thinking though, and would like to support any ideas for a specification. I guess we need to start writing down requirements for what we expect regarding multi-tenancy support and see how that can be implemented and standardized?
We probably need some implementation experience before actually adding it into the specification. I'm ok to add a feature like this into SR Config and try it out.
@vietk can you detail a bit more or add some expectations on how would you like to use config with multi-tenancy?
Thanks both of you for considering my proposal.
The main use case is to be able to override configuration depending a specific tenant, this configuration would be usable in the application itself for example using @ConfigProperty. The target property would take different values depending a "contextual information". This contextual information could be provided in a extensible way using, for example an abstraction such as a TenantResolver: depending the use case, the tenant can be guessed from a HTTP request header for example, if using JAX-RS endpoint. Note that's probably here where MP specifications could collaborate to provide concrete implementation of TenantResolvers.
So as you might understood, the ConfigProperty could be seen as a @RequestScoped object whose value need to computed upon each request/message. However this is causing some issues regarding how this ConfigProperty can be injected in another CDI bean whose scope is broader than @RequestScoped, i.e: @ApplicationScoped/@Singleton. If I am not mistaken we can only inject primitive types and thus it would not possible to recompute multi-tenant value. The only way is to use a proxy object to comply with the need.
Now let me give some example :
# default value
endpoint.property=default
# tenant-1
endpoint.property.tenant-1=tenant-1
# tenant-2
endpoint.property.tenant-2=tenant-2
Then the above property could used like that:
@ApplicationScoped
class Endpoint {
@Inject
@ConfigProperty(name = "endpoint.property")
Supplier<String> endpoint
public void doSomethingWithEndpoint() {
if (endpoint.get().equals("default") {
// default use case
}
else {
// ...
}
}
}
In the ideal world, a developer should not be aware about multi-tenancy in the code, so the usage Supplier
The above could be directly used by other MicroProfile specification like MP Rest microprofile in order to map a specific tenant for a specific target endpoint:
# default endpoint
mpClient.endpoint/mp-rest/url=http://target.url/default
# tenant-1 has different url
mpClient.endpoint.tenant-1/mp-rest/url=http://target.url/tenant-1
# and tenant-1 endpoint takes more time to answer
mpClient.endpoint.tenant-1/mp-rest/readTimeout=2000
And finally the same mechanism could be used to configure multi-tenant databases clients/drivers and select at runtime which client a tenant will use.
To illustrate my proposal you can have a look at micronaut implementation of multi-tenancy who implements tenant normalization for using different databases.
I hope I am clear enough ...
I totally went over the fact that, dynamic injection of property is already available in MP config :sweat_smile:
//Injects a Supplier for the value of myprj.some.supplier.timeout property to
//resolve the property dynamically. Each invocation to Supplier#get() will
//resolve the latest value from underlying Config.
@Inject
@ConfigProperty(name="myprj.some.supplier.timeout", defaultValue="100")
private java.util.function.Supplier<Long> timeout;
So to indicate that an property is multi-tenant, we would need a Qualifier annotation :
@Inject
@ConfigProperty(name = "endpoint.property")
@MultiTenant
Supplier<String> endpoint
@vietk thanks for explaining your use case! For the MultiTenant scenario, IIUC, it is very close to config profile support. The only difference is that the config property specified using the naming convention of %phase.property.name
. Can you try that out using %tenant.property.name
and then you set the tenant use mp.config.profile=tenant-1
to have the property tenenant-1.endpoint.property
injected?
I believe that ConfigValue
would be a better fit to support something like multi-tenancy.
Probably, a better fit is SR @ConfigMapping
. This is because is just a plain interface that the implementation could enhance to easily add the multi-tenancy support. The contract is clear and it would remove any source of confusion about direct injection of supported types.
@Emily-Jiang I saw config profile support, however the value of the tenant is dynamic and is based on external source : for example a http header
X-TENANT_ID: tenant-1
Sorry it's not possible to. use profile for the use case.
@radcortez I thought about @ConfigMapping too, I think it's a good idea. However I did not understood the remark about ConfigValue, can you elaborate?
In the picture we would need also a contract interface in MP Config, it's the TenantResolver, that could retrieve the current tenant information when resolving the ConfigMapping (if we stick to what you said) for a multi-tenant ConfigSource. The default implementation could be configured to implement the empty tenant (probably equals to "")
However I did not understood the remark about ConfigValue, can you elaborate?
The ConfigValue
acts as a config accessor, so it can encapsulate any logic of a TenantResolver
in the implementation. In the sense it acts as the Supplier
but with a proper API for Config:
@Inject
@ConfigProperty(name="my.prop")
ConfigValue value;
...
// internally this would route to the resolved tenant
value.getValue();
In the picture we would need also a contract interface in MP Config, it's the TenantResolver, that could retrieve the current tenant information when resolving the ConfigMapping (if we stick to what you said) for a multi-tenant ConfigSource. The default implementation could be configured to implement the empty tenant (probably equals to "")
Correct. A TenantResolver
could be part of the ConfigBuilder
and from that point forward if a TenantResolver
is available, the @ConfigMapping
implementation would use it to route to the correct configuration.
We would still need to come up with some sort of syntax to represent the tenant. Maybe it can be implemented on top of profiles and use the same concept as we have today.
Hello everybody,
I am currently evaluating technical solutions to achieve multi-tenancy within a Microprofile application and I think there's a gap that would be solved by making microprofile configuration tenant aware. By multi-tenancy, I am referring to the fact that an application is shared to support many tenants (clients) within the same Microprofile application instances, deployed as many pods on a kubernetes cluster for example. The purpose is to share and optimize the infracture costs among the clients that use the application.
First for a given Microprofile configuration property we want to have different values, whose depend on the "current Tenant", when handling a request inside our application. Also this application would need to communicate with external datasources such a JPA/hibernate or even MongoDatabase. The way to achieve multi-tenancy of these external resources is out of scope of my subject, it's a topic in itself. However these 'external resources' would need to be accessed in a tenant aware fashion, for example to support logical isolation (an Oracle schema or a Database in MongoDB) accessisble in the external datasource/resource.
In this area MicroProfile Config should play a central role: defining a standardised way of setting and retrieving and overrding tenant-aware configuration property.
Even if a bit different, some Quarkus properties (which follows Microprofile configuration for runtime properties) seems to already have started to use a kind of convention for multi-tenant configuration property :
OpenID connect multi-tenancy
This convention could server as a base for implementing multi-tenant configuration property.
I really want to know MicroProfilers's opinion on that subject :smiley:
Thanks and regards