Multi-Tenancy

40 views
Skip to first unread message

Dominik Dorn

unread,
Aug 27, 2017, 7:19:28 AM8/27/17
to Play framework dev
Hi all!

New client, new topics :) .. this time its about Multi-Tenancy. 

Client has the following requirements (that I know of till now) - and a lot of self-made solutions
for it already in place, but I think it would be good if we could somehow get this "standardised":

- Different domain names, localised routes
- Different message bundles and available locales per tenant
( - Implementation of the MessagesApi with ICU http://site.icu-project.org/ )
( - getting a list of supported locales to present the user)
( - locale encoded in route )
( - encoding the tenant in subdomains - Play 1.x had something like that in the routes file)
( - assets serving through CDN, different CDN-URL per tenant) 

As already said, there are already solutions for this in place (filters, regex in routes, alternative trait + impl of messagesApi).

Are there any plans to have a more or less standardised way of handling this in Play?
Do we want to provide such a way? 


best regards,
Dominik

James Roper

unread,
Aug 27, 2017, 10:05:42 PM8/27/17
to Dominik Dorn, Play framework dev
On the topic of routes - my experience with multi tenancy and routes is that no two multi tenancy requirements are the same. Sometimes it should be by domain. Sometimes by path. I've seen cookies used to implement multi tenancy, etc.  So, the question is can we provide an abstraction that both gives the power necessary for all the different multitenant routing scenarios, but would also be simpler than just using what already exists?  If it's not simpler than using what already exists, then we've introduced an abstraction that adds no value, at the cost of introducing a layer of opacity as to what's really happening (this is what abstractions tend to do), and that's really not a good thing.

So, let's have a look at a setup where you have a router that everyone uses, and then a localised router per tenant, and the tenant is decided based on the hostname.  Here's what it might look like:

class MultiTenantRouter(
  commonRouter: common.Routes,
  tenantARouter: tenanta.Routes,
  tenantBRouter: tenantb.Routes
) extends SimpleRouter {

  val routerMap = Map(
    "tenanta.com" -> tenantARouter.routes.orElse(commonRouter.routes),
    "tenantb.com" -> tenantBRouter.routes.orElse(commonRouter.routes)
  )

  override def routes = Function.unlift { req =>
    for {
      tenantRoutes <- routerMap.get(req.host)
      handler      <- tenantRoutes.lifted(req)
    } yield handler
  }
)

Is the above code going to be any less complex than the configuration to achieve the above?  If you wanted to allow for all the different strategies that people want, I'd say no, you're going to end up with a similar number of lines in config to the code above.  But the above is really simple, it's just partial function composition. If you have a couple of tenants that have a completely different routing requirement, no problem, just modify the code above.  If you want to use a more complex tenant selection strategy - maybe you want to do something different in development so that multiple tenants can be served at localhost, no problem, just modify the code.  Maybe you actually want a completely different set of components injected into the common router, all configured to be tenant aware - very straight forward to do, inject a factory method into the multitenant router, and perhaps each of these is instantiated by their own tenant cake.  You can do that.  It's all plain and simple to see how it works.  But as soon as you create an abstraction, try to standardise it, then you lose that power.  And it's ok to lose that power, but only if you're gaining something, if the abstraction implements something that's complex and hard to get right. But as you can see above, multi tenant routing is not complex and is not hard to get right.

So my thought on multi tenant routing features is that Play should not provide anything out of the box - what it already provides, which is based on partial functions, and so allows incredibly simple but powerful composition, is already sufficient.

As far as localisation is concerned, there are definitely opportunities to provide alternate MessagesApi implementations, but I'm not sure if there's anything that should be multi tenant specific.

Regards,

James

--
You received this message because you are subscribed to the Google Groups "Play framework dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-dev+unsub...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--

Dominik Dorn

unread,
Aug 28, 2017, 5:04:31 AM8/28/17
to James Roper, Play framework dev
Hmm very valid points.. how about we add this as an example 
to the documentation to show users how they can get started? 



To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-dev+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Play framework dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-dev+unsub...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Jean Helou

unread,
Aug 28, 2017, 5:39:10 AM8/28/17
to Dominik Dorn, James Roper, Play framework dev
Hello, 
One point I find interesting in Dominik's list is : 
-  localised routes

Since we had to write our own localized router and fight the framework a bit to handle partially localized routes (the result being a terrible migration story which means we are stuck on 2.4 for now)

For SEO purposes we want to translate parts of our URLs in the target language. In an ecommerce website we could have 
/order/*
/commande/*
/bestsellung/*
...
all pointing to the same services (locale has already been guessed from the domain or user preferences) 
 
what we did was write a custom router generator which understands

POST /%%order/ controllers.OrderController.createOrder()
GET /%%order/:id controllers.OrderController.getOrder(id:String)

And depending on the locale of the request will dynamically substitute the "%%order" by its translation while leaving the rest of the path untouched. Another possibility which we have not tried would be to generate all the possible routes at compile time. 

I am curious how this was solved in dominik case (if it was a similar  issue) and if the solution could be made generic.

cheers, 
jean

To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Play framework dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "Play framework dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages