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

Scalable Application Architecture

Minko Gechev
December 09, 2016

Scalable Application Architecture

Modern web applications have constantly growing requirements and their complexity grows exponentially. Some of the biggest challenges in front of us are state management, testability and flexibility. Given the dynamic business environment we need to make sure our projects' design uses well known patterns most developers are familiar with. In this talk I’ll introduce a scalable Angular 2 application architecture, which answers the following requirements: testability, predictable state management, scalable communication layer and modular and robust design.

Minko Gechev

December 09, 2016
Tweet

More Decks by Minko Gechev

Other Decks in Programming

Transcript

  1. Data layer • RESTful API • WebSocket application service •

    WebRTC data-channel • Various data packages • Bert-RPC • JSON • etc.
  2. abstraction |əbˈstrakʃ(ə)n| noun [ mass noun ] … 4 the

    process of considering something independently of its associations or attributes: the question cannot be considered in abstraction from the historical context in which it was raised.
  3. Sample Tech Stack • Angular 2 • RxJS • ngrx

    • TypeScript • ImmutableJS
  4. UI components Façade (Active Record-like) (provides simplified interface to the

    components) State management Async services Gateways (HTTP, WS, WebRTC) Commands (RESTful, RPC) Payloads (BERT, JSON) Store Reducers
  5. UI components Façade (Active Record-like) (provides simplified interface to the

    components) State management Async services Gateways (HTTP, WS, WebRTC) Commands (RESTful, RPC) Payloads (BERT, JSON) Store Reducers
  6. UI components Façade (Active Record-like) (provides simplified interface to the

    components) State management Async services Gateways (HTTP, WS, WebRTC) Commands (RESTful, RPC) Payloads (BERT, JSON) Store Reducers
  7. UI components Façade (Active Record-like) (provides simplified interface to the

    components) State management Async services Gateways (HTTP, WS, WebRTC) Commands (RESTful, RPC) Payloads (BERT, JSON) Store Reducers
  8. UI components Façade (Active Record-like) (provides simplified interface to the

    components) State management Async services Gateways (HTTP, WS, WebRTC) Commands (RESTful, RPC) Payloads (BERT, JSON) Store Reducers
  9. UI components Façade (Active Record-like) (provides simplified interface to the

    components) State management Async services Gateways (HTTP, WS, WebRTC) Commands (RESTful, RPC) Payloads (BERT, JSON) Store Reducers
  10. game.component.ts @Component({ selector: 'sd-game', template: `...`, styles: [...] }) export

    class GameComponent { constructor(private _model: GameModel) {} changeHandler(data: string) { this._model.onProgress(data); } }
  11. game.component.ts @Component({ selector: 'sd-game', template: `...`, styles: [...] }) export

    class GameComponent { constructor(private _model: GameModel) {} changeHandler(data: string) { this._model.onProgress(data); } }
  12. game.component.ts @Component({ selector: 'sd-game', template: `...`, styles: [...] }) export

    class GameComponent { constructor(private _model: GameModel) {} changeHandler(data: string) { this._model.onProgress(data); } }
  13. UI components • Simple presentational logic • As stateless as

    possible • Delegate business logic to models
  14. UI components Façade (Active Record-like) (provides simplified interface to the

    components) State management Async services Gateways (HTTP, WS, WebRTC) Commands (RESTful, RPC) Payloads (BERT, JSON) Store Reducers
  15. class GameModel extends Model { game$: Observable<Game>; constructor(protected _store: Store<any>,

    @Inject(AsyncService) _services: AsyncService[]) { super(_services || []); this.game$ = this._store.select('game'); } completeGame(time: number, text: string) { const action = GameActions.completeGame(time, text); this.performAsyncAction(action) .subscribe(() => this._store.dispatch(action)); } } game.model.ts
  16. game.model.ts class GameModel extends Model { game$: Observable<Game>; constructor(protected _store:

    Store<any>, @Inject(AsyncService) _services: AsyncService[]) { super(_services || []); this.game$ = this._store.select('game'); } completeGame(time: number, text: string) { const action = GameActions.completeGame(time, text); this.performAsyncAction(action) .subscribe(() => this._store.dispatch(action)); } }
  17. class GameModel extends Model { game$: Observable<Game>; constructor(protected _store: Store<any>,

    @Inject(AsyncService) _services: AsyncService[]) { super(_services || []); this.game$ = this._store.select('game'); } completeGame(time: number, text: string) { const action = GameActions.completeGame(time, text); this.performAsyncAction(action) .subscribe(() => this._store.dispatch(action)); } } game.model.ts
  18. BaseModel Model B Model A State tree Component tree state

    stream (game$) references (store.select(…))
  19. class GameModel extends Model { game$: Observable<Game>; constructor(protected _store: Store<any>,

    @Inject(AsyncService) _services: AsyncService[]) { super(_services || []); this.game$ = this._store.select('game'); } completeGame(time: number, text: string) { const action = GameActions.completeGame(time, text); this.performAsyncAction(action) .subscribe(() => this._store.dispatch(action)); } } game.model.ts
  20. class GameModel extends Model { game$: Observable<Game>; constructor(protected _store: Store<any>,

    @Inject(AsyncService) _services: AsyncService[]) { super(_services || []); this.game$ = this._store.select('game'); } completeGame(time: number, text: string) { const action = GameActions.completeGame(time, text); this.performAsyncAction(action) .subscribe(() => this._store.dispatch(action)); } } game.model.ts
  21. class GameModel extends Model { game$: Observable<Game>; constructor(protected _store: Store<any>,

    @Inject(AsyncService) _services: AsyncService[]) { super(_services || []); this.game$ = this._store.select('game'); } completeGame(time: number, text: string) { const action = GameActions.completeGame(time, text); this.performAsyncAction(action) .subscribe(() => this._store.dispatch(action)); } } game.model.ts
  22. Models (Façade) • Map the state stream • Delegate business

    logic to reducers • Delegate data-access logic to async services
  23. UI components Façade (Active Record-like) (provides simplified interface to the

    components) State management Async services Gateways (HTTP, WS, WebRTC) Commands (RESTful, RPC) Payloads (BERT, JSON) Store Reducers
  24. game.reducer.ts export const gameReducer = (state: any = initialState.get('game'), action:

    Action) => { switch (action.type) { case START_GAME: state = fromJS({}); break; case GAME_PROGRESS: state = state.set('currentText', action.payload.text); break; } return state; };
  25. game.reducer.ts export const gameReducer = (state: any = initialState.get('game'), action:

    Action) => { switch (action.type) { case START_GAME: state = fromJS({}); break; case GAME_PROGRESS: state = state.set('currentText', action.payload.text); break; } return state; };
  26. game.reducer.ts export const gameReducer = (state: any = initialState.get('game'), action:

    Action) => { switch (action.type) { case START_GAME: state = fromJS({}); break; case GAME_PROGRESS: state = state.set('currentText', action.payload.text); break; } return state; };
  27. game.reducer.ts export const gameReducer = (state: any = initialState.get('game'), action:

    Action) => { switch (action.type) { case START_GAME: state = fromJS({}); break; case GAME_PROGRESS: state = state.set('currentText', action.payload.text); break; } return state; };
  28. UI components Façade (Active Record-like) (provides simplified interface to the

    components) State management Async services Gateways (HTTP, WS, WebRTC) Commands (RESTful, RPC) Payloads (BERT, JSON) Store Reducers
  29. export class GameP2PService extends AsyncService { constructor(private _rtcGateway: WebRTCGateway, private

    _store: Store) { _rtcGateway.dataStream .map((data: any) => JSON.parse(data.toString())) .subscribe((command: any) => { command.method === PROGRESS && _store.dispatch(P2PActions.progress(command.payload.text)); }); } process(action: Action) { const commandBuilder = buildP2PCommand(action); if (commandBuilder) return commandBuilder().invoke(); } } game-p2p.async-service.ts
  30. export class GameP2PService extends AsyncService { constructor(private _rtcGateway: WebRTCGateway, private

    _store: Store) { _rtcGateway.dataStream .map((data: any) => JSON.parse(data.toString())) .subscribe((command: any) => { command.method === PROGRESS && _store.dispatch(P2PActions.progress(command.payload.text)); }); } process(action: Action) { const commandBuilder = buildP2PCommand(action); if (commandBuilder) return commandBuilder().invoke(); } } game-p2p.async-service.ts
  31. export class GameP2PService extends AsyncService { constructor(private _rtcGateway: WebRTCGateway, private

    _store: Store) { _rtcGateway.dataStream .map((data: any) => JSON.parse(data.toString())) .subscribe((command: any) => { command.method === PROGRESS && _store.dispatch(P2PActions.progress(command.payload.text)); }); } process(action: Action) { const commandBuilder = buildP2PCommand(action); if (commandBuilder) return commandBuilder().invoke(); } } game-p2p.async-service.ts
  32. Async services Business facade Business logic Communication logic Immutable app

    state Component tree root cmp sign-up form user UserModel User Action Creator signup(data) signup(data) RESTful Async Service process(action) userReducer register() RESTful CommandBuilder build(action) Restful Command Restful Gateway invoke() send() creates() uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name uses as user$
  33. Async services Business facade Business logic Communication logic Immutable app

    state Component tree root cmp sign-up form user UserModel User Action Creator signup(data) RESTful Async Service process(action) userReducer register() RESTful CommandBuilder build(action) Restful Command Restful Gateway invoke() send() creates() uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) uses as user$
  34. Async services Business facade Business logic Communication logic Immutable app

    state Component tree root cmp sign-up form user UserModel User Action Creator RESTful Async Service process(action) userReducer register() RESTful CommandBuilder build(action) Restful Command Restful Gateway invoke() send() creates() uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data) uses as user$
  35. Async services Business facade Business logic Communication logic Immutable app

    state Component tree root cmp sign-up form user UserModel User Action Creator RESTful Async Service process(action) userReducer register() RESTful CommandBuilder build(action) Restful Command Restful Gateway invoke() send() creates() uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data) uses as user$
  36. Async services Business facade Business logic Communication logic Immutable app

    state Component tree root cmp sign-up form user UserModel User Action Creator RESTful Async Service process(action) userReducer register() RESTful CommandBuilder build(action) Restful Command Restful Gateway invoke() send() creates() uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data) uses as user$
  37. Async services Business facade Business logic Communication logic Immutable app

    state Component tree root cmp sign-up form user UserModel User Action Creator RESTful Async Service process(action) userReducer register() RESTful CommandBuilder build(action) Restful Command Restful Gateway invoke() send() creates() uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data) uses as user$
  38. Async services Business facade Business logic Communication logic Immutable app

    state Component tree root cmp sign-up form user UserModel User Action Creator RESTful Async Service process(action) userReducer register() RESTful CommandBuilder build(action) Restful Command Restful Gateway invoke() send() creates() uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data) uses as user$
  39. Async services Business facade Business logic Communication logic Immutable app

    state Component tree root cmp sign-up form user UserModel User Action Creator RESTful Async Service process(action) userReducer register() RESTful CommandBuilder build(action) Restful Command Restful Gateway invoke() send() creates() uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data) uses as user$
  40. Async services Business facade Business logic Communication logic Immutable app

    state Component tree root cmp sign-up form user UserModel User Action Creator RESTful Async Service process(action) userReducer register() RESTful CommandBuilder build(action) Restful Command Restful Gateway invoke() send() creates() uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data) uses as user$
  41. Async services Business facade Business logic Communication logic Immutable app

    state Component tree root cmp sign-up form user UserModel User Action Creator signup(data) RESTful Async Service process(action) userReducer register() RESTful CommandBuilder build(action) Restful Command Restful Gateway invoke() send() creates() uses as user$ uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data)
  42. Properties… • Predictable state management • Testable (easy to mock

    services thanks to DI) • Not coupled to any remote service • Serializable state usable offline • Model can use different services based on context • Easy management of async events
  43. Properties… • Predictable state management • Testable (easy to mock

    services thanks to DI) • Not coupled to any remote service • Serializable state usable offline • Model can use different services based on context • Easy management of async events
  44. Properties… • Predictable state management • Testable (easy to mock

    services thanks to DI) • Not coupled to any remote service • Serializable state usable offline • Model can use different services based on context • Easy management of async events
  45. Properties… • Predictable state management • Testable (easy to mock

    services thanks to DI) • Not coupled to any remote service • Serializable state usable offline • Model can use different services based on context • Easy management of async events
  46. Properties… • Predictable state management • Testable (easy to mock

    services thanks to DI) • Not coupled to any remote service • Serializable state usable offline • Model can use different services based on context • Easy management of async events
  47. Properties… • Predictable state management • Testable (easy to mock

    services thanks to DI) • Not coupled to any remote service • Serializable state usable offline • Model can use different services based on context • Easy management of async events
  48. Properties… • Predictable state management • Testable (easy to mock

    services thanks to DI) • Not coupled to any remote service • Serializable state usable offline • Model can use different services based on context • Easy management of async events