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

DDD, Rails and persistence

DDD, Rails and persistence

Michał Łomnicki

January 18, 2016
Tweet

More Decks by Michał Łomnicki

Other Decks in Programming

Transcript

  1. Problem I want DDD in my Rails project I want

    fast and clean tests I want to build my application around domain objects not around database schema ...but I struggle with persistence and ActiveRecord gets into my way all the time 3 / 21
  2. Model c l a s s S q u a

    d i n c l u d e V i r t u s . m o d e l # o p t i o n a l , c a n b e P O R O M A X _ F I R S T _ S Q U A D _ P L A Y E R S = 1 1 a t t r i b u t e : i d , U U I D a t t r i b u t e : m a t c h _ i d , U U I D a t t r i b u t e : t e a m _ i d , U U I D a t t r i b u t e : f o r m a t i o n , F o r m a t i o n a t t r i b u t e : f i r s t _ s q u a d , S e t [ P l a y e r ] a t t r i b u t e : b e n c h , S e t [ P l a y e r ] 4 / 21
  3. Model d e f r e m o v e

    _ f r o m _ f i r s t _ s q u a d ( p l a y e r ) r a i s e S q u a d E r r o r i f ! f i r s t _ s q u a d . m e m b e r ? ( p l a y e r ) f i r s t _ s q u a d . d e l e t e ( p l a y e r ) b e n c h . a d d ( p l a y e r ) e n d d e f a d d _ t o _ f i r s t _ s q u a d ( p l a y e r ) r a i s e S q u a d E r r o r i f ! b e n c h . m e m b e r ? ( p l a y e r ) r a i s e S q u a d E r r o r i f f i r s t _ s q u a d . s i z e = = M A X _ F I R S T _ S Q U A D _ P L A Y E R S b e n c h . r e m o v e ( p l a y e r ) f i r s t _ s q u a d . a d d ( p l a y e r ) e n d 5 / 21
  4. Model d e f s u b s t i

    t u t e ( p l a y e r _ o f f , p l a y e r _ o n ) r e m o v e _ f r o m _ f i r s t _ s q u a d ( p l a y e r _ o f f ) a d d _ t o _ f i r s t _ s q u a d ( p l a y e r _ o n ) D o m a i n E v e n t P u b l i s h e r . p u b l i s h ( P l a y e r S u b s t i t u t e d . n e w ( s q u a d _ i d : i d , p l a y e r _ o f f _ i d : p l a y e r _ o f f . i d , p l a y e r _ o n _ i d : p l a y e r _ o n . i d ) ) e n d 6 / 21
  5. Service c l a s s S q u a

    d S e r v i c e i n c l u d e T r a n s a c t i o n S u p p o r t d e f i n i t i a l i z e ( s q u a d _ r e p o s i t o r y , p l a y e r _ r e p o s i t o r y ) @ s q u a d _ r e p o s i t o r y = s q u a d _ r e p o s i t o r y @ p l a y e r _ r e p o s i t o r y = p l a y e r _ r e p o s i t o r y e n d d e f s u b s t i t u t e ( s u b s t i t u t i o n _ f o r m ) t r a n s a c t i o n d o D o m a i n E v e n t P u b l i s h e r . s u b s c r i b e ( P l a y e r S u b s t i t u t e d , S o m e H a n d l e r ) p l a y e r _ o f f = p l a y e r _ r e p o s i t o r y . f i n d ( s u b s t i t u t i o n _ f o r m . p l a y e r _ o f f _ i d ) p l a y e r _ o n = p l a y e r _ r e p o s i t o r y . f i n d ( s u b s t i t u t i o n _ f o r m . p l a y e r _ o n _ i d ) s q u a d = s q u a d _ r e p o s i t o r y . f i n d ( s u b s t i t u t i o n _ f o r m . s q u a d _ i d ) s q u a d . s u b s t i t u t e ( p l a y e r _ o f f , p l a y e r _ o n ) s q u a d _ r e p o s i t o r y . s a v e ( s q u a d ) e n d e n d d e f c h a n g e _ f o r m a t i o n ( f o r m a t i o n _ f o r m ) . . . e n d e n d 7 / 21
  6. Repository c l a s s S q u a

    d R e p o s i t o r y d e f s a v e ( s q u a d ) i f s q u a d . i d u p d a t e ( s q u a d ) e l s e c r e a t e ( s q u a d ) e n d e n d d e f c r e a t e ( s q u a d ) S q u a d A R . c r e a t e ( m a t c h _ i d : s q u a d . m a t c h _ i d , f o r m a t i o n : s q u a d . f o r m a t i o n . t o _ s , s q u a d _ p l a y e r s : s q u a d . f i r s t _ s q u a d . m a p { | p l a y e r | P l a y e r A R . n e w ( f i r s t _ s q u a d : t r u e , . . . ) } + s q u a d . b e n c h . m a p { | p l a y e r | P l a y e r A R . n e w ( f i r s t _ s q u a d : f a l s e , . . . ) } ) e n d . . . 8 / 21
  7. Repository / Naive update d e f u p d

    a t e ( s q u a d ) r e c o r d = S q u a d A R . f i n d ( s q u a d . i d ) r e c o r d . f o r m a t i o n = s q u a d . f o r m a t i o n . t o _ s # d e l e t e a n d r e - c r e a t e a s s o c i a t i o n s r e c o r d . s q u a d _ p l a y e r s = s q u a d . f i r s t _ s q u a d . m a p { | p l a y e r | P l a y e r A R . n e w ( f i r s t _ s q u a d : t r u e , . . . ) } + s q u a d . b e n c h . m a p { | p l a y e r | P l a y e r A R . n e w ( f i r s t _ s q u a d : f a l s e , . . . ) } r e c o r d . s a v e e n d 9 / 21
  8. Repository / Naive update d e f u p d

    a t e ( s q u a d ) r e c o r d = S q u a d A R . f i n d ( s q u a d . i d ) r e c o r d . f o r m a t i o n = s q u a d . f o r m a t i o n . t o _ s # d e l e t e a n d r e - c r e a t e a s s o c i a t i o n s r e c o r d . s q u a d _ p l a y e r s = s q u a d . f i r s t _ s q u a d . m a p { | p l a y e r | P l a y e r A R . n e w ( f i r s t _ s q u a d : t r u e , . . . ) } + s q u a d . b e n c h . m a p { | p l a y e r | P l a y e r A R . n e w ( f i r s t _ s q u a d : f a l s e , . . . ) } r e c o r d . s a v e e n d Hard to maintain Error­prone Poor performance 10 / 21
  9. Solution 1 Data Mapper No mature Data Mapper for Ruby

    ROM looks promising ...but is yet incomplete 11 / 21
  10. Solution 2 Events as a storage mechanism Yes, that's a

    good solution Big mental model change 12 / 21
  11. Solution 3 Postgres + JSON Ideal DDD Aggregate store? Aggregate

    data stored as JSON One database row ­ one aggregate c r e a t e _ t a b l e " s q u a d s " d o | t | t . j s o n b : d a t a , n u l l : f a l s e e n d 13 / 21
  12. DB schema c r e a t e _ t

    a b l e " u s e r s " d o | t | t . j s o n b : d a t a , n u l l : f a l s e e n d c r e a t e _ t a b l e " m a t c h e s " d o | t | t . j s o n b : d a t a , n u l l : f a l s e e n d c r e a t e _ t a b l e " t e a m s " d o | t | t . j s o n b : d a t a , n u l l : f a l s e e n d c r e a t e _ t a b l e " s q u a d s " d o | t | t . j s o n b : d a t a , n u l l : f a l s e e n d 14 / 21
  13. JSON Repository c l a s s S q u

    a d R e p o s i t o r y d e f s a v e ( s q u a d ) i f s q u a d . i d u p d a t e ( s q u a d ) e l s e c r e a t e ( s q u a d ) e n d e n d d e f f i n d ( s q u a d _ i d ) D o m a i n : : S q u a d . n e w ( S q u a d A R . f i n d ( s q u a d _ i d ) ) e n d p r i v a t e d e f c r e a t e ( s q u a d ) S q u a d A R . c r e a t e ( d a t a : s q u a d . a s _ j s o n ) e n d d e f u p d a t e ( s q u a d ) S q u a d A R . w h e r e ( i d : s q u a d . i d ) . u p d a t e _ a l l ( d a t a : s q u a d . a s _ j s o n ) e n d e n d 15 / 21
  14. Why not Mongo? This looks like NoSQL Mongo is not

    ACID­compliant Transactions only at the document level 16 / 21
  15. Postgres + JSON JSON introduced in Postgres 9.3 JSONB introduced

    in Postgres 9.4 JSONB can be indexed Postgres = ACID No foreign keys and unique indexes Data consistency ensured at application level Introduce this approach in the existing database 17 / 21
  16. Postgres + JSON JSON introduced in Postgres 9.3 JSONB introduced

    in Postgres 9.4 JSONB can be indexed Postgres = ACID No foreign keys and unique indexes Data consistency ensured at application level Introduce this approach in the existing database C R E A T E I N D E X O N s q u a d s U S I N G g i n ( d a t a ) S E L E C T * F R O M s q u a d s W H E R E ( d a t a - > > ' m a t c h _ i d ' ) : : I N T = 1 2 18 / 21
  17. Locking Some locking mechanism is required Optimisic locking is preferred

    a ) P r o c e s s 1 r e a d s b ) P r o c e s s 2 r e a d s c ) P r o c e s s 1 w r i t e s d ) P r o c e s s 2 o v e r w r i t e s c ) S q u a d A R . w h e r e ( i d : s q u a d . i d , l o c k _ v e r s i o n : c u r r e n t _ v e r s i o n [ s q u a d ] ) . u p d a t e _ a l l ( d a t a : s q u a d . a s _ j s o n , l o c k _ v e r s i o n : c u r r e n t _ v e r s i o n [ s q u a d ] + 1 ) 19 / 21
  18. Lessons learnt It works Changes are easy to introduce Fast

    and easy to store and load an entire aggregate Code explains the application, not the DB schema Migrations require more work Most probably you will build a read model De­normalized data needs synchronization Avoid big aggregates No help from the database (foreign keys, not null, etc) Can't really fiddle in r a i l s c o n s o l e S q u a d . f i n d ( 1 2 3 ) . s q u a d _ p l a y e r s . u p d a t e _ a l l ( . . . ) 20 / 21