1 Windows Azure Multi-tenant applicatie Bouwen van een multi-tenant applicatie in de praktijk Edo van Asseldonk Ordina Microsoft Solutions Agenda • Introductie / Architectuur • URL strategie / Tenant id • Data opslag (SQL Azure, Table Storage) • Maatwerk (UI / Business Logica) • Cache • Access Control Service 3 Multitenancy Definitie 4 Multitenancy Definitie …single instance of software serving multiple clients (tenants)… …een enkele softwareinstantie die meerdere klanten bedient… 5 Multitenancy Definitie 6 Multitenancy Definitie 7 Multitenancy Definitie Iedere tenant heeft: • eigen data • eigen opmaak • eigen business logic • gemeenschappelijke codebase • gemeenschappelijk datamodel 8 Architectuur Overview Casus / Demo 9 Architectuur Overview 10 Table Storage ACS Webrole SQL Azure Architectuur Overview 11 Table Storage ACS Webrole SQL Azure Architectuur Overview Eisen: • Tenantapplicatie bereikbaar via eigen url • Eén database schema • Eigen stijl per tenant • Self service • Share everything 12 URL Strategie / Tenant id URL Strategie / Tenant id Opties 14 Optie 1: Tenant bepalen a.d.h.v. ingelogde gebruiker http://www.MyMultitenantApp.nl/Home Geen referentie naar naam van tenant URL Strategie / Tenant id Opties https://www.chatter.com/nl/ 15 URL Strategie / Tenant id Opties https://eu1.salesforce.com/home/home.jsp 16 URL Strategie / Tenant id Opties 17 Optie 2: TenantId in URL http://www.MyTenantApp.nl/Home?tenantId=17 http://www.MyTenantApp.nl/17/Home http://www.MyTenantApp.nl/A219F47cd-898B/Home http://www.MyTenantApp.nl/WazugNL/Home URL Strategie / Tenant id Opties Optie 3: Tenant bepalen a.d.h.v. URL http://tenant1.MyTenantApp.nl/Home http://tenant2.MyTenantApp.nl/Home 18 URL Strategie / Tenant id Opties Optie 3: Tenant bepalen a.d.h.v. URL http://tenant1.MyTenantApp.nl/Home http://tenant2.MyTenantApp.nl/Home http://www.customdomain.nl/ -> verwijst naar http://tenant2.MyTenantApp.nl 19 URL Strategie / Tenant id Opties http://gaming.stackexchange.com/ 20 URL Strategie / Tenant id Opties http://programmers.stackexchange.com/ 21 URL Strategie / Tenant id Opties http://www.stackoverflow.com/ 22 URL Strategie In Windows Azure [MyMultitenantApp].cloudapp.net 23 URL Strategie In Windows Azure [MyMultitenantApp].cloudapp.net [Tenant].cloudapp.net [Tenant].[MyMultitenantApp].cloudapp.net 24 URL Strategie In Windows Azure [MyMultitenantApp].cloudapp.net Domain name: MyMultitenantApp.nl 25 URL Strategie In Windows Azure [MyMultitenantApp].cloudapp.net Domain name: MyMultitenantApp.nl URLs: http://tenant1.MyMultitenantApp.nl http://tenant2.MyMultitenantApp.nl http://www.customdomain.nl 26 Data Database Database Introductie Alle tenants in dezelfde database Twee issues: • Limiet aan databasegrootte • Limiet aan verwerkingscapaciteit (CPU, I/O etc.) 28 Database Introductie Oplossing: • Database splitsen 29 Database Introductie Oplossing: • Database splitsen -> SQL Azure Federations 30 Database Introductie Database Introductie Database Introductie Database Introductie Database Introductie Database Introductie Database Introductie Database Introductie Database Introductie Database Introductie Database Introductie Database Introductie Database Introductie Database Introductie SQL Azure Federations Root database Root DB Root Table1 Root Table2 Root Table… 45 SQL Azure Federations Create federation Root DB Root Table1 Root Table2 Root Table… 46 Member DB SQL Azure Federations Create tables Root DB Root Table1 Root Table2 Root Table… 47 Member DB Member Table2 Member Table1 Member Table… SQL Azure Federations Split federation member Root DB Root Table1 Root Table2 Root Table… 48 Member DB Member Table1 Member Table2 Member Table… SQL Azure Federations Split federation member Root DB Root Table1 Root Table2 Root Table… 49 Member DB Member Table1 Member Table2 Member Table… SQL Azure Federations Split federation member 50 Member DB Member Table1 Root DB Root Table1 Root Table2 Root Table… Member DB Member Table1 Member Table2 Member Table… Member Table2 Member Table… Member DB Member Table1 Member Table2 Member Table… SQL Azure Federations Split federation member 51 Member DB Member Table1 Root DB Root Table1 Member Table2 Member Table… Root Table2 Root Table… Member DB Member Table1 Member Table2 Member Table… SQL Azure Federations Voordelen • DB Size van max 150 GB -> 75 TB • CPU Cores en I/O verveelvoudigd • Real-time • Geen downtime 52 SQL Azure Federations Voordelen • DB Size van max 150 GB -> 75 TB • CPU Cores en I/O verveelvoudigd • Real-time • Geen downtime 53 SQL Azure Federations Voordelen • DB Size van max 150 GB -> 75 TB • CPU Cores en I/O verveelvoudigd • Real-time • Geen downtime 54 SQL Azure Federations Voordelen • DB Size van max 150 GB -> 75 TB • CPU Cores en I/O verveelvoudigd • Real-time • Geen downtime 55 SQL Azure Federations Kosten 56 Sinds halverwege februari 2012 prijsverlaging. Tot 78% goedkoper. Hoe meer tenants per member database, hoe goedkoper. Eén database Meerdere databases FORMAAT KOSTEN FORMAAT KOSTEN 10 GB $45,95 p/m 10 x 1 GB 10 x $9.99 = $99,90 p/m SQL Azure Federations Kosten 57 Sinds halverwege februari 2012 prijsverlaging. Tot 78% goedkoper. Hoe meer tenants per member database, hoe goedkoper. Eén database Meerdere databases FORMAAT KOSTEN FORMAAT KOSTEN 10 GB $45,95 p/m 10 x 1 GB 10 x $9.99 = $99,90 p/m 50 GB $125,88 p/m 10 x 5 GB $259,80 p/m SQL Azure Federations Kosten 58 Sinds halverwege februari 2012 prijsverlaging. Tot 78% goedkoper. Hoe meer tenants per member database, hoe goedkoper. Eén database Meerdere databases FORMAAT KOSTEN FORMAAT KOSTEN 10 GB $45,95 p/m 10 x 1 GB 10 x $9.99 = $99,90 p/m 50 GB $125,88 p/m 10 x 5 GB $259,80 p/m 50 GB $125,88 p/m 50 x 1 GB $499,50 p/m SQL Azure Federations Kosten 59 Sinds halverwege februari 2012 prijsverlaging. Tot 78% goedkoper. Hoe meer tenants per member database, hoe goedkoper. Eén database Meerdere databases FORMAAT KOSTEN FORMAAT KOSTEN 10 GB $45,95 p/m 10 x 1 GB 10 x $9.99 = $99,90 p/m 50 GB $125,88 p/m 10 x 5 GB $259,80 p/m 50 GB $125,88 p/m 50 x 1 GB $499,50 p/m 150 GB $225,78 p/m 150 x 1 GB $1498,50 p/m SQL Azure Federations Samenwerking met Entity Framework var query = from o in _myDatabaseContext.Orders string federationCmdText = where o.TenantId = id "USE FEDERATION tenant_federation (tenantId = [id]) select o; WITH FILTERING=ON, RESET"; ((IObjectContextAdapter)myDatabaseContext).ObjectContext.Connection.Open(); _databaseContext.Database.ExecuteSqlCommand(federationCmdText); 60 SQL Azure Federations Geen Federations in SQL Server 2008R2 string federationCmdText = "USE FEDERATION tenant_federation (tenantId = [id]) WITH FILTERING=ON, RESET"; ((IObjectContextAdapter)myDatabaseContext).ObjectContext.Connection.Open(); _databaseContext.Database.ExecuteSqlCommand(federationCmdText); var query = from o in _myDatabaseContext.Orders where o.TenantId = id select o; 61 SQL Azure Federations Geen where-clause nodig in gefilterde verbinding string federationCmdText = "USE FEDERATION tenant_federation (tenantId = [id]) WITH FILTERING=ON, RESET"; ((IObjectContextAdapter)myDatabaseContext).ObjectContext.Connection.Open(); _databaseContext.Database.ExecuteSqlCommand(federationCmdText); var query = from o in _myDatabaseContext.Orders where o.TenantId = id select o; 62 SQL Azure Federations Entity Framework: geen support parallelle queries 63 Architectuur Overview - > Table Storage 64 Table Storage ACS Webrole SQL Azure Table storage Meten en Afrekenen Afrekenen op basis van verbruik (aantal page requests, aantal objecten etc.) Verbruik inzichtelijk maken voor tenants Statcounter.com is niet geschikt voor multitenancy 65 Maatwerk Maatwerk Maatwerk per tenant op twee gebieden: • User Interface • Business Logic 67 Maatwerk User Interface Tenant-logo <div id="header"> <a href="/" class="logo" style="background: url(@ViewBag.Logo) no-repeat;"> 68 Maatwerk User Interface Tenant-logo <div id="header"> <a href="/" class="logo" style="background: url(@ViewBag.Logo) no-repeat;"> Niet doen op deze manier: Kleuren Fonts Posities van html-elementen Etc. 69 Maatwerk User Interface - Themes 70 Maatwerk User Interface - Themes 71 Maatwerk User Interface - Themes 72 public class ThemesViewEngine : BuildManagerViewEngine { private readonly string _themeName; public ThemesViewEngine(string themeName) { _themeName = themeName; } protected override IView CreateView( ControllerContext controllerContext, string viewPath, string masterPath) { masterPath = string.Format("~/Themes/{0}/_Layout.cshtml", _themeName); return new RazorView(controllerContext, viewPath, masterPath, …); } Maatwerk User Interface - Themes 73 public class ThemesViewEngine : BuildManagerViewEngine { private readonly string _themeName; public ThemesViewEngine(string themeName) { _themeName = themeName; } protected override IView CreateView( ControllerContext controllerContext, string viewPath, string masterPath) { masterPath = string.Format("~/Themes/{0}/_Layout.cshtml", _themeName); return new RazorView(controllerContext, viewPath, masterPath, …); } Maatwerk User Interface - Themes 74 public class ThemesViewEngine : BuildManagerViewEngine { private readonly string _themeName; public ThemesViewEngine(string themeName) { _themeName = themeName; } protected override IView CreateView( ControllerContext controllerContext, string viewPath, string masterPath) { masterPath = string.Format("~/Themes/{0}/_Layout.cshtml", _themeName); return new RazorView(controllerContext, viewPath, masterPath, …); } Maatwerk User Interface - Themes 75 public class ThemesViewEngine : BuildManagerViewEngine { private readonly string _themeName; public ThemesViewEngine(string themeName) { _themeName = themeName; } protected override IView CreateView( ControllerContext controllerContext, string viewPath, string masterPath) { masterPath = string.Format("~/Themes/{0}/_Layout.cshtml", _themeName); return new RazorView(controllerContext, viewPath, masterPath, …); } Maatwerk User Interface - Themes 76 public class UseThemesViewEngine : ActionFilterAttribute { public ITenantSettings Settings { get; set; } public override void OnActionExecuting( ActionExecutingContext context) { ViewEngines.Engines.Clear(); var engine = new ThemesViewEngine(Settings.ThemeName); ViewEngines.Engines.Add(engine)); } } Maatwerk User Interface - Themes 77 public class UseThemesViewEngine : ActionFilterAttribute { public ITenantSettings Settings { get; set; } public override void OnActionExecuting(…) { ViewEngines.Engines.Clear(); var engine = new ThemesViewEngine(Settings.ThemeName); ViewEngines.Engines.Add(engine)); } } Maatwerk User Interface - Themes Public class HomeController : Controller { [UseThemesViewEngine] public ActionResult Index() { } } 78 Maatwerk User Interface - Themes 79 public class UseThemesViewEngine : ActionFilterAttribute { public ITenantSettings Settings { get; set; } public override void OnActionExecuting( ActionExecutingContext context) { ViewEngines.Engines.Clear(); var engine = new ThemesViewEngine(Settings.ThemeName); ViewEngines.Engines.Add(engine)); } } Maatwerk Maatwerk per tenant op twee gebieden: • User Interface • Business Logic 80 Maatwerk Business Logic 81 Probleem • Tenant settings moeten continue doorgegeven worden • If-statements en switch-statements door alle code verweven Maatwerk Business Logic var data = Foo.Bar(TenantSettings) MVC Controller 82 TenantSettings If(settings.IsGoldPartner) … else … Business Logic layer Maatwerk Business Logic var data = Foo.Bar(TenantSettings) MVC Controller 83 TenantSettings If(settings.IsGoldPartner) … else … Business Logic layer Maatwerk Business Logic var data = BLL.GetData(TenantId) MVC Controller 84 TenantId return DA.GetData(TenantId) Business Logic layer TenantId from t in table where tenantid == TenantId TenantId select t Data Access select * from table where tenantid = TenantId Database Maatwerk Business Logic var data = BLL.GetData(TenantId) MVC Controller 85 TenantId return DA.GetData(TenantId) Business Logic layer TenantId from t in table where tenantid == TenantId TenantId select t Data Access select * from table where tenantid = TenantId Database Maatwerk Business Logic public class BusinessLogic { public void DoeIets(string messageText) { var message = CreateMessage(messageText); var tenantSettings = TenantSettingsHelper.GetSettings(); if(tenantSettings.UseSpamFilter) { if(tenantSettings.SpamFilterType == Fast) { spamfilter = new FastSpamfilter().Execute(message); } else if(TenantSettings.SpamFilterType == Slow) { spamfilter = new SlowSpamfilter().Execute(message); } } SaveMessage(message); } 86 Maatwerk Business Logic IoC container (autofac): builder.Register(c => NewSpamfilter()) .As<ISpamfilter>; private Ispamfilter NewSpamfilter() { if(!TenantSettings.UseSpamFilter) return new NoSpamfilter(); if(TenantSettings.SpamFilterType == Fast) return new FastSpamfilter(); else if(TenantSettings.SpamFilterType == Slow) return new SlowSpamfilter(); 87 Maatwerk Business Logic public class BusinessLogic { ISpamfilter _spamfilter; public Messenger(ISpamfilter spamfilter) { _spamfilter = spamfilter } public void DoeIets(string messageText) { var message = CreateMessage(messageText); _spamfilter.Execute(message); SaveMessage(message); } 88 Cache 89 App Fabric Cache doet de basics Wissen van totale cache niet mogelijk Cache Geen persistentie Geen inzicht in bytes per tenant Versienummer in cachekey: Bijvoorbeeld [tenantId]_[objecttype]_[versienr]_[objectId] 2ED669F2-9EBC-4248-8BE2-56CDFDA89B75_employee_v1.1_924F32EA-7793-46C1-986E-69FF0857F39C 2ED669F2-9EBC-4248-8BE2-56CDFDA89B75_employee_v1.2_924F32EA-7793-46C1-986E-69FF0857F39C Cache • • • • App Fabric Cache doet de basics, maar niet meer dan dat. Wissen van totale cache niet mogelijk. Geen persistentie. Geen inzicht in bytes per tenant. Versienummer in cachekey: Bijvoorbeeld [tenantId]_[objecttype]_[versienr]_[objectId] 90 Access Control Service Access Control 91 Access Control Service Intro 92 Iedere tenant -> Relying Party Application in ACS Management Service (beschikbaar als Odata Service) Geen gebruikcijfers Access Control Service AudienceMode Na inloggen stuurt ACS een token naar de website. Check (url in token == AllowedAudienceUri) Uitzetten! <microsoft.identityModel> <service> <audienceUris mode="Never"> 93 Contact Bereikbaar voor vragen Edo van Asseldonk Email: [email protected] Twitter: @edovanasseldonk Blog: http://edo-van-asseldonk.blogspot.com 94 95 www.ordina.nl