Scalable Angular 2 Application Architecture
Transcript of Scalable Angular 2 Application Architecture
Scalable Application rchitecture
github.com/mgechev twitter.com/mgechev blog.mgechev.com
Package formats
• RESTful API • JSON commands
• WebSocket application service • JSON-RPC
• WebRTC data-channel • BERT-RPC
Dynamic RequirementsScalable Communication Layer
Various package formatsMultiple state mutation sources
Scalable teamLazy-loading
Dynamic RequirementsScalable Communication Layer
Various package formatsMultiple state mutation sources
Scalable teamLazy-loading
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.
Dynamic RequirementsScalable Communication Layer
Various package formatsMultiple state mutation sources
Scalable teamLazy-loading
Dynamic RequirementsScalable Communication Layer
Various package formatsMultiple state mutation sources
Scalable teamLazy-loading
. src
multi-player commands components gateways single-player components home components shared
/home
. src
multi-player commands components gateways single-player components home components shared
/single-player
. src
multi-player commands components gateways single-player components home components shared
/multi-player
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
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
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
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
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
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
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
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
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
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
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
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!')); } }
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!')); } }
@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
@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
@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
@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
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
Component
Model
Store
Dispatcher
startGame()dispatch(action)
applyReducers(action, store)
next(state)
Component
Model
Store
Dispatcher
startGame()dispatch(action)
applyReducers(action, store)
next(state)
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; };
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; };
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; };
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; };
Component
Model
Store
Dispatcher
startGame()dispatch(action)
applyReducers(action, store)
next(state)
game.component.ts
…
get invalid() { return this._model.game$ .scan((accum: boolean, current: any) => { return current.get('invalid') || accum; }, false); }
…
game.component.ts
…
get invalid() { return this._model.game$ .scan((accum: boolean, current: any) => { return current.get('invalid') || accum; }, false); }
…
game.component.ts
…
get invalid() { return this._model.game$ .scan((accum: boolean, current: any) => { return current.get('invalid') || accum; }, false); }
…
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
export abstract class AsyncService { abstract process(data: Action): Observable<any>; }
base.async-service.ts
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
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
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
let user = new Map(); user = user.set('name', 'Joe');
// { name: 'Joe' } console.log(user.toJS());
immutable.js
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
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
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
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
Async services
Business facade
Business logicCommunication logic
Immutable app state Component tree
root cmp
sign-up form
userUserModel
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$
StreamDependencyAction (manipulation/method call)
User registration
user.email = email user.name = name
Async services
Business facade
Business logicCommunication logic
Immutable app state Component tree
root cmp
sign-up form
userUserModel
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$
StreamDependencyAction (manipulation/method call)
User registration
user.email = email user.name = name
signup(data)
Async services
Business facade
Business logicCommunication logic
Immutable app state Component tree
root cmp
sign-up form
userUserModel
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$
StreamDependencyAction (manipulation/method call)
User registration
user.email = email user.name = name
signup(data)
signup(data)
Async services
Business facade
Business logicCommunication logic
Immutable app state Component tree
root cmp
sign-up form
userUserModel
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$
StreamDependencyAction (manipulation/method call)
User registration
user.email = email user.name = name
signup(data)
signup(data)
Async services
Business facade
Business logicCommunication logic
Immutable app state Component tree
root cmp
sign-up form
userUserModel
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$
StreamDependencyAction (manipulation/method call)
User registration
user.email = email user.name = name
signup(data)
signup(data)
Async services
Business facade
Business logicCommunication logic
Immutable app state Component tree
root cmp
sign-up form
userUserModel
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$
StreamDependencyAction (manipulation/method call)
User registration
user.email = email user.name = name
signup(data)
signup(data)
Async services
Business facade
Business logicCommunication logic
Immutable app state Component tree
root cmp
sign-up form
userUserModel
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$
StreamDependencyAction (manipulation/method call)
User registration
user.email = email user.name = name
signup(data)
signup(data)
Async services
Business facade
Business logicCommunication logic
Immutable app state Component tree
root cmp
sign-up form
userUserModel
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$
StreamDependencyAction (manipulation/method call)
User registration
user.email = email user.name = name
signup(data)
signup(data)
Async services
Business facade
Business logicCommunication logic
Immutable app state Component tree
root cmp
sign-up form
userUserModel
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$
StreamDependencyAction (manipulation/method call)
User registration
user.email = email user.name = name
signup(data)
signup(data)
Async services
Business facade
Business logicCommunication logic
Immutable app state Component tree
root cmp
sign-up form
userUserModel
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$
StreamDependencyAction (manipulation/method call)
User registration
user.email = email user.name = name
signup(data)
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
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
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
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
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
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
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
Resources
• redux • ngrx • Scalable Single-Page Application Architecture • Demo
Thank you!github.com/mgechev twitter.com/mgechev blog.mgechev.com