From 3b4391ae10087cfa75a7648c9727e18774d0622a Mon Sep 17 00:00:00 2001 From: Chris Tilden Date: Mon, 2 Jan 2012 23:02:01 -0800 Subject: [PATCH 1/4] fixes accidental documentation revert commit 190bed38faa2b81074c7ce514e44e20243657e5e "reverting code formatting" also accidentally removed the latest documentation. this commit corrects that --- src/docs/guide/2. Multi-Tenant Modes.gdoc | 2 +- .../2.1 Multi-Tenant Database Set Up.gdoc | 25 +++-- .../2.1.1 Multi-Tenant mode gotchas.gdoc | 5 + .../2.2 Single-Tenant Database Set Up.gdoc | 95 +++++++++++++++++++ src/docs/guide/3.1 DNS Resolver Set Up.gdoc | 4 +- 5 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 src/docs/guide/2.1.1 Multi-Tenant mode gotchas.gdoc create mode 100644 src/docs/guide/2.2 Single-Tenant Database Set Up.gdoc diff --git a/src/docs/guide/2. Multi-Tenant Modes.gdoc b/src/docs/guide/2. Multi-Tenant Modes.gdoc index e4ff47b..b8b488a 100644 --- a/src/docs/guide/2. Multi-Tenant Modes.gdoc +++ b/src/docs/guide/2. Multi-Tenant Modes.gdoc @@ -8,4 +8,4 @@ tenant { } {code} -{warn}multiTenant is the default{warn} \ No newline at end of file +{warning}multiTenant is the default{warning} \ No newline at end of file diff --git a/src/docs/guide/2.1 Multi-Tenant Database Set Up.gdoc b/src/docs/guide/2.1 Multi-Tenant Database Set Up.gdoc index 9154a9a..f16715a 100644 --- a/src/docs/guide/2.1 Multi-Tenant Database Set Up.gdoc +++ b/src/docs/guide/2.1 Multi-Tenant Database Set Up.gdoc @@ -1,19 +1,28 @@ -h2. Annotate Domain Classes +The 'multiTenant' mode relies your application to use just one database for all tenants running. This is useful when you have a SaaS that allows user to sign up online and have their tenants running just after signing up. This is the simpler approach on multi-tenancy. -As of 0.10, you only have to annotate domain classes that you want to be multi-tenant. Any domain classes that you want to be shared, such as acegi Role/Requestmap can be left without the annotation. +First thing to do, is to tell the plugin witch classes will be treated as multi-tenant ones. You can do it, but annotating your domain classes with the *@MultiTenant* annotation, as show below: {code} -import grails.plugin.multitenant.core.groovy.compiler.MultiTenant; +import grails.plugin.multitenant.core.groovy.compiler.MultiTenant -@MultiTenant class MyDomainClass { +@MultiTenant +class MyDomainClass { } {code} -Because the plugin hooks into the compiler, make sure to do a "grails clean" after you've annotated your classes. Eventually, we'd like to get rid of this step. +This way, each instance of MyDomainClass will belong to some tenant. To achieve this, the plugin hooks into all hibernate events that query your database, adding another condition in your queries that will filter just instances of the current tenant. -h2. A note on indexing in multi-tenant environment +For example, imagine you have 2 tenants, each one with 2 instances of MyDomainClass. When the app is running on tenant 1, calling -Indexing a multi-tenant database is a tricky beast. If you have large datasets (100MM-1BB), prepare yourself for some fun. Google multi-tenant databases and start reading. :) +{code} +def lst = MyDomainClass.list() +{code} + +will return just its 2 instances, even if you have 4 in your database. + +The plugin hooks either on save/update events, setting the value of tenantId property automagically. -The short of it is that it's probably most efficient to create multi-column indexes that always include tenantId, that way the db can limit the working set to a single tenant before it starts filtering on another field. \ No newline at end of file +{warning} +You don't need to declare the 'tenantId' property, the plugin even auto-inject it for you, so, keep your domain class code clean +{warning} \ No newline at end of file diff --git a/src/docs/guide/2.1.1 Multi-Tenant mode gotchas.gdoc b/src/docs/guide/2.1.1 Multi-Tenant mode gotchas.gdoc new file mode 100644 index 0000000..eb9f789 --- /dev/null +++ b/src/docs/guide/2.1.1 Multi-Tenant mode gotchas.gdoc @@ -0,0 +1,5 @@ +h3. A note on indexing in multi-tenant environment + +Indexing a multi-tenant database is a tricky beast. If you have large datasets (100MM-1BB), prepare yourself for some fun. Google 'multi-tenant databases' and start reading. :) + +The short of it is that it's probably most efficient to create multi-column indexes that always include tenantId, that way the db can limit the working set to a single tenant before it starts filtering on another field. \ No newline at end of file diff --git a/src/docs/guide/2.2 Single-Tenant Database Set Up.gdoc b/src/docs/guide/2.2 Single-Tenant Database Set Up.gdoc new file mode 100644 index 0000000..ee3f7e1 --- /dev/null +++ b/src/docs/guide/2.2 Single-Tenant Database Set Up.gdoc @@ -0,0 +1,95 @@ +The single-tenant option is a little more immature than the multi-tenant option. It inherits all datasource configuration from the default datasource in Datasource.groovy, and allows you to provide a custom datasource url per tenant. This means that you can't mix and match drivers or user/pass combos for different tenants in the same instance. + +{warning} +TenantUtils.doWithTenant and currentTenant.set() do not work in single tenant mode, unless you manually create a new hibernate session and bind it to the current thread. + +Also, normal second-level caches will not work in single-tenant mode, as you will get primary key collisions across databases. You'll need to use a multi-tenant wrapper for your cache implementation (see below) +{warning} + +The plugin supports the following types of datasources: + +* jndi +* pooled (dbcp) +* regular (DriverManagerDataSource) +* TransactionAwareDataSourceProxy (Default for 1.3.X JNDI support only for now.) + +h2. Resolving the DataSource + +Note: Everywhere in the documentation that you see a datasource url, you can also use a jndiName. If the system encounters a tenant you haven't mapped, it will use the default datasource configuration in Datasource.groovy. This includes all database operations that happen before grails is completely loaded. + +h3. Mapping DataSources through Config.groovy + +To map datasource urls in Config.groovy, add the following code: + +{code} +tenant { + mode = "singleTenant" + datasourceResolver.type = "config" //This is the default and can be omitted + dataSourceTenantMap { + + //The "t" before the tenantId is required because you can't have a + //variable that's a number + t1 = "jdbc:mysql://localhost/ets_dev1" + t2 = "jdbc:mysql://localhost/ets_dev2" + t3 = "jdbc:mysql://localhost/ets_dev3" + + //JNDI Example + t1 = "java:comp/env/myDataSource" + } +} +{code} + +h3. Mapping DataSources through the database + +Mapping data sources through the database allows them to be provisioned on the fly without any restarting or reloading. + +Add the following line to Config.groovy + +{code} +tenant { + mode = "singleTenant" + datasourceResolver.type = "db" +} +{code} + +Then run the following command: + +{code} +grails create-data-source-map +{code} + +This will create a domain class used to store the mappings (tenant.DataSourceTenantMap). You can add a new mapping on the fly like this: + +{code} +def dsMap = new DataSourceTenantMap() +dsMap.mappedTenantId = 5 +dsMap.dataSource = "jdbc:mysql://myserver/customerdb" + +//Jndi Example +//dsMap.dataSource = "java:comp/env/myDataSource" +dsMap.save() +{code} + +When the record is saved, the datasource resolver will be automatically updated, and the app can start accessing the new datasource immediately + +h2. 2nd-Level Caching + +As mentioned above, regular 2nd level caches will not work in single-tenant mode, as you will get primary key collisions across databases. You will need to use a special wrapped version of the cache that makes it tenant-aware. The wrapped versions should maintain all your settings, but each tenant will maintain its OWN cache instances. This means that if can't simply set cache settings to be applied to the entire JVM; you'll have to 'divide' the settings among tenants. To set this update your datasource.groovy file to set + +{code} +cache.provider_class = 'grails.plugin.multitenant.oscache.cache.MultiTenantOSCacheProvider' +{code} + +or + +{code} +cache.provider_class = 'grails.plugin.multitenant.ehcache.cache.MultiTenantEhCacheProvider' +{code} + +h3. OSCache + +If you use the OpenSymphony cache provider (org.hibernate.cache.OSCacheProvider), you should use *grails.plugin.multitenant.oscache.cache.MultiTenantOSCacheProvider* instead. + +h3. EHCache(New with 0.18) + +If you are using the EHCache cache provider your should use *grails.plugin.multitenant.ehcache.cache.MultiTenantEhCacheProvider* insead. \ No newline at end of file diff --git a/src/docs/guide/3.1 DNS Resolver Set Up.gdoc b/src/docs/guide/3.1 DNS Resolver Set Up.gdoc index 2f01fc5..7d3a4e7 100644 --- a/src/docs/guide/3.1 DNS Resolver Set Up.gdoc +++ b/src/docs/guide/3.1 DNS Resolver Set Up.gdoc @@ -39,13 +39,13 @@ grails create-dns-map h2. Security and TenantId -{warn} +{warning} One advantage of identifying tenants by domain name is that any session cookies will be unique per domain, helping with security. This means that if a user authenticates (and gets an authentication cookie), if he changes the domain he's using to access the application, he won't stay logged on because the cookie for the old domain won't apply. This does have a loophole, though. Because the cookie lives on the client browser, it may be possible for the user to inspect the cookie, and copy any values such as jsessionid to a cookie for a different domain, thus gaining access to another tenant's data. It really depends on how secure your cookie is (does an authentication token only work for one domain?) The multi-tenant plugin uses a very simple messaging system from the falcone-util plugin. Whenever the tenantId changes, a message is published with the new and old tenantIds. You can use this event to perform any necessary checks, and clean up any resources you don't want around when the tenantId changes. Example as follows: -{warn} +{warning} {code} //A sample class that might contain an authenticated user From a2ed804f297c49f805519958443d55a2445d92f3 Mon Sep 17 00:00:00 2001 From: Chris Tilden Date: Mon, 2 Jan 2012 23:10:57 -0800 Subject: [PATCH 2/4] removes duplicate doc file --- .../2.2 Single Tenant Database Set Up.gdoc | 95 ------------------- 1 file changed, 95 deletions(-) delete mode 100644 src/docs/guide/2.2 Single Tenant Database Set Up.gdoc diff --git a/src/docs/guide/2.2 Single Tenant Database Set Up.gdoc b/src/docs/guide/2.2 Single Tenant Database Set Up.gdoc deleted file mode 100644 index 140e753..0000000 --- a/src/docs/guide/2.2 Single Tenant Database Set Up.gdoc +++ /dev/null @@ -1,95 +0,0 @@ -The single-tenant option is a little more immature than the multi-tenant option. It inherits all datasource configuration from the default datasource in Datasource.groovy, and allows you to provide a custom datasource url per tenant. This means that you can't mix and match drivers or user/pass combos for different tenants in the same instance. - -{warn} -TenantUtils.doWithTenant and currentTenant.set() do not work in single tenant mode, unless you manually create a new hibernate session and bind it to the current thread. - -Also, normal second-level caches will not work in single-tenant mode, as you will get primary key collisions across databases. You'll need to use a multi-tenant wrapper for your cache implementation (see below) -{warn} - -The plugin supports the following types of datasources: - -* jndi -* pooled (dbcp) -* regular (DriverManagerDataSource) -* TransactionAwareDataSourceProxy (Default for 1.3.X JNDI support only for now.) - -h2. Resolving the DataSource - -Note: Everywhere in the documentation that you see a datasource url, you can also use a jndiName. If the system encounters a tenant you haven't mapped, it will use the default datasource configuration in Datasource.groovy. This includes all database operations that happen before grails is completely loaded. - -h3. Mapping DataSources through Config.groovy - -To map datasource urls in Config.groovy, add the following code: - -{code} -tenant { - mode = "singleTenant" - datasourceResolver.type = "config" //This is the default and can be omitted - dataSourceTenantMap { - - //The "t" before the tenantId is required because you can't have a - //variable that's a number - t1 = "jdbc:mysql://localhost/ets_dev1" - t2 = "jdbc:mysql://localhost/ets_dev2" - t3 = "jdbc:mysql://localhost/ets_dev3" - - //JNDI Example - t1 = "java:comp/env/myDataSource" - } -} -{code} - -h3. Mapping DataSources through the database - -Mapping data sources through the database allows them to be provisioned on the fly without any restarting or reloading. - -Add the following line to Config.groovy - -{code} -tenant { - mode = "singleTenant" - datasourceResolver.type = "db" -} -{code} - -Then run the following command: - -{code} -grails create-data-source-map -{code} - -This will create a domain class used to store the mappings (tenant.DataSourceTenantMap). You can add a new mapping on the fly like this: - -{code} -def dsMap = new DataSourceTenantMap() -dsMap.mappedTenantId = 5 -dsMap.dataSource = "jdbc:mysql://myserver/customerdb" - -//Jndi Example -//dsMap.dataSource = "java:comp/env/myDataSource" -dsMap.save() -{code} - -When the record is saved, the datasource resolver will be automatically updated, and the app can start accessing the new datasource immediately - -h2. 2nd-Level Caching - -As mentioned above, regular 2nd level caches will not work in single-tenant mode, as you will get primary key collisions across databases. You will need to use a special wrapped version of the cache that makes it tenant-aware. The wrapped versions should maintain all your settings, but each tenant will maintain its OWN cache instances. This means that if can't simply set cache settings to be applied to the entire JVM; you'll have to 'divide' the settings among tenants. To set this update your datasource.groovy file to set - -{code} -cache.provider_class = 'grails.plugin.multitenant.oscache.cache.MultiTenantOSCacheProvider' -{code} - -or - -{code} -cache.provider_class = 'grails.plugin.multitenant.ehcache.cache.MultiTenantEhCacheProvider' -{code} - -h3. OSCache - -If you use the OpenSymphony cache provider (org.hibernate.cache.OSCacheProvider), you should use *grails.plugin.multitenant.oscache.cache.MultiTenantOSCacheProvider* instead. - -h3. EHCache(New with 0.18) - -If you are using the EHCache cache provider your should use *grails.plugin.multitenant.ehcache.cache.MultiTenantEhCacheProvider* insead. \ No newline at end of file From 6da7e9668e5b88abb289abb74555c9eab88d492a Mon Sep 17 00:00:00 2001 From: Chris Tilden Date: Mon, 2 Jan 2012 23:13:33 -0800 Subject: [PATCH 3/4] correct documentation names for table of contents --- .../{3 Spring Integration.gdoc => 3. Spring Integration.gdoc} | 0 ...a Tenant Resolver.gdoc => 4. Configure a Tenant Resolver.gdoc} | 0 ...{3.1 DNS Resolver Set Up.gdoc => 4.1 DNS Resolver Set Up.gdoc} | 0 ... Acegi Resolver Set Up.gdoc => 4.2 Acegi Resolver Set Up.gdoc} | 0 ...ustom Tenant Resolver.gdoc => 4.3 Custom Tenant Resolver.gdoc} | 0 ...lly changing tenant.gdoc => 4.4 Manually changing tenant.gdoc} | 0 ... Tenant Status Checker.gdoc => 4.5 Tenant Status Checker.gdoc} | 0 .../{4. Behind the scenes.gdoc => 5. Behind the scenes.gdoc} | 0 src/docs/guide/{5. Release Notes.gdoc => 6. Release Notes.gdoc} | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename src/docs/guide/{3 Spring Integration.gdoc => 3. Spring Integration.gdoc} (100%) rename src/docs/guide/{3. Configure a Tenant Resolver.gdoc => 4. Configure a Tenant Resolver.gdoc} (100%) rename src/docs/guide/{3.1 DNS Resolver Set Up.gdoc => 4.1 DNS Resolver Set Up.gdoc} (100%) rename src/docs/guide/{3.2 Acegi Resolver Set Up.gdoc => 4.2 Acegi Resolver Set Up.gdoc} (100%) rename src/docs/guide/{3.3 Custom Tenant Resolver.gdoc => 4.3 Custom Tenant Resolver.gdoc} (100%) rename src/docs/guide/{3.4 Manually changing tenant.gdoc => 4.4 Manually changing tenant.gdoc} (100%) rename src/docs/guide/{3.5 Tenant Status Checker.gdoc => 4.5 Tenant Status Checker.gdoc} (100%) rename src/docs/guide/{4. Behind the scenes.gdoc => 5. Behind the scenes.gdoc} (100%) rename src/docs/guide/{5. Release Notes.gdoc => 6. Release Notes.gdoc} (100%) diff --git a/src/docs/guide/3 Spring Integration.gdoc b/src/docs/guide/3. Spring Integration.gdoc similarity index 100% rename from src/docs/guide/3 Spring Integration.gdoc rename to src/docs/guide/3. Spring Integration.gdoc diff --git a/src/docs/guide/3. Configure a Tenant Resolver.gdoc b/src/docs/guide/4. Configure a Tenant Resolver.gdoc similarity index 100% rename from src/docs/guide/3. Configure a Tenant Resolver.gdoc rename to src/docs/guide/4. Configure a Tenant Resolver.gdoc diff --git a/src/docs/guide/3.1 DNS Resolver Set Up.gdoc b/src/docs/guide/4.1 DNS Resolver Set Up.gdoc similarity index 100% rename from src/docs/guide/3.1 DNS Resolver Set Up.gdoc rename to src/docs/guide/4.1 DNS Resolver Set Up.gdoc diff --git a/src/docs/guide/3.2 Acegi Resolver Set Up.gdoc b/src/docs/guide/4.2 Acegi Resolver Set Up.gdoc similarity index 100% rename from src/docs/guide/3.2 Acegi Resolver Set Up.gdoc rename to src/docs/guide/4.2 Acegi Resolver Set Up.gdoc diff --git a/src/docs/guide/3.3 Custom Tenant Resolver.gdoc b/src/docs/guide/4.3 Custom Tenant Resolver.gdoc similarity index 100% rename from src/docs/guide/3.3 Custom Tenant Resolver.gdoc rename to src/docs/guide/4.3 Custom Tenant Resolver.gdoc diff --git a/src/docs/guide/3.4 Manually changing tenant.gdoc b/src/docs/guide/4.4 Manually changing tenant.gdoc similarity index 100% rename from src/docs/guide/3.4 Manually changing tenant.gdoc rename to src/docs/guide/4.4 Manually changing tenant.gdoc diff --git a/src/docs/guide/3.5 Tenant Status Checker.gdoc b/src/docs/guide/4.5 Tenant Status Checker.gdoc similarity index 100% rename from src/docs/guide/3.5 Tenant Status Checker.gdoc rename to src/docs/guide/4.5 Tenant Status Checker.gdoc diff --git a/src/docs/guide/4. Behind the scenes.gdoc b/src/docs/guide/5. Behind the scenes.gdoc similarity index 100% rename from src/docs/guide/4. Behind the scenes.gdoc rename to src/docs/guide/5. Behind the scenes.gdoc diff --git a/src/docs/guide/5. Release Notes.gdoc b/src/docs/guide/6. Release Notes.gdoc similarity index 100% rename from src/docs/guide/5. Release Notes.gdoc rename to src/docs/guide/6. Release Notes.gdoc From a6de18bc2c676eebb7c95b4fd1cfbe10280d4945 Mon Sep 17 00:00:00 2001 From: Chris Tilden Date: Mon, 2 Jan 2012 23:30:16 -0800 Subject: [PATCH 4/4] corrects typos and grammar in the documentation --- src/docs/guide/2.1 Multi-Tenant Database Set Up.gdoc | 6 +++--- src/docs/guide/2.2 Single-Tenant Database Set Up.gdoc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/docs/guide/2.1 Multi-Tenant Database Set Up.gdoc b/src/docs/guide/2.1 Multi-Tenant Database Set Up.gdoc index f16715a..a3dbb06 100644 --- a/src/docs/guide/2.1 Multi-Tenant Database Set Up.gdoc +++ b/src/docs/guide/2.1 Multi-Tenant Database Set Up.gdoc @@ -1,6 +1,6 @@ The 'multiTenant' mode relies your application to use just one database for all tenants running. This is useful when you have a SaaS that allows user to sign up online and have their tenants running just after signing up. This is the simpler approach on multi-tenancy. -First thing to do, is to tell the plugin witch classes will be treated as multi-tenant ones. You can do it, but annotating your domain classes with the *@MultiTenant* annotation, as show below: +First tell the plugin which classes will be treated as multi-tenant ones. You can do it by annotating your domain classes with the *@MultiTenant* annotation, as show below: {code} import grails.plugin.multitenant.core.groovy.compiler.MultiTenant @@ -24,5 +24,5 @@ will return just its 2 instances, even if you have 4 in your database. The plugin hooks either on save/update events, setting the value of tenantId property automagically. {warning} -You don't need to declare the 'tenantId' property, the plugin even auto-inject it for you, so, keep your domain class code clean -{warning} \ No newline at end of file +You do not need to declare the 'tenantId' property, the plugin even auto-injects it for you, to keep your domain class code clean +{warning} diff --git a/src/docs/guide/2.2 Single-Tenant Database Set Up.gdoc b/src/docs/guide/2.2 Single-Tenant Database Set Up.gdoc index ee3f7e1..9e5ca56 100644 --- a/src/docs/guide/2.2 Single-Tenant Database Set Up.gdoc +++ b/src/docs/guide/2.2 Single-Tenant Database Set Up.gdoc @@ -92,4 +92,4 @@ If you use the OpenSymphony cache provider (org.hibernate.cache.OSCacheProvider) h3. EHCache(New with 0.18) -If you are using the EHCache cache provider your should use *grails.plugin.multitenant.ehcache.cache.MultiTenantEhCacheProvider* insead. \ No newline at end of file +If you are using the EHCache cache provider your should use *grails.plugin.multitenant.ehcache.cache.MultiTenantEhCacheProvider* instead.