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
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
_ 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
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
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
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
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
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 Errorprone Poor performance 10 / 21
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
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
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
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
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
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
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 Denormalized 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