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

Multi-tenancy support #729

Open
vietk opened this issue Nov 8, 2021 · 8 comments
Open

Multi-tenancy support #729

vietk opened this issue Nov 8, 2021 · 8 comments

Comments

@vietk
Copy link

vietk commented Nov 8, 2021

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 :

This convention could server as a base for implementing multi-tenant configuration property.

I really want to know MicroProfilers's opinion on that subject 😃

Thanks and regards

@ederks85
Copy link

ederks85 commented Nov 9, 2021

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?

@radcortez
Copy link
Contributor

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?

@vietk
Copy link
Author

vietk commented Nov 9, 2021

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 seems to break that promise but it matches with the intent.

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 ...

@vietk
Copy link
Author

vietk commented Nov 10, 2021

I totally went over the fact that, dynamic injection of property is already available in MP config 😅

//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

@Emily-Jiang
Copy link
Member

Emily-Jiang commented Nov 10, 2021

@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?

@radcortez
Copy link
Contributor

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.

@vietk
Copy link
Author

vietk commented Nov 10, 2021

@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 "")

@radcortez
Copy link
Contributor

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.

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

4 participants