DESIGNING DOMAINS FOR WEB APPLICATIONS

Report 7 Downloads 120 Views
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

Recommend Documents