Slide 1

Slide 1 text

DDD, Rails and persistence Michał Łomnicki January, 2016 DRUG 1 / 21

Slide 2

Slide 2 text

Inspiration Blog https://vaughnvernon.co Ideal DDD Aggregate Store Book 2 / 21

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Solution 1 Data Mapper No mature Data Mapper for Ruby ROM looks promising ...but is yet incomplete 11 / 21

Slide 12

Slide 12 text

Solution 2 Events as a storage mechanism Yes, that's a good solution Big mental model change 12 / 21

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Why not Mongo? This looks like NoSQL Mongo is not ACID­compliant Transactions only at the document level 16 / 21

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Thank you Resources: https://vaughnvernon.co/?p=942 http://www.amazon.com/Implementing­Domain­Driven­Design­Vaughn­ Vernon/dp/0321834577 http://www.postgresql.org/docs/9.4/static/datatype­json.html 21 / 21

Slide 22

Slide 22 text

No content