Chris Holden
Chris Holden

Reputation: 177

How to create a multi-tenant application where the databases dont match

I'm trying to come up with an approach for creating an application that serves as CMS for multiple clients, each client has its own database but so far as I can tell most of what we return on the front end should be the same.

So far I have decided that I will use Reactjs as a front-end as it will allow me to create flexibility where I need it whilst also being able to create reusable components.

The problem I am having is coming up with an approach for the backend, because non of the clients share an exact schema match, so whilst scaffolding dbcontexts each one must be created new per client in order to work.

My initial idea was just to run with this and create a dbcontext per client/database as an when I need to, and come up with some routing logic that will help me locate a context on a per request basis. However this could leave me writing a new data layer every time we get a new client which will make this application large and unwieldy, and require some messy controllers or services to manage it all.

My other idea is to create an API per client, and create a configurable call from the front end to get the correct API, but im not sure how secure this is and it doesn't seem all that sensible, but would leave my backend in small maintainable chunks.

Any advice on either of these approaches or potentially another approach that would be better?

Upvotes: 1

Views: 283

Answers (1)

Adrian K
Adrian K

Reputation: 10227

I know this horse has bolted (at least for now) but avoiding different schema's would save you a world of pain. Next best is to have a common schema for 80% of the solution, and carefully manage how you handle the variable part.

In terms of your actual question, in isolation... it sounds like Dependency Injection might be your friend in this case.

The general architecture looks something like this. There a few different things going on, let me explain them each in turn.

enter image description here

  1. Each client has it's own database and corresponding data access code; every instance of the data access code implements the same interface: IClientDataProvider.
  2. All code north of the data access code programs against IClientDataProvider. I have assumed you have separate business logic and UI layers - but that's kind of irrelevant to your question.
  3. The DI (Dependency Injection) sub-system decides at runtime which actual data access code (and therefore also which database) to load up.

The assumption here is that regardless of different schemas, those differences can be handled by the data access code, and work to a single consistent interface.

Interface & Data Provider Design - the diagram depicts the most basic solution, but it's easily extendable. For example, lets say you had two different concepts:

  • Client profile data (name, address, favorite colour, etc).
  • Client transport network data (different across clients).

Make two different types of interfaces, ones for the common stuff (profile data), and ones that handle the different stuff (transport network data). The common ones only requires you to develop one implementation of each, so you only need to write duplicate code for interfaces where it's necessary.

Pro tip - if you haven't heard about it, definitely get familiar with the Interface Segregation Principle (ISP), for when you start designing the interfaces.

You might be asking, if there's only ever one implementation of the common stuff, do you need to use interfaces? You don't, you can work with the objects directly - but:

  • Using interfaces across the board means the architecture and code is more consistent, easier to maintain over time.
  • Having interfaces in-place makes the solution easier to change in the long run. E.g. let's say you want to change data provider technology? Or client demands require you to make differences in areas that previously were the same.

DTOs - to get data around the place, create a suite of totally dumb objects that hold information; the system uses these to move data around. E.g.:

PizzaInfo info = IClientDataProvider.GetClientPizzaData(guid clientId)
IClientDataProvider.Save(KittenInfo kitten);

The simplest solution is to make one set of those that all layers use. You can obviously choose to do variations on that if you wish.

Choosing which data provider at runtime - depends on how users access the tenant, I guess. E.g. have something (probably in the DI sub-system) which is passed the URL, and makes a call as to which provider is loaded up.

hogwarts.holdencms.com = CMS.DataAccess.Clients.HogwartsDataProvider
starwars.holdencms.com = CMS.DataAccess.Clients.StarWarsDataProvider
thunderbirds.holdencms.com = CMS.DataAccess.Clients.ThunderBirdsDataProvider

What if differences between clients can't be contained within a single set of interfaces? Two parts to that answer: at a high-level re-use the same approach / principles but use it to load up different business logic classes and/or different UI widgets. Second part, it's not an area I have hands-on experience with but I'm pretty sure most decent UI frameworks will have stuff to help you do that sort of thing.

Upvotes: 1

Related Questions