I am in the process of developing a multi tenancy application with Jersey using Guice for DI (I also use Dropwizard but I don't think it matters here).
One thing that bothers me is the fact that some kind of tenancy_id
is all over the place in my application. Most of my URLs look like this:
/:tenancy_id/some_resource/do_stuff
. So the method in my Jersey resource is called with the tenancy_id
and hands it over to a service which calls other services and so on. These services are configured differently for different tenants.
I managed to resolve this problem by using a @RequestScoped
TenancyIdProdiver
:
public class TenancyIdProvider {
@Inject
public TenancyIdProvider(HttpServletRequest request) {
this.request = request;
}
public TenancyId getTenancyId() {
// extract the tenancy id from the request
}
}
`
My GuiceModule contains the following methods:
@RequestScoped
public TenancyId getTenancyId(TenancyIdProvider tenancyIdFactory) {
return tenancyIdFactory.getTenancyId();
}
public SomeTenancyService getTenancyId(TenancyId tenancyId, Configuration configuration) {
return new SomeTenancyService(configuration.getConfiguration(tenancyId));
}
So now I don't need to worry about proper configuration of my services. All is handled by the DI container and the application is tenant agnostic where it doesn't care about the tenant.
My question is though:
All these services and resources are created on every single request, since they all have a @RequestScoped
dependency. This is not feasible at all. So my idea was to create a custom scope with guice. So every tenant will get its own object graph with all resources and services properly configured (but only once). I tried it following the example here, but I am very unsure if this is even possible with Guice' custom scopes. Where do I need to enter my custom scope from a Jersey point of view? Is a ContainerRequestFilter
the right way to do it?
I finally figured it out by myself. The Guice page about custom scopes was a good starting point. I needed to tweak it a bit though.
First I've created a
@TenancyScoped
annotation:Then I used a request filter:
Please note the
@PreMatching
annotation. It is important that every request is filtered, otherwise your code might behave weirdly (scope could be set incorrectly).And here comes the
TenancyScope
implementation:The last step is to wire everything together in the Guice module:
What you have now is a scope that is set before each request and all instances the are provided by Guice are cached per tenancy id (also per thread, but that can be changed easily). Basically you have a object graph per tenant id (similar to having one per session e.g.).
Also notice, that the
TenancyScope
class acts both as aScope
and aTenancyId
provider.