CONTROLLER CONTROL
DESIGNING DOMAINS FOR WEB APPLICATIONS
FORMAT Move non-web stu out of web Umbrellas
DEFAULT PHOENIX STRUCTURE Most of the code in web Some les in lib (endoint.ex, repo.ex) Split into controllers, models, views and templates
t r e edI" n o d e _ m o d u l e s | _ b u i l d | d e p s | s t a t i c ". ├ ─ ─c o n f i g ├ ─ ─l i b │ └ ─ ─o x o ├ ─ ─p r i v │ ├ ─ ─g e t t e x t │ └ ─ ─r e p o │ └ ─ ─m i g r a t i o n s ├ ─ ─t e s t │ ├ ─ ─c h a n n e l s │ ├ ─ ─c o n t r o l l e r s │ ├ ─ ─m o d e l s │ ├ ─ ─o x o │ ├ ─ ─s u p p o r t │ └ ─ ─v i e w s └ ─ ─w e b ├ ─ ─c h a n n e l s ├ ─ ─c o n t r o l l e r s ├ ─ ─m o d e l s ├ ─ ─t e m p l a t e s │ ├ ─ ─c h a l l e n g e │ ├ ─ ─g a m e │ ├ ─ ─l a y o u t │ ├ ─ ─p a g e │ ├ ─ ─s e s s i o n │ └ ─ ─u s e r └ ─ ─v i e w s
WHAT IS A CONTROLLER? Glue code between model and view Should focus on web layer (Generally) last part of endpoint pipeline Interfaces with conns Friends with sockets and channels
A CONTROLLER IS NOT THE INTERFACE TO YOUR DATABASE
STOP USING REPO IN CONTROLLER $g r e pi r" R e p o "a p p s / o x o _ w e b / w e b / / c o n t r o l l e r s / g a m e _ c o n t r o l l e r . e x : ch a lle n ge=R e po. g et ( Ch a lle n ge,id ) / c o n t r o l l e r s / g a m e _ c o n t r o l l e r . e x : | >R e po. u pda t e ( ) / c o n t r o l l e r s / u s e r _ c o n t r o l l e r . e x : ca s eR e po . i nse r t ( ch a nge s et )do / c o n t r o l l e r s / c h a l l e n g e _ c o n t r o l l e r . e x : | >R e po. a ll ( ) / c o n t r o l l e r s / c h a l l e n g e _ c o n t r o l l e r . e x : | >R e po. p rel o ad ( :u s er ) / c o n t r o l l e r s / c h a l l e n g e _ c o n t r o l l e r . e x : / w e b . e x : a l i a sO x o . R e p o
| >R e po. i nse r t ! ( )
WHY IS REPO IN CONTROLLER?
WHY IS REPO IN CONTROLLER? WE SAID IT SHOULD BE BUT... THINGS EVOLVE
WORLD OF GRAND THEFT POKÉMON GO 2.0
# TODO: SHOW APPLICTION
HOW TO NOT REPO Create functions in "context" Functions should do everything non-web related Delete Repo alias in web.ex
REGISTER USER
MOVE FROM CONTROLLER
d e fc r e a t e ( c o n n ,p a r a m s )d o { % { " e m a i l "= >e m a i l ," p a s s w o r d "= >p a ssw o rd } ,p l aye r _p }= M a p . s p l i t ( p a r a m s ,[ " e m a i l " ," p a s sw o rd" ] ) c a s eA c c o u n t s . r e g i s t e r _ p l a y e r ( e m a i l ,p a ssw o rd ,p l aye r _p )do { : o k ,p l a y e r }>r e d i r e c t ( . . . ) { : e r r o r ,c h a n g e s e t }>r e n d e r ( . . . ) e n d e n d
d e f m o d u l eO x o . A c c o u n t sd o a l i a sO x o . { P l a y e r ,R e p o ,A u t h } d e fr e g i s t e r _ p l a y e r ( e m a i l ,p a s s w o r d ,p l aye r _pa r ams )do E c t o . M u l t i . n e w ( ) | >E c t o . M u l t i . r u n ( : a c c o u n t ,f n_-> A u t h . r e g i s t e r ( e m a i l ,p a s s w o r d ) e n d ) | >E c t o . M u l t i . r u n ( : p l a y e r ,f n% { a c c oun t :a c cou n t }-> % P l a y e r { a c c o u n t _ i d :a c c o u n t . i d } | >P l a y e r . c h a n g e s e t ( p l a y e r _ p a r a ms ) | >R e p o . i n s e r t ( ) e n d ) | >R e p o . t r a n s a c t i o n ( ) | >h a n d l e _ n e w _ r e g i s t r a t i o n ( ) e n d e n d
d e f m o d u l eO x o . A c c o u n t sd o a l i a sO x o . M a i l e r d e f ph a n d l e _ n e w _ r e g i s t r a t i o n ( { : o k ,r e sul t s } )do p l a y e r=% { r e s u l t s . p l a y e r|e m a i l :r e sul t s . ac c oun t . em a il } M a i l e r . s e n d _ r e g i s t r a t i o n _ e m a i l ( p l a yer ) { : o k ,p l a y e r } e n d d e f ph a n d l e _ n e w _ r e g i s t r a t i o n ( { : e r r or ,_ ,c h a ng e set ,_ } )do { : e r r o r ,c h a n g e s e t } e n d e n d
REGISTER USER Accounts is the context Contains all database logic Leverages the auth context Handles email Transform response
MODULE BENEFITS Domain logic no longer tied to controller We can provider other interfaces to the function Mix Task Telnet Email Can test the ow without the controller
LISTING GAME CHALLENGES d e fi n d e x ( c o n n ,_ p a r a m s )d o c u r r e n t _ u s e r=c o n n . a s s i g n s . c u r r e n t_ u ser - p l a y e r=R e p o . p r e l o a d ( c u r r e n t _ u s e r,:pl a ye r ).player - c h a l l e n g e s= - C h a l l e n g e - | >E c t o . Q u e r y . w h e r e ( o p e n :t r u e ) -
| >E c t o . Q u e r y . w h e r e ( [ c ] ,c . p l a y er _ id! =^ player.id) | >R e p o . a l l ( ) | >R e p o . p r e l o a d ( : p l a y e r )
+ c h a l l e n g e s= + | >A c c o u n t s . g e t _ p l a y e r ( c u r r e n t _ us e r) + | >G a m e . l i s t _ o p e n _ c h a l l e n g e s ( ) r e n d e r ( c o n n ," i n d e x . h t m l " ,c h a l l e n ge s :c h al l enges) e n d
Doesn't matter where challenges are fetched from
LISTING GAME CHALLENGES d e f m o d u l eO x o . G a m ed o d e fg e t _ p l a y e r ( % A u t h . A c c o u n t { }=a c cou n t )d o R e p o . p r e l o a d ( a c c o u n t ,: p l a y e r ) . p l a yer e n d d e fl i s t _ o p e n _ c h a l l e n g e s ( % P l a y e r { }=p l aye r )do C h a l l e n g e | >E c t o . Q u e r y . w h e r e ( o p e n :t r u e ) | >E c t o . Q u e r y . w h e r e ( [ c ] ,c . p l a y e r _ id!=^ pl a ye r . id ) | >R e p o . a l l ( ) | >R e p o . p r e l o a d ( : p l a y e r ) e n d e n d
Game is the context
CREATE NEW CHALLENGE d e fc r e a t e ( c o n n ,_ p a r a m s )d o c u r r e n t _ p l a y e r=c o n n . a s s i g n s . c u r r en t _pl a ye r - c h a l l e n g e= - c u r r e n t _ p l a y e r - | >E c t o . b u i l d _ a s s o c ( : c h a l l e n g e s ) -
| >C h a l l e n g e . c h a n g e s e t ( % { } ) | >R e p o . i n s e r t ! ( )
+ c h a l l e n g e=G a m e . i s s u e _ o p e n _ c h a l l en g e!( c ur r ent_player) r e d i r e c t ( c o n n ,t o :g a m e _ p a t h ( c o n n ,: s how ,c h allenge)) e n d
This was an easy case because we use insert!
CREATE NEW CHALLENGE d e f m o d u l eO x o . G a m ed o d e fi s s u e _ o p e n _ c h a l l e n g e ! ( % P l a y e r { }=p l aye r )do p l a y e r | >E c t o . b u i l d _ a s s o c ( : c h a l l e n g e s ) | >C h a l l e n g e . c h a n g e s e t ( % { } ) | >R e p o . i n s e r t ! ( ) e n d e n d
Game is still the context Try to use descriptive verbs (issue instead of create)
CLOSE CHALLENGE (GAMECONTROLLER.SHOW)
-
c h a l l e n g e=R e p o . g e t ( C h a l l e n g e ,i d ) c u r r e n t _ p l a y e r _ i d=c o n n . a s s i g n s. c urr e nt _ player.id c a s ec h a l l e n g ed o % C h a l l e n g e { p l a y e r _ i d :^ c u r r e n t_ p lay e r_ i d}-> p l a y e r _ t o k e n=P h o e n i x . T o k e n. s ign ( .. . ) r e n d e r ( . . . ) % C h a l l e n g e { }> c h a l l e n g e | >C h a l l e n g e . c h a n g e s e t ( % { o p en :fa l se } )
+
| >R e p o . u p d a t e ( ) c a s eG a m e . c l o s e _ c h a l l e n g e ( i d ,c ur r ent _ pl a yer)do
+
{ : o k ,c h a l l e n g e }> p l a y e r _ t o k e n=P h o e n i x . T o k e n. s ign ( .. . ) r e n d e r ( . . . ) _> c o n n | >p u t _ f l a s h ( : e r r o r ," C o u l dno tfi n dc hallenge") | >r e d i r e c t ( t o :c h a l l e n g e _ p a th ( con n ,: index))
CLOSING A CHALLENGE d e fc l o s e _ c h a l l e n g e ( i d ,% P l a y e r { i d :p l aye r _id }=p l aye r )do c a s eR e p o . g e t ( C h a l l e n g e ,i d )d o % C h a l l e n g e { p l a y e r _ i d :^ p l a y e r _ i d }=c h all e nge-> { : o k ,c h a l l e n g e } % C h a l l e n g e { }=c h a l l e n g e> u p d a t e _ c h a l l e n g e ( c h a l l e n g e ,% { o p e n:fa l s e } ) { : o k ,c h a l l e n g e } _>{ : e r r o r ,: n o t _ f o u n d } e n d e n d
CONVERTING CONTROLLERS SUMMARY Remove Repo alias from web.ex Hope this causes compilation errors grep -ir Repo controllers/ Convert controller body to context module function Rename controllers/channels to be namespaced in web Do one controller at a time Commit at the end!
SO...MODELS
S/MODELS/SCHEMAS/G
WHAT IS A MODEL? A representation of your domain (domain model) Nothing to do with the database tables Can leverage the database Can encapsualte many database transactions Can do other things too (email, etc.)
WHAT IS A SCHEMA? A representation of your table row Can reference other schemas (has_many, belongs_to) Can perform data integrity validations Can't be blank Must be unique (enforced by db index) Not can't create on a Tuesday
CHANGING MODELS TO SCHEMAS Could just have rename "models" directory to "schemas" schemas/challenge.ex, schemas/player.ex Or nest schemas in context instead game/challange.ex, accounts/player.ex change ModelCase to DatabaseCase
t r e edI" n o d e _ m o d u l e s | _ b u i l d | d e p s | s t a t i c ". ├ ─ ─c o n f i g ├ ─ ─l i b │ └ ─ ─o x o │ ├ ─ ─a c c o u n t s │ ├ ─ ─a u t h │ └ ─ ─g a m e ├ ─ ─p r i v │ ├ ─ ─g e t t e x t │ └ ─ ─r e p o │ └ ─ ─m i g r a t i o n s ├ ─ ─t e s t │ ├ ─ ─c h a n n e l s │ ├ ─ ─c o n t r o l l e r s │ ├ ─ ─m o d e l s │ ├ ─ ─o x o │ ├ ─ ─s u p p o r t │ └ ─ ─v i e w s └ ─ ─w e b ├ ─ ─c h a n n e l s ├ ─ ─c o n t r o l l e r s ├ ─ ─p l u g s ├ ─ ─t e m p l a t e s │ ├ ─ ─c h a l l e n g e │ ├ ─ ─g a m e │ ├ ─ ─l a y o u t │ ├ ─ ─p a g e │ ├ ─ ─s e s s i o n │ └ ─ ─u s e r └ ─ ─v i e w s
MOVING WEB g i tm vw e b / s t a t i c /a s s e t s /
g i tm vp a c k a g e . j s o na s s e t s / m vn o d e _ m o d u l e s /a s s e t s / e c h o" a s s e t s / n o d e _ m o d u l e s "> >. g i t i g no r e g i tm vw e b /l i b / o x o / w e b / f i n dl i b / o x o / w e b /t y p efe x e cs e d-i\ ' s / d e f m o d u l eO x o / d e f m o d u l eO x o . W e b /g '{}+
t r e edI" n o d e _ m o d u l e s | _ b u i l d | d e p s | s t a t i c ". ├ ─ ─a s s e t s ├ ─ ─c o n f i g ├ ─ ─l i b │ └ ─ ─o x o │ ├ ─ ─a c c o u n t s │ ├ ─ ─a u t h │ ├ ─ ─g a m e │ └ ─ ─w e b │ ├ ─ ─c h a n n e l s │ ├ ─ ─c o n t r o l l e r s │ ├ ─ ─p l u g s │ ├ ─ ─t e m p l a t e s │ │ ├ ─ ─c h a l l e n g e │ │ ├ ─ ─g a m e │ │ ├ ─ ─l a y o u t │ │ ├ ─ ─p a g e │ │ ├ ─ ─s e s s i o n │ │ └ ─ ─u s e r │ └ ─ ─v i e w s ├ ─ ─p r i v │ ├ ─ ─g e t t e x t │ └ ─ ─r e p o │ └ ─ ─m i g r a t i o n s └ ─ ─t e s t ├ ─ ─c h a n n e l s ├ ─ ─c o n t r o l l e r s ├ ─ ─m o d e l s ├ ─ ─o x o ├ ─ ─s u p p o r t └ ─ ─v i e w s
UMBRELLA APPLICATIONS
WHY USE AN UMBRELLA APPLICATION? Can run and develop each application on its own Can test each application on its own Makes contracts between applications explicit Releases can be built for a part of the umbrella Easy to extract as a dependency (e.g. hex, private repo)
WHY NOT USE AN UMBRELLA APPLICATION? More complicated? Can be harder to test the entire suite Process name collisions Applications started at the wrong time Dependency con icts harder to resolve (multiple overrides) Developing a new feature touches multiple applications
SHOULD YOU USE AN UMBRELLA APPLICATION?
IT DEPENDS!
THE GOOD NEWS You don't need to decide today! Moving code around in Elixir is easy Moving code between applications is quite easy Possible to convert from an umbrella to single application
LET'S BEGIN!
mix new oxo --umbrella
Create a new umbrella application Even though we already have our application!
RENAME UMBRELLA APPLICATION d e f m o d u l eO x o . M i x f i l ed o u s eM i x . P r o j e c t # . . . e n d
There is already an Oxo.Mix le in the Phoenix application Rename to Oxo.Platform.Mix le or something equally as generic
SET UP GIT FOR UMBRELLA g i ti n i t g i ta d d. g i tc o m m i tm" i n i t i a lc o m m i t "
Git is important for the next step Good idea to commit before making changes Prevent a massive initial commit
ADD EXISTING APPLICATION TO UMBRELLA Could just `cp` the directory Loses git history No longer possible to bisect, blame, etc. Introduces a massive import commit There is a better way!
INTRODUCTING GIT SUBTREE Imports code into directory (unlike submodules) Maintains entire history Can subtree merge more than once
SUBTREE ADD THE APPLICATION g i tr e m o t ea d do x o _ r e m o t eg i t @ g i t h u b . c om: G azl e r/oxo.git g i tf e t c ho x o _ r e m o t e g i ts u b t r e ea d dp r e f i xa p p s / o x o _ w e boxo _ rem o te/master
Entire history is preserved! (With original SHAs) Some changes required since we use oxo_web Application could have been called `oxo` instead
UPDATE APP CONFIG d e fp r o j e c td o [ # . . . b u i l d _ p a t h :" . . / . . / _ b u i l d " , c o n f i g _ p a t h :" . . / . . / c o n f i g / c o n f i g . e x s" , d e p s _ p a t h :" . . / . . / d e p s " , l o c k f i l e :" . . / . . / m i x . l o c k " , # . . . ] e n d
Share build, con g and lock les git mv apps/oxo_web/mix.lock mix.lock
CHECK EVERYTHING IS WORKING Run mix deps.get && mix test Hope everything works! Run mix phoenix.server for good measure Don't forget to cd `apps/oxo_web/assets && npm install`!
SPLITTING OUT OUR APPLICATION Web stays, everything else goes Remove concerns of other applications Using dependencies of other apps directly (Comeonin, etc.) Con g for other applications Remember to move the tests too
MAKE A NEW `OXO` APPLICATION mix new oxo --sup Add dependency in oxo_web Don't forget to add it to applications list d e f pd e p sd o [ { : o x o ,i n _ u m b r e l l a :t r u e } , { : p h o e n i x ," ~ >1 . 2 . 0 " } , { : p h o e n i x _ p u b s u b ," ~ >1 . 0 . 0 " } , # . . . { : c o w b o y ," ~ >1 . 0 " } ] e n d
MOVE DATABASE TO CORE APPLICATION Move database con g from con g/*.exs Update ecto_repos in con g/con g.exs Add Ecto and other dependencies (Comeonin, etc.) Add repo in app supervisor
MOVE TESTS OVER A commit with failing tests is not a good commit Will need DatabaseCase Update elixirc_paths in mix.exs Update test_helper.exs
t r e edI" n o d e _ m o d u l e s | _ b u i l d | d e p s | s t a t i c ". ├ ─ ─a p p s │ ├ ─ ─o x o │ │ ├ ─ ─c o n f i g │ │ ├ ─ ─l i b │ │ │ └ ─ ─o x o │ │ │ ├ ─ ─a c c o u n t s │ │ │ ├ ─ ─a u t h │ │ │ └ ─ ─g a m e │ │ ├ ─ ─p r i v │ │ │ └ ─ ─r e p o │ │ │ └ ─ ─m i g r a t i o n s │ │ └ ─ ─t e s t │ │ ├ ─ ─o x o │ │ └ ─ ─s u p p o r t │ └ ─ ─o x o _ w e b │ ├ ─ ─a s s e t s │ ├ ─ ─c o n f i g │ ├ ─ ─l i b │ │ └ ─ ─o x o │ │ └ ─ ─w e b │ │ ├ ─ ─c h a n n e l s │ │ ├ ─ ─c o n t r o l l e r s │ │ ├ ─ ─p l u g s │ │ ├ ─ ─t e m p l a t e s │ │ └ ─ ─v i e w s │ ├ ─ ─p r i v │ │ └ ─ ─g e t t e x t │ └ ─ ─t e s t │ ├ ─ ─c h a n n e l s │ ├ ─ ─c o n t r o l l e r s
MIGRATING TO UMBRELLA SUMMARY Not too di cult to migrate to an umbrella application Entire git history can be maintained grep -ir Repo apps/oxo_web should return no results
GOING FORWARD There can be more than 2 applications Try to think in applications Split out common functions into own applications Authentication Email/Noti cations Game logic
Gazler
@TheGazler
https://github.com/Gazler/oxo