Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Multi-tenancy with Rails

Multi-tenancy with Rails

Dimiter Petrov

September 14, 2016
Tweet

More Decks by Dimiter Petrov

Other Decks in Programming

Transcript

  1. What is multi-tenancy? A software architecture where a single instance

    of a software application serves multiple customers. A customer is called a tenant in this architecture.
  2. Advantages/problems/compromises Deploy once to update everyone's software, but there is

    single point of failure for all tenants. Shared development and maintenance costs,
  3. Advantages/problems/compromises Deploy once to update everyone's software, but there is

    single point of failure for all tenants. Shared development and maintenance costs, but customer-specific features are burdensome.
  4. Advantages/problems/compromises Deploy once to update everyone's software, but there is

    single point of failure for all tenants. Shared development and maintenance costs, but customer-specific features are burdensome. Single codebase,
  5. Advantages/problems/compromises Deploy once to update everyone's software, but there is

    single point of failure for all tenants. Shared development and maintenance costs, but customer-specific features are burdensome. Single codebase, but with configuration overhead.
  6. Advantages/problems/compromises Deploy once to update everyone's software, but there is

    single point of failure for all tenants. Shared development and maintenance costs, but customer-specific features are burdensome. Single codebase, but with configuration overhead. However, after configuration, development is the same as for a classic, single- tenant application.
  7. Data separation strategies Application-level: in the controller and sometimes the

    model Database-level: different databases or different PostgreSQL schemas
  8. PostgreSQL schemas From the docs: A database contains one or

    more named schemas, which in turn contain tables. Schemas also contain other kinds of named objects, including data types, functions, and operators. The same object name can be used in different schemas without conflict http://www.postgresql.org/docs/9.5/static/ddl-schemas.html There is a default p u b l i c schema, but you can create your own.
  9. Schema example # c r e a t e s

    c h e m a m y s c h e m a ; # c r e a t e t a b l e m y s c h e m a . t h i n g s ( i d s e r i a l ) ;
  10. Schema search path Used to automatically infer which table is

    meant without specifying it explicitly. # s h o w s e a r c h _ p a t h ; s e a r c h _ p a t h - - - - - - - - - - - - - - - - " $ u s e r " , p u b l i c # s e t s e a r c h _ p a t h t o m y s c h e m a , p u b l i c ; # s h o w s e a r c h _ p a t h ; s e a r c h _ p a t h - - - - - - - - - - - - - - - - - - m y s c h e m a , p u b l i c
  11. Schemas for data separation 1. Create one schema per tenant

    2. Create all tables in all schemas 3. Detect tenant on every request and change the schema search path
  12. Gems a p a r t m e n t

    Multitenancy for Rails and ActiveRecord a p a r t m e n t - s i d e k i q Support for Sidekiq with the a p a r t m e n t gem
  13. Migrations Handled by a p a r t m e

    n t which runs the migration for each tenant.
  14. Seeds # s e e d s . r b

    r e q u i r e ' a c t i v e _ r e c o r d / f i x t u r e s ' r e q u i r e R a i l s . r o o t . j o i n ( ' l i b / f i x t u r e _ l o a d e r ' ) f i x t u r e s _ d i r e c t o r y = R a i l s . r o o t . j o i n ( " d b / s e e d s / # { A p a r t m e n t : : T e n a n t . c u r r e n t } " ) F i x t u r e L o a d e r : : l o a d _ d i r e c t o r y ( f i x t u r e s _ d i r e c t o r y ) # l i b / f i x t u r e _ l o a d e r . r b c l a s s F i x t u r e L o a d e r d e f s e l f . l o a d _ d i r e c t o r y ( d i r e c t o r y ) u n l e s s D i r . e x i s t ? ( d i r e c t o r y ) r a i s e A r g u m e n t E r r o r . n e w ( " # { d i r e c t o r y } d o e s n o t e x i s t ! " ) e n d A c t i v e R e c o r d : : F i x t u r e S e t . r e s e t _ c a c h e D i r . g l o b ( d i r e c t o r y . j o i n ( " * * / * " ) ) . e a c h d o | f i l e n a m e | f i x t u r e = P a t h n a m e . n e w ( f i l e n a m e . c h o m p ( ' . y m l ' ) ) . b a s e n a m e A c t i v e R e c o r d : : F i x t u r e S e t . c r e a t e _ f i x t u r e s ( d i r e c t o r y , f i x t u r e ) e n d e n d e n d
  15. PostgreSQL shared extensions From the a p a r t

    m e n t gem documentation: When using extensions, keep in mind: Extensions can only be installed into one schema per database, so we will want to install it into a schema that is always available in the schema_search_path The schema and extension need to be created in the database before they are referenced in migrations, database.yml or apartment. There does not seem to be a way to create the schema and extension using standard rails migrations. Rails db:test:prepare deletes and recreates the database, so it needs to be easy for the extension schema to be recreated here.
  16. PostgreSQL shared extensions p s q l - U p

    o s t g r e s - c " C R E A T E D A T A B A S E t e m p l a t e _ a p p ; p s q l - U p o s t g r e s - d t e m p l a t e _ a p p - c \ " C R E A T E S C H E M A I F N O T E X I S T S s h a r e d _ e x t e n s i o n s ; \ C R E A T E E X T E N S I O N I F N O T E X I S T S h s t o r e S C H E M A s h a r e d _ e x t e n s i o n s ; " # d a t a b a s e . y m l d e f a u l t : & d e f a u l t a d a p t e r : p o s t g r e s q l e n c o d i n g : u n i c o d e t e m p l a t e : t e m p l a t e _ a p p Also had to use s t r u c t u r e . s q l instead of s c h e m a . r b .
  17. Uploaded assets P a p e r c l i

    p : : A t t a c h m e n t . d e f a u l t _ o p t i o n s [ : u r l ] = " / : c l a s s / : t e n a n t / : a t t a c h m e n t / : i d / : s t y l e / : u p d a t e d _ a t " P a p e r c l i p : : A t t a c h m e n t . d e f a u l t _ o p t i o n s [ : p a t h ] = R a i l s . r o o t . j o i n ( ' f i l e s ' ) . j o i n ( " : c l a s s / : t e n a n t / : a t t a c h m e n t / : i d / : s t y l e / : u p d a t e d _ a t " ) . t o _ s P a p e r c l i p . i n t e r p o l a t e s : t e n a n t d o | a t t a c h m e n t , s t y l e | A p a r t m e n t : : T e n a n t . c u r r e n t e n d # o t h e r i n t e r p o l a t i o n s . . .
  18. Feature gates A simple table with three columns: ID, feature

    name and a creation timestamp. c l a s s F e a t u r e < A c t i v e R e c o r d : : B a s e d e f s e l f . e n a b l e d ? ( n a m e ) w h e r e ( n a m e : n a m e ) . e x i s t s ? e n d e n d Remember: the contents of this table can be different for every tenant thanks to PosgreSQL schemas.
  19. Scheduled jobs d e f a u l t :

    & d e f a u l t c l a s s : ' T a s k W o r k e r ' q u e u e : ' c r o n ' r e t r i e s : 0 s e n d _ r e c a p _ e m a i l s : < < : * d e f a u l t c r o n : " 0 2 * * * " # 2 : 0 0 e v e r y d a y a r g s : [ ' s e n d _ r e c a p _ e m a i l s ' ] Results in something like w o r k e r = T a s k W o r k e r . n e w ( o p t i o n s ) w o r k e r . p e r f o r m ( ' s e n d _ r e c a p _ e m a i l s ' )
  20. Scheduled jobs c l a s s T a s

    k W o r k e r i n c l u d e S i d e k i q : : W o r k e r s i d e k i q _ o p t i o n s r e t r y : f a l s e d e f p e r f o r m ( t a s k _ n a m e ) u n l e s s r e s p o n d _ t o ? ( t a s k _ n a m e ) r a i s e ( A r g u m e n t E r r o r , " m i s s i n g t a s k # { t a s k _ n a m e } " ) e n d A p a r t m e n t . t e n a n t _ n a m e s . e a c h d o | t e n a n t | A p a r t m e n t : : T e n a n t . s w i t c h ( t e n a n t ) d o i f F e a t u r e . e n a b l e d ? ( " t a s k : # { t a s k _ n a m e } " ) s e n d ( t a s k _ n a m e ) e n d e n d e n d e n d d e f s e n d _ r e c a p _ e m a i l s ; e n d # . . . e n d
  21. Tenant con guration le A YAML file with configuration for

    all tenants (and optionally different environments for each tenant) t e n a n t 1 : < < : * d e f a u l t d o m a i n : e x a m p l e . c o m s m t p : d o m a i n : e x a m p l e . c o m Loaded in an initializer: r e q u i r e R a i l s . r o o t . j o i n ( ' l i b / c o n f i g ' ) R a i l s . a p p l i c a t i o n . c o n f i g . x . a p p = A p p : : C o n f i g . n e w
  22. Mailer con guration c l a s s A p

    p l i c a t i o n M a i l e r < A c t i o n M a i l e r : : B a s e d e f a u l t f r o m : - > ( m a i l e r ) d o R a i l s . c o n f i g u r a t i o n . x . a p p . s e n d e r _ e m a i l e n d l a y o u t ' m a i l e r ' d e f s e l f . s m t p _ s e t t i n g s s u p e r . m e r g e ( R a i l s . c o n f i g u r a t i o n . x . a p p . s m t p . s y m b o l i z e _ k e y s ) e n d d e f u r l _ o p t i o n s s u p e r . m e r g e ( h o s t : R a i l s . c o n f i g u r a t i o n . x . a p p . d o m a i n ) e n d e n d
  23. c l a s s C o n f i

    g d e f m e t h o d _ m i s s i n g ( m e t h o d _ n a m e , * a r g s , & b l o c k ) g e t ( m e t h o d _ n a m e ) e n d p r i v a t e d e f g e t ( k e y , t e n a n t = n i l ) c o n f i g = t e n a n t s _ c o n f i g [ t e n a n t | | A p a r t m e n t : : T e n a n t . c u r r e n t ] | | { } c o n f i g = c o n f i g . m e r g e ( c o n f i g . f e t c h ( R a i l s . e n v , { } ) ) c o n f i g . f e t c h ( k e y . t o _ s ) r e s c u e K e y E r r o r r a i s e K e y E r r o r , e x c e p t i o n _ m e s s a g e ( k e y ) e n d d e f t e n a n t s _ c o n f i g @ t e n a n t s _ c o n f i g | | = Y A M L : : l o a d ( E R B . n e w ( R a i l s . r o o t . j o i n ( " f o o . y m l " ) . r e a d ) . r e s u l t ) e n d d e f e x c e p t i o n _ m e s s a g e ( k e y ) # . . . e n d e n d
  24. Absolute URL helpers For use in the API m o

    d u l e U r l m o d u l e U s e r P i c t u r e d e f s e l f . p a t h ( u s e r , s t y l e ) # . . . e n d d e f s e l f . u r l ( u s e r , s t y l e ) h o s t = R a i l s . c o n f i g u r a t i o n . x . a p p . h o s t U R I . j o i n ( h o s t , p a t h ( u s e r , s t y l e ) ) . t o _ s e n d e n d e n d
  25. Error reporting m o d u l e E x

    c e p t i o n N o t i f i e r c l a s s M u l t i t e n a n t E m a i l N o t i f i e r < E m a i l N o t i f i e r d e f o p t i o n s s u p e r . t a p d o | o p t s | e n v = R a i l s . e n v . s t a g i n g ? ? ' S T A G I N G ' : n i l t e n a n t _ w i t h _ e n v i r o n m e n t = [ c u r r e n t _ t e n a n t , e n v ] . c o m p a c t . j o i n ( ' ' ) o p t s [ : e m a i l _ p r e f i x ] = " [ # { t e n a n t _ w i t h _ e n v i r o n m e n t } ] [ E R R O R ] " e n d e n d p r i v a t e d e f c u r r e n t _ t e n a n t : : A p a r t m e n t : : T e n a n t . c u r r e n t . u p c a s e e n d e n d e n d Examples: [ T E N A N T O N E ] [ E R R O R ] E r r o r m e s s a g e [ T E N A N T O N E S T A G I N G ] [ E R R O R ] E r r o r m e s s a g e [ T E N A N T T W O ] [ E R R O R ] O t h e r e r r o r
  26. Internationalization — locale les e n : t e n

    a n t 1 : h e l l o : ' H e l l o t e n a n t 1 ' o t h e r : b y e : ' G o o d b y e ' h e l l o : ' H e l l o '
  27. Internationalization — i18n backend m o d u l e

    I 1 8 n m o d u l e B a c k e n d c l a s s M u l t i t e n a n t < S i m p l e d e f l o o k u p ( l o c a l e , k e y , s c o p e , o p t i o n s ) t e n a n t _ s c o p e = [ c u r r e n t _ t e n a n t ] + [ s c o p e ] . f l a t t e n s u p e r ( l o c a l e , k e y , t e n a n t _ s c o p e , o p t i o n s ) e n d p r i v a t e d e f c u r r e n t _ t e n a n t : : A p a r t m e n t : : T e n a n t . c u r r e n t e n d e n d e n d e n d
  28. Internationalization — i18n backend r e q u i r

    e ' i 1 8 n / b a c k e n d / m u l t i t e n a n t ' I 1 8 n . b a c k e n d = I 1 8 n : : B a c k e n d : : C h a i n . n e w ( I 1 8 n : : B a c k e n d : : M u l t i t e n a n t . n e w , I 1 8 n . b a c k e n d )
  29. Custom views c l a s s A p p

    l i c a t i o n C o n t r o l l e r b e f o r e _ a c t i o n : p r e p e n d _ t e n a n t _ v i e w _ p a t h p r i v a t e d e f p r e p e n d _ t e n a n t _ v i e w _ p a t h t e n a n t = A p a r t m e n t : : T e n a n t . c u r r e n t p r e p e n d _ v i e w _ p a t h R a i l s . r o o t . j o i n ( ' a p p ' , ' v i e w s ' , t e n a n t ) e n d e n d
  30. Custom view example r e n d e r '

    h o m e / l e g a l ' $ l s a p p / v i e w s / * / h o m e a p p / v i e w s / t e n a n t 1 / h o m e : l e g a l . h t m l . e r b a p p / v i e w s / t e n a n t 2 / h o m e : l e g a l . h t m l . e r b
  31. Custom design Layout: < % = s t y l

    e s h e e t _ l i n k _ t a g A p a r t m e n t : : T e n a n t . c u r r e n t % > t e n a n t 1 . c s s : / * * = r e q u i r e . / t e n a n t 1 / m a i n * / t e n a n t 1 / m a i n . s c s s : @ i m p o r t " . . / s h a r e d / f o n t - l a t o " ; @ i m p o r t " v a r i a b l e s " ; @ i m p o r t " . . / a p p l i c a t i o n / m a i n " ; @ i m p o r t " i m a g e s " ;
  32. $ t r e e a p p / a

    s s e t s / s t y l e s h e e t s a p p / a s s e t s / s t y l e s h e e t s ├── a p p l i c a t i o n │ ├── a d m i n │ │ └── . . . │ ├── m a i n . s c s s │ └── . . . ├── e m b e d │ └── m a i n . s c s s ├── m o b i l e │ ├── m a i n . s c s s │ └── . . . ├── t e n a n t 1 │ ├── e m b e d . s c s s │ ├── f o n t - l a t o . s c s s │ ├── i m a g e s . s c s s │ ├── m a i n . s c s s │ ├── m o b i l e . s c s s │ └── _ v a r i a b l e s . s c s s ├── t e n a n t 1 . c s s ├── t e n a n t 1 _ e m b e d . c s s ├── t e n a n t 1 _ m o b i l e . c s s ├── s h a r e d │ └── f o n t - l a t o . s c s s └── . . .
  33. d e f a p p _ c a c

    h e ( n a m e = { } , o p t i o n s = n i l , & b l o c k ) k e y = [ A p a r t m e n t : : T e n a n t . c u r r e n t , I 1 8 n . l o c a l e , n a m e ] c a c h e ( k e y , o p t i o n s , & b l o c k ) e n d
  34. Real-world example m o d u l e C a

    c h e H e l p e r d e f a p p _ c a c h e ( n a m e = { } , o p t i o n s = n i l , & b l o c k ) c a c h e ( [ A p a r t m e n t : : T e n a n t . c u r r e n t , I 1 8 n . l o c a l e , d e v i c e _ c a c h e _ k e y , c u r r e n t _ a c c o u n t . t r y ( : r o l e s _ c a c h e _ k e y ) , n a m e ] . f l a t t e n ( 1 ) , o p t i o n s , & b l o c k ) e n d d e f d e v i c e _ c a c h e _ k e y [ d e v i c e _ r e q u i r e s _ u s e r _ a c t i o n _ t o _ p l a y ? ] . i n d e x { | v e r s i o n | v e r s i o n } e n d d e f m o d e l _ i s _ o r _ b e l o n g s _ t o _ c u r r e n t _ u s e r ? ( m o d e l ) i f c u r r e n t _ u s e r m o d e l = = c u r r e n t _ u s e r | | m o d e l . t r y ( : u s e r ) = = c u r r e n t _ u s e r e n d e n d e n d