views:

237

answers:

4

We're building about 10 ASP.NET MVC sites which have a common set of features (and corresponding URLs, Routes, Controllers, Actions, and Views). The sites will also all share a base set of domain objects (e.g. users, companies) and base attributes on those objects (e.g. name, address, etc.).

But each site will also be highly customized and extended from the base. For example, our site for large, public companies will have "Subsidiary" and "Stock Symbol" fields on the Company domain object, while our site for startups will have a "Venture Firm" and and "Funding" attributes. Look and feel will also vary considerably, although we're trying to keep HTML as consistent as possible (modulo extra form fields for extra domain object attributes, etc.). We'll also be overriding images sparingly, so we can, for example, re-use the same button graphics across sites.

Anyway, we're trying to figure out how best to factor and architect things so that we can reuse as much code and as many tests as possible without limiting our freedom to add per-app attributes and vary the UI between apps.

I'm familiar with how to handle limited-customization multi-tenancy like you find in StackOverflow/SuperUser/ServerFault (or MSDN/TechNet for that matter), where the UI is a little different and the data model is more-or-less identical. But when the models and UI are very different (but inherit from a common base), I'm less sure how to proceed.

I'm less worried about operational issues, since we'll probably be running each site in a separate appdomain and hosting them on separate databases. I'm more worried about reducing long-term code maintenance costs, increasing agility (e.g. easy to add new features to the base without breaking derived apps), and realizing short-term dev-/test-cost savings as we build our 2nd, 3rd, 4th, etc. site.

I'm looking both for high-level guidance and suggestions, but also concrete suggestions for how to make that guidance real using modern ASP.NET MVC practices.

I realize this is a very general question, but for starters I'm looking for both high-level guidance as well as concrete tips-n-tricks for how to apply that guidance with ASP.NET MVC, including things like:

  • recommendations where to split base/derived across Visual Studio projects
  • source control tips to avoid forking
  • database schema tips (FWIW, our databases are all small-- under 10K rows per table, so dev/test cost is more of an issue than DB perf)
  • tips about re-using Controllers/Views/etc. corresponding to the "base" model attributes, especially re-using UI for things like "new customer" forms which will have a mix of base and derived attributes.

Anyone have good advice for how to architect a multi-tenant app like this?

A: 

Mike Hadlow goes into good detail on how to accomplish this:

http://mikehadlow.blogspot.com/2008/11/multi-tenancy-part-1-strategy.html

Chris Missal
I'm not impressed...He talks a good talk, but it's all vaporware discussions without specifics, and there's no mention of the multi-tenancy strategy at all in his open-source project; it looks like he has not gotten the time to implement it.
Robert Harvey
+2  A: 

I'm involved in a similar type of "suite" of projects currently which is focused on allowing customers to apply for products online but have very similar requirements for what information to collect, where the only differences are around product specific pieces of information or slightly different legislative requirements.

One thing that we have tried to do is create pages (model, view and controller combinations) that are reusable in themselves, so any application can use the page to capture information but redirect to the next page which may be different depending on what type of product is being applied for. To achieve this we are using abstract base controllers in the form of the template method pattern that contain basically all the required controller logic (including action methods with their applied action filters) but then use abstract methods to do the specific stuff such as redirecting to the next page in the process. This means that the concrete implementation of the controller used by specific application page flows may contain only one method which returns a RedirectToActionResult corresponding to the next page in the flow. There is also quite a bit of other stuff that handles going backwards and those kinds of navigational things, but with the help of action filters you can get it set up that you don't have to worry about it once you get it up and working.

There are also base model objects which contains common functionality, be it validation logic or state persistence logic.

The data captured during the application process is persisted in database as xml serialized model objects which can then be pulled out and de-serialised once the application is completed and spat out in whatever format to whatever system the backend operations staff use to process applications.

The implications of this is that we have a project structure that consists of a base dll that contains top level abstract classes, interfaces and utility classes as well as html helpers, action filters etc. Then we have mvc projects which contain the concrete implementations of the base controllers, models etc as well as the views and masterpages.

The hardest thing is sharing views and I don't think we have properly got this sorted yet. Although with MVC 2.0 containing Areas I think this will become less of an issue but I haven't had a good play with it yet. (see Scott Gu's post on 2.0: http://weblogs.asp.net/scottgu/archive/2009/07/31/asp-net-mvc-v2-preview-1-released.aspx) One thing I have POCed that looks like it will work is using a base MVC project to contain common views and then extending the default view engine to search that project on the web server when looking for a view to render (which is quite easy to do). Areas though is a far nicer solution.

As for source control, we are using svn and I think you are reasonable in being concerned about branches. It is not something that we have had to deal with yet, but we are probably going to go with git as it seems to make the process of branching and merging much less painful.

Not sure whether this helps you much but I would definitely recommend keep in mind abstract controllers and models, and also look at how you can use html helpers and and partial views to group similar pieces of functionality.

Dean Johnston
Thanks Dean for the thorough response! I'm going to dig into your suggestions and probably build some prototypes to investigate.
Justin Grant
+1  A: 

One way to do this is to use branching in a source control system.

The main branch is for the common functionality. You then have a branch for customization and can merge changes out to the customization or back to the main branch.

Shiraz Bhaiji
funny, this is how I've been doing so far (on a prototype basis) but I assumed initially this approach would be hacky and brittle relative to more traditional software development approaches like interitance. Have you implemented customization using source control in projects with significant complexity?
Justin Grant
Yes, I know of one company that does this. They have versions of a project and customer customizations of each version. They have also built a system to keep track of which system is installed at each customer location, and which upgrade scripts need to be run between versions.
Shiraz Bhaiji
+1  A: 

Here's what we do, and it works pretty well for about 8 sites currently.

  • Define a core MVC project for your Controllers, ViewModels, HttpApplication, routes, etc. This will compile into a DLL and compromise the bulk of your site.

  • Create a basic set of default views, scripts, images, etc. for your site. These will server as defaults for your individual sites.

  • Per client, create any custom controllers, routes, etc that you'll need in a project that compiles to another dll.

  • Also per client, recreate any views, scripts, images that you'll want to use.

To make the above steps work together you'll need to write a little glue. The first piece of glue is a custom view engine. You'll want to customize the standard view engine to first look for views in your client-specific folder, and then the default folder. This lets you easily override the default layout per client.

The second method of getting everything working is to have your core application load the routes, controllers, etc from your client specific assembly. To do this I use the Managed Extensibility Framework (MEF) to expose a single Register method. Calling this method on my client assembly code registers the routes and any other client-specific needs.

Here's a general view of what my site folder structure looks like, with SiteContent being checked for views first:

  - AppContent
  - AppContent/Static
  - AppContent/Static/Images
  - AppContent/Static/Scripts
  - AppContent/Static/Styles
  - AppContent/Views
  - AppContent/Views/Shared

  - SiteContent
  - SiteContent/Static
  - SiteContent/Static/Images
  - SiteContent/Static/Scripts
  - SiteContent/Static/Styles
  - SiteContent/Views
  - SiteContent/Views/Shared

  - web.config
  - Global.asax

I have helpers that I can use like SiteImage and AppImage for use in my views. Also, I make each of my client sites use certain specific names for their master pages, that I don't ever define in my AppContent defaults.

I realize this is a rough overview, but it is working well enough for us right now.

Brian Vallelunga