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. Scalable
    Application rchitecture
    github.com/mgechev
    twitter.com/mgechev
    blog.mgechev.com
    Minko Gechev

    View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. Hold on for the
    second edition!

    View Slide

  6. Story Time

    View Slide

  7. Disclaimer
    • High-level architecture, doesn’t define interfaces
    • Suggestions, not rules
    • Works for me, may not work for you

    View Slide

  8. Design Architecture

    View Slide

  9. – Martin Fowler
    “…decisions that are hard
    to change…”
    Architecture

    View Slide

  10. High-level constraints
    of the project

    View Slide

  11. Dynamic
    requirements

    View Slide

  12. View Slide

  13. Scalable & flexible
    data layer

    View Slide

  14. Data layer
    • RESTful API
    • WebSocket application service
    • WebRTC data-channel
    • Various data packages
    • Bert-RPC
    • JSON
    • etc.

    View Slide

  15. Scalable
    team & codebase

    View Slide

  16. View Slide

  17. High-level decisions
    for the architecture

    View Slide

  18. Dynamic Requirements
    Scalable & flexible data layer
    Scalable team & codebase

    View Slide

  19. Dynamic Requirements
    Scalable & flexible data layer
    Scalable team & codebase

    View Slide

  20. 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.

    View Slide

  21. WebRTC
    Gateway
    WebSocket
    Gateway

    View Slide

  22. Gateway
    WebRTC
    Gateway
    WebSocket
    Gateway

    View Slide

  23. Dynamic Requirements
    Scalable & flexible data layer
    Scalable team & codebase

    View Slide

  24. Explicitness

    View Slide

  25. Explicitness
    • Explicit state mutation
    • Explicit package mapping
    • Others…

    View Slide

  26. Dynamic Requirements
    Scalable & flexible data layer
    Scalable team & codebase

    View Slide

  27. Consistent
    state management

    View Slide

  28. Consistent state
    • Domain objects should:
    • Have a single instance
    • Explicit update

    View Slide

  29. Single instance !=>

    View Slide

  30. Single instance !=> Identity map

    View Slide

  31. Single instance !=> Identity map
    Explicit update !=>

    View Slide

  32. Single instance !=> Identity map
    Explicit update !=> Strict conventions

    View Slide

  33. redux++

    View Slide

  34. redux++

    View Slide

  35. Sample Tech Stack
    • Angular 2
    • RxJS
    • ngrx
    • TypeScript
    • ImmutableJS

    View Slide

  36. Sample application

    View Slide

  37. View Slide

  38. View Slide

  39. High-level architecture

    View Slide

  40. 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

    View Slide

  41. 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

    View Slide

  42. 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

    View Slide

  43. 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

    View Slide

  44. 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

    View Slide

  45. UI components

    View Slide

  46. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  50. UI components
    • Simple presentational logic
    • As stateless as possible
    • Delegate business logic to models

    View Slide

  51. 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

    View Slide

  52. class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @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

    View Slide

  53. game.model.ts
    class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @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));
    }
    }

    View Slide

  54. class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @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

    View Slide

  55. BaseModel
    Model B
    Model A
    State tree Component tree
    state stream (game$)
    references (store.select(…))

    View Slide

  56. class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @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

    View Slide

  57. class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @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

    View Slide

  58. Component
    Model
    ngrx

    View Slide

  59. Component
    Model
    completeGame()
    ngrx

    View Slide

  60. Component
    Model
    dispatch(action)
    ngrx
    completeGame()

    View Slide

  61. Component
    Model
    ngrx
    stateObserver.next(state)
    dispatch(action)
    completeGame()

    View Slide

  62. Component
    Model
    stateObserver.next(state)
    dispatch(action)
    ngrx
    completeGame()

    View Slide

  63. class GameModel extends Model {
    game$: Observable;
    constructor(protected _store: Store,
    @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

    View Slide

  64. Models (Façade)
    • Map the state stream
    • Delegate business logic to reducers
    • Delegate data-access logic to async services

    View Slide

  65. Beware for violations of the SRP

    View Slide

  66. 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

    View Slide

  67. Component
    Model
    stateObserver.next(state)
    dispatch(action)
    ngrx
    onProgress()

    View Slide

  68. ngrx
    reducers
    dispatch(action)
    State tree
    Action
    New state
    New state

    View Slide

  69. 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;
    };

    View Slide

  70. 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;
    };

    View Slide

  71. 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;
    };

    View Slide

  72. 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;
    };

    View Slide

  73. game.component.ts

    get currentText$() {
    return this._model.game$
    .map((game: Game) => game.currentText);
    }

    View Slide

  74. game.component.ts

    get currentText$() {
    return this._model.game$
    .map((game: Game) => game.currentText);
    }

    View Slide

  75. game.component.ts

    get currentText$() {
    return this._model.game$
    .map((game: Game) => game.currentText);
    }

    View Slide

  76. game.component.html


    {{ currentText$ | async }}


    View Slide

  77. game.component.html


    {{ currentText$ | async }}


    View Slide

  78. Reducers
    • Pure functions of the state and an action
    • Implement the business logic

    View Slide

  79. Async Services

    View Slide

  80. 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

    View Slide

  81. Remote
    Service
    App

    View Slide

  82. Remote
    Service
    App

    View Slide

  83. Remote
    Service
    App

    View Slide

  84. export abstract class AsyncService {
    abstract process(data: Action): Observable;
    }
    base.async-service.ts

    View Slide

  85. 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

    View Slide

  86. 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

    View Slide

  87. 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

    View Slide

  88. Async services
    • Map actions to commands
    • Map responses/messages to actions
    • Use an abstract gateway

    View Slide

  89. View Slide

  90. Recap

    View Slide

  91. 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$

    View Slide

  92. 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$

    View Slide

  93. 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$

    View Slide

  94. 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$

    View Slide

  95. 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$

    View Slide

  96. 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$

    View Slide

  97. 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$

    View Slide

  98. 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$

    View Slide

  99. 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$

    View Slide

  100. 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)

    View Slide

  101. View Slide

  102. 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

    View Slide

  103. 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

    View Slide

  104. 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

    View Slide

  105. 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

    View Slide

  106. 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

    View Slide

  107. 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

    View Slide

  108. 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

    View Slide

  109. Resources
    • redux
    • ngrx
    • mgv.io/scalable-architecture
    • mgv.io/scalable-architecture-demo

    View Slide

  110. Thank you!
    github.com/mgechev
    twitter.com/mgechev
    blog.mgechev.com

    View Slide