Architecture – Designing a multi-tenant single page application for the web

Architecturemultitenancytypescriptweb-api

I'm looking at designing a multi-tenant application (well I believe it is multi-tenant). We have a single page application, an API and common database.

We have groups of users, each with their own slightly different requirements. A user cannot be in more than one group at the moment.

The key features we have are:

  • Searching: Search fields varying per group of users
  • Grid: Columns varying per group of users
  • Details panel: Varying fields to show for the result

There are some common properties in each key feature above, but some are completely unique to each group.

I have two main considerations here:

  1. Handling the UI views per user group
  2. Posting and serving back to the front-end an object with only the properties required.

We are using TypeScript + Angular 1 for the front end, and .NET Web API for the backend.

I want to avoid:

  • God objects that contain all the properties for all groups going back and forth over the wire.
  • Lots of if statements littered in the front-end code to show and hide based on the user group.

With this in mind, the current design I am considering is as follows:

  • Load different views per group at load-time of the front-end application
  • Bind different controllers depending on the view
  • Currently, this means the bundle of JS for all groups will be served to the client
  • This means we can have specific endpoints for the same operation, taking in specific request objects and returning specific objects.
  • From a folder structure perspective, separate by code feature, then group if it differs.

The advantages of this are:

  • Code specific to a group is easy to reason when it differs
  • Posting and receiving only what is relevant to the user.
  • If a user belongs to multiple groups, we can provide them a functionality to switch between the mini-applications by changing the active group they are in.

The disadvantages of this are:

  • A lot more plumbing on loading the views and controllers dependant on the group the logged in user falls into.
  • Serving all the front-end application code for all groups up front
  • Handling deep linking/routing for a group when a user belongs to multiple groups. I suppose this can be handled via querystring param. 🙁

Thoughts?

Update 27/07/2017

So using a made-up example:

The search criteria varies by having different fields per group.

  • Group a: Name, Age, DOB, Car type, Car engine size
  • Group b: Name, Age, Shoe size, shirt size, favourite colour

Extrapolating this, we have 8 groups, each having 10 common properties and 5 group specific properties. My conundrum is as follows:

If I go with a single search endpoint with a request object encapsulating all the properties for all groups. That's 50 properties (10 common + 5*8 group specific properties).

For any given group only 15 are relevant. I need to make the server-side code to use only the 15 appropriate ones and exclude the other 35.

Further more, the same occurs on the response. I will receive a large object, of which only a small subset contains data pertinent to the user in that group, the rest being null. I will have to place a fair amount of conditional statements to handle showing and hiding these things on the front-end side.

This is probably not considered multi-tenant, but I'm trying seek advice on a good design for this type of problem.

Best Answer

You've given scant details on your user base, but it sounds like you do not have a multi-tenant requirement. There are also few details into how your views differ between each other. On instinct, I would guess that you do not have mutli-tenant, but in fact have multiple groups. I would also guess that you are leaning towards a solution which will cause you to be angry with yourself later on. More specifically, separate implementations of each of the search view, grid view, and detail view.

I believe you should use standard user authentication via keystone, or keycloak, or another third party. Take advantage of their users / groups / roles to assign a hierarchy of permissions. Something like USER_BASIC_PERM would grant them access to an object's ID, Name, and Category. USER_CASH_PERM would grant then access to cost, value, profit margin. USER_ADMIN_PERM would let them see fields relating to 3rd party buyer, url to PO, etc. You can use groups to put mutliple role permissions together and manage users more easily with a single group.

Then you can build your three views by including / excluding sub-views based on the user's permissions. When they submit a search, you must validate their fields based on their roles server-side. When returning details, you must put it through a permission filter before returning it to the client. In the long run, this will be easier to maintain than implementing separate views for each of your tenants.

I think that if you consider that it's only permissions of a user that are the difference, then you'll see that the major functionality should be self-contained. 'search' would be one server-side module. Grid data access would be another. Keeping similar functionality together on the server-side will lead towards less repetition. Separating server-side by group would mean that you need to visit 3 separate modules to fix one bug that spans all of your 'grid' queries.

Think about long term - if you add 5 more tenants that are very close to the three you know about, which one will cause less pain to implement later.

Let's say you add a fourth view. Do you want to add it once with permission checks, or once for each of your now 8 tenants - even if most of them share the exact same view?

Related Topic