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

Scalable Angular 2 Application Architecture

Scalable Angular 2 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, flexibility and on top of that we need to develop the application at reasonable level of simplicity in order to ease the recruitment process.

In this presentation I’ll introduce a scalable Angular 2 application architecture, which answers the following requirements:
- Testability.
- Predictable state management.
- Scalable communication layer.
- Modular and robust design.

Minko Gechev

April 16, 2016
Tweet

More Decks by Minko Gechev

Other Decks in Programming

Transcript

  1. Package formats • RESTful API • JSON commands • WebSocket

    application service • JSON-RPC • WebRTC data-channel • BERT-RPC
  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. . ʦʒʒ src ʮʒʒ multi-player ʔ ʮʒʒ commands ʔ ʮʒʒ

    components ʔ ʦʒʒ gateways ʮʒʒ single-player ʔ ʦʒʒ components ʮʒʒ home ʔ ʦʒʒ components ʦʒʒ shared
  4. . ʦʒʒ src ʮʒʒ multi-player ʔ ʮʒʒ commands ʔ ʮʒʒ

    components ʔ ʦʒʒ gateways ʮʒʒ single-player ʔ ʦʒʒ components ʮʒʒ home ʔ ʦʒʒ components ʦʒʒ shared /home
  5. . ʦʒʒ src ʮʒʒ multi-player ʔ ʮʒʒ commands ʔ ʮʒʒ

    components ʔ ʦʒʒ gateways ʮʒʒ single-player ʔ ʦʒʒ components ʮʒʒ home ʔ ʦʒʒ components ʦʒʒ shared /single-player
  6. . ʦʒʒ src ʮʒʒ multi-player ʔ ʮʒʒ commands ʔ ʮʒʒ

    components ʔ ʦʒʒ gateways ʮʒʒ single-player ʔ ʦʒʒ components ʮʒʒ home ʔ ʦʒʒ components ʦʒʒ shared /multi-player
  7. Sample Tech Stack • Angular 2 • RxJS • ngrx

    • TypeScript • ImmutableJS
  8. [1, 2, 3] .map(n => n * 2) .filter(n =>

    n > 2); higher-order-functions.ts
  9. let obs = Rx.Observable.create(observer => { let counter = 0;

    setInterval(() => observer.next(counter++), 1000); }); obs .map(n => n * 2) .filter(n => n > 2) .subscribe(n => console.log(n)); rx.ts
  10. let obs = Rx.Observable.create(observer => { let counter = 0;

    setInterval(() => observer.next(counter++), 1000); }); obs .map(n => n * 2) .filter(n => n > 2) .subscribe(n => console.log(n)); rx.ts
  11. UI components Façade (provides simplified interface to the components) State

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

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

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

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

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

    management Async services Gateways (HTTP, WS, WebRTC) Commands (RESTful, RPC) Payloads (BERT, JSON) Store Reducers
  17. export class GameComponent implements AfterViewInit { @Input() text: string; @Output()

    change: EventEmitter<string> … constructor(private _model: GameModel …) {} changeHandler(data: string) { this._model.onProgress(data); } get invalid() { return this._model.game$ .scan((accum: boolean, current: any) => { return (current && current.get(‘invalid’) || accum; }, false); } } game.component.ts
  18. export class GameComponent implements AfterViewInit { @Input() text: string; @Output()

    change: EventEmitter<string> … constructor(private _model: GameModel …) {} changeHandler(data: string) { this._model.onProgress(data); } get invalid() { return this._model.game$ .scan((accum: boolean, current: any) => { return (current && current.get(‘invalid’) || accum; }, false); } } game.component.ts
  19. UI components Façade (provides simplified interface to the components) State

    management Async services Gateways (HTTP, WS, WebRTC) Commands (RESTful, RPC) Payloads (BERT, JSON) Store Reducers
  20. game.model.ts @Injectable() export class GameModel extends Model { game$: Observable<Game>;

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

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

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

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

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

    _store: Store<any>, @Inject(AsyncService) _services) { super(_services || []); this.game$ = this._store.select('game'); } ... completeGame(time: number, text: string) { const action = GameActions.completeGame(time, text); this._store.dispatch(action); this.performAsyncAction(action) .subscribe(() => console.log('Done!')); } } game.model.ts
  26. UI components Façade (provides simplified interface to the components) State

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

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

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

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

    Action) => { switch (action.type) { case START_GAME: state = fromJS({}); break; case INVALID_GAME: state = state.set('invalid', true); break; case GAME_PROGRESS: state = state.set(‘currentText', action.payload.text); break; } return state; };
  31. game.component.ts … get invalid() { return this._model.game$ .scan((accum: boolean, current:

    any) => { return current.get('invalid') || accum; }, false); } …
  32. game.component.ts … get invalid() { return this._model.game$ .scan((accum: boolean, current:

    any) => { return current.get('invalid') || accum; }, false); } …
  33. game.component.ts … get invalid() { return this._model.game$ .scan((accum: boolean, current:

    any) => { return current.get('invalid') || accum; }, false); } …
  34. UI components Façade (provides simplified interface to the components) State

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

    _store: Store) { _rtcGateway.dataStream .map((data: any) => JSON.parse(data.toString())) .subscribe((command: any) => { switch (command.method) { case PROGRESS: _store.dispatch(P2PActions.progress(command.payload.text)); break; } }); } process(action: Action) { const commandBuilder = buildP2PCommand(action); if (!commandBuilder) { console.warn('This command is not supported'); return Observable.create((obs: Observer<any>) => obs.complete()); } else return commandBuilder(baseCommand).invoke(); } } game-p2p.async-service.ts
  36. export class GameP2PService extends AsyncService { constructor(private _rtcGateway: WebRTCGateway, private

    _store: Store) { _rtcGateway.dataStream .map((data: any) => JSON.parse(data.toString())) .subscribe((command: any) => { switch (command.method) { case PROGRESS: _store.dispatch(P2PActions.progress(command.payload.text)); break; } }); } process(action: Action) { const commandBuilder = buildP2PCommand(action); if (!commandBuilder) { console.warn('This command is not supported'); return Observable.create((obs: Observer<any>) => obs.complete()); } else return commandBuilder(baseCommand).invoke(); } } game-p2p.async-service.ts
  37. export class GameP2PService extends AsyncService { constructor(private _rtcGateway: WebRTCGateway, private

    _store: Store) { _rtcGateway.dataStream .map((data: any) => JSON.parse(data.toString())) .subscribe((command: any) => { switch (command.method) { case PROGRESS: _store.dispatch(P2PActions.progress(command.payload.text)); break; } }); } process(action: Action) { const commandBuilder = buildP2PCommand(action); if (!commandBuilder) { console.warn('This command is not supported'); return Observable.create((obs: Observer<any>) => obs.complete()); } else return commandBuilder(baseCommand).invoke(); } } game-p2p.async-service.ts
  38. let user = new Map(); user = user.set('name', 'Joe'); //

    { name: 'Joe' } console.log(user.toJS()); immutable.js
  39. export interface IUser { id?: number; gender?: number; email?: string;

    } const userRecord = Immutable.Record({ id: 0, gender: 0, email: null }); export class User extends userRecord implements IUser { id: number; gender: number; email: string; constructor(config: IUser) { super(config); } } immutable-records.ts
  40. export interface IUser { id?: number; gender?: number; email?: string;

    } const userRecord = Immutable.Record({ id: 0, gender: 0, email: null }); export class User extends userRecord implements IUser { id: number; gender: number; email: string; constructor(config: IUser) { super(config); } } immutable-records.ts
  41. export interface IUser { id?: number; gender?: number; email?: string;

    } const userRecord = Immutable.Record({ id: 0, gender: 0, email: null }); export class User extends userRecord implements IUser { id: number; gender: number; email: string; constructor(config: IUser) { super(config); } } immutable-records.ts
  42. export interface IUser { id?: number; gender?: number; email?: string;

    } const userRecord = Immutable.Record({ id: 0, gender: 0, email: null }); export class User extends userRecord implements IUser { id: number; gender: number; email: string; constructor(config: IUser) { super(config); } } immutable-records.ts
  43. 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 as user$ uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name
  44. 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)
  45. 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 as user$ uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data)
  46. 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 as user$ uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data)
  47. 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 as user$ uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data)
  48. 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 as user$ uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data)
  49. 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 as user$ uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data)
  50. 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 as user$ uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data)
  51. 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 as user$ uses user$ Stream Dependency Action (manipulation/method call) User registration user.email = email user.name = name signup(data) signup(data)
  52. 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)
  53. Properties… • Predictable state management • Testable (easy to mock

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

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

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

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

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

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

    services thanks to DI) • Not coupled to any remote service • Not coupled to any message format • Model can use different services based on context • Easy management of async events