\n
IClientDataProvider
.IClientDataProvider
. I have assumed you have separate business logic and UI layers - but that's kind of irrelevant to your question.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.
\nInterface & Data Provider Design - the diagram depicts the most basic solution, but it's easily extendable. For example, lets say you had two different concepts:
\nMake 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.
\nPro tip - if you haven't heard about it, definitely get familiar with the Interface Segregation Principle (ISP), for when you start designing the interfaces.
\nYou 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:
\nDTOs - 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.:
\nPizzaInfo info = IClientDataProvider.GetClientPizzaData(guid clientId)\nIClientDataProvider.Save(KittenInfo kitten);\n
\nThe 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.
\nChoosing 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.
\nhogwarts.holdencms.com = CMS.DataAccess.Clients.HogwartsDataProvider\nstarwars.holdencms.com = CMS.DataAccess.Clients.StarWarsDataProvider\nthunderbirds.holdencms.com = CMS.DataAccess.Clients.ThunderBirdsDataProvider\n
\nWhat 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.
\n","author":{"@type":"Person","name":"Adrian K"},"upvoteCount":1}}}Reputation: 177
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
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.
IClientDataProvider
.IClientDataProvider
. I have assumed you have separate business logic and UI layers - but that's kind of irrelevant to your question.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:
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:
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