Slide 1

Slide 1 text

Code/Demo Server book-jacket-api book-info-website Book Jacket API Rating API Book Info Website Browser ratings-api {server}.herokuapp.com https://github.com/PurpleBooth/{server}

Slide 2

Slide 2 text

Angular JS & Microservices Welcome to the world of tomorrow!

Slide 3

Slide 3 text

Goals • Working knowledge • Client side JavaScript frameworks • Know how to integrate them with existing services • Become more of a Cross Stack developer

Slide 4

Slide 4 text

Who’re you? Billie Thompson @PurpleBooth [email protected] PHP Contractor

Slide 5

Slide 5 text

Imagination Time! http://www.businesscat.happyjar.com/comic/coffee/

Slide 6

Slide 6 text

Book Information Service Book Jacket API Rating API Book Info Website Browser

Slide 7

Slide 7 text

Book Information Service DB Queues Logic Responsive Extendible Clean Parallel Serve JS Frontend-y Book Jacket API Rating API Book Info Website Browser

Slide 8

Slide 8 text

Book Information Service Silex PHP AngularJS JavaScript Express JavaScript Book Jacket API Rating API Book Info Website Browser

Slide 9

Slide 9 text

Code/Demo Server book-jacket-api book-info-website Book Jacket API Rating API Book Info Website Browser ratings-api {server}.herokuapp.com https://github.com/PurpleBooth/{server}

Slide 10

Slide 10 text

MVVM Model, View and View Model

Slide 11

Slide 11 text

Book Information Service Book Jacket API Rating API Book Info Website Browser

Slide 12

Slide 12 text

MVVM Framework View View Model Model Remote API Browser Server

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

–AngularJS Demo “It looked so easy when I saw someone else’s talk”

Slide 15

Slide 15 text

//- book-info-website/views/index.jade doctype html
 html(lang="en" ng-app="booksApp")
 head
 title Book Info Website
 link(rel='stylesheet', href='/stylesheets/style.css')
 script(src='/assets/modules/angular/angular.js')
 script(src='/assets/modules/angular-resource/angular-resource.js')
 script(src='/assets/modules/angular-route/angular-route.js')
 script(src='/javascripts/angular/app.js')
 script(src='/javascripts/angular/services.js')
 script(src='/javascripts/angular/controllers.js')
 body
 div(ng-view)

Slide 16

Slide 16 text


 
 
 
 Book Info Website
 
 
 
 
 
 
 
 
 
 


 
 


Slide 17

Slide 17 text

//- book-info-website/views/index.jade 
 script(src='/assets/modules/angular/angular.js')
 script(src='/assets/modules/angular-resource/angular-resource.js')
 script(src='/assets/modules/angular-route/angular-route.js')

Slide 18

Slide 18 text

//- book-info-website/views/index.jade script(src='/javascripts/angular/app.js')
 script(src='/javascripts/angular/services.js')
 script(src='/javascripts/angular/controllers.js')

Slide 19

Slide 19 text

// book-info-website/public/javascripts/angular/app.js
 var booksApp = angular.module('booksApp', [
 'ngResource', 
 'ngRoute',
 'booksServices',
 'booksControllers'
 ]);
 
 booksApp.config(['$routeProvider',
 function($routeProvider) {
 $routeProvider.
 when('/', {
 templateUrl: 'partials/list.html',
 controller: 'IndexController'
 }).
 when('/:isbn', {
 templateUrl: 'partials/details.html',
 controller: 'DetailController'
 }).
 otherwise({
 redirectTo: '/'
 });
 }]);

Slide 20

Slide 20 text

//- book-info-website/views/index.jade html(lang="en" ng-app=“booksApp”) … // book-info-website/public/javascripts/angular/app.js 
 var booksApp = angular.module('booksApp', [
 'ngResource', 
 'ngRoute',
 'booksServices',
 'booksControllers'
 ]); 


Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

// book-info-website/public/javascripts/angular/app.js booksApp.config(['$routeProvider',
 function($routeProvider) {
 $routeProvider.
 when('/', {
 templateUrl: 'partials/list.html',
 controller: 'IndexController'
 }).
 when('/:isbn', {
 templateUrl: 'partials/details.html',
 controller: 'DetailController'
 }).
 otherwise({
 redirectTo: '/'
 });
 }]); … //- book-info-website/views/index.jade body
 div(ng-view)

Slide 23

Slide 23 text

//- book-info-website/views/partials/list.jade
 div(ng-controller="IndexController")
 h1= title
 h2 Book list!
 ul
 li(ng-repeat="book in books")
 a(href="#/{{book.isbn}}") {{book.title}}

Slide 24

Slide 24 text

// book-info-website/public/javascripts/angular/controllers.js
 var booksControllers = angular.module('booksControllers', []);
 
 booksControllers.controller('IndexController', ['$scope', 'Book', function ($scope, Book) {
 Book.query({}, function (books) {
 $scope.books = books;
 });
 }]);
 
 booksControllers.controller(
 'DetailController',
 ['$scope', '$routeParams', 'Book',
 function ($scope, $routeParams, Book) {
 Book.pushGet({isbn: $routeParams.isbn}, function (book) {
 $scope.book = book;
 });
 
 $scope.$on('$destroy', function () {
 Book.stopPushGet();
 });
 }
 ]
 );

Slide 25

Slide 25 text

// book-info-website/public/javascripts/angular/controllers.js var booksControllers = angular.module('booksControllers', []);
 
 booksControllers.controller( 'IndexController', ['$scope', 'Book', function ($scope, Book) {
 $scope.books = Book.query();
 }] ); …

Slide 26

Slide 26 text

// book-info-website/public/javascripts/angular/services.js
 var booksService = angular.module('booksServices', []);
 
 booksService.factory('Book', ['$resource', '$timeout',
 function ($resource, $timeout) {
 var api = $resource('api/v2/:isbn', {'isbn': '@id'});
 
 var promise;
 var pushing = false;
 
 api.pushGet = function (params, callback) {
 if (!pushing) {
 pushing = true;
 
 var refreshData = function () {
 api.get(params, function (book) {
 callback(book);
 
 promise = $timeout(
 function () {
 refreshData(params, callback);
 }, 4000);
 });
 };
 
 refreshData(params, callback);
 }
 };
 
 api.stopPushGet = function () {
 pushing = false;
 
 if (angular.isDefined(promise)) {
 $timeout.cancel(promise);
 promise = undefined;
 }
 };
 
 return api;
 }]);

Slide 27

Slide 27 text

// book-info-website/public/javascripts/angular/services.js var booksService = angular.module('booksServices', []);
 
 booksService.factory('Book', ['$resource', '$timeout',
 function ($resource, $timeout) {
 var api = $resource('api/v2/:isbn', {'isbn': '@id'});
 …
 return api;
 }]);


Slide 28

Slide 28 text

MVVM Framework View View Model Model Remote API Browser Server

Slide 29

Slide 29 text

// book-info-website/public/javascripts/angular/controllers.js booksControllers.controller(
 'DetailController',
 ['$scope', '$routeParams', 'Book',
 function ($scope, $routeParams, Book) {
 Book.pushGet({isbn: $routeParams.isbn}, function (book) {
 $scope.book = book;
 });
 
 $scope.$on('$destroy', function () {
 Book.stopPushGet();
 });
 }
 ]
 );

Slide 30

Slide 30 text

// book-info-website/public/javascripts/angular/services.js var promise;
 var pushing = false;
 
 api.pushGet = function (params, callback) {
 if (!pushing) {
 pushing = true;
 
 var refreshData = function () {
 api.get(params, function (book) {
 callback(book);
 
 promise = $timeout(
 function () {
 refreshData(params, callback);
 }, 4000);
 });
 };
 
 refreshData(params, callback);
 }
 };

Slide 31

Slide 31 text

// book-info-website/public/javascripts/angular/services.js api.stopPushGet = function () {
 pushing = false;
 
 if (angular.isDefined(promise)) {
 $timeout.cancel(promise);
 promise = undefined;
 }
 };

Slide 32

Slide 32 text

Alternatives? • React • Backbone • Ember

Slide 33

Slide 33 text

Future of frontend development • Simpler than it looks • Has a lot of similarities with server side frameworks • Fast, as data is loaded after the bare minimum

Slide 34

Slide 34 text

Express Contender for the worlds most boring logo

Slide 35

Slide 35 text

Book Information Service Book Jacket API Rating API Book Info Website Browser

Slide 36

Slide 36 text

Blocking Book Jacket API Rating API Book Info Website Book Info Website

Slide 37

Slide 37 text

Non-blocking Book Jacket API Rating API Book Info Website Book Info Website

Slide 38

Slide 38 text

– Express Demo “How likely is that it will break twice?”

Slide 39

Slide 39 text

// book-info-website/app.js
 var express = require('express');
 var path = require('path');
 var favicon = require('serve-favicon');
 var logger = require('morgan');
 var cookieParser = require('cookie-parser');
 var bodyParser = require('body-parser');
 
 var routes = require('./routes/index');
 var bookApi1 = require('./routes/book-api-v1');
 var bookApi2 = require('./routes/book-api-v2');
 
 var app = express();
 
 // view engine setup
 app.set('views', path.join(__dirname, 'views'));
 app.set('view engine', 'jade');
 
 // uncomment after placing your favicon in /public
 //app.use(favicon(__dirname + '/public/favicon.ico'));
 app.use(logger('dev'));
 app.use(bodyParser.json());
 app.use(bodyParser.urlencoded({ extended: false }));
 app.use(cookieParser());
 app.use(express.static(path.join(__dirname, 'public')));
 app.use("/assets/modules", express.static(__dirname + '/bower_components'));
 
 app.use('/', routes);
 app.use('/api/v1', bookApi1);
 app.use('/api/v2', bookApi2);
 
 // catch 404 and forward to error handler
 app.use(function(req, res, next) {
 var err = new Error('Not Found');
 err.status = 404;
 next(err);
 });
 
 // error handlers
 
 // development error handler
 // will print stacktrace
 if (app.get('env') === 'development') {
 app.use(function(err, req, res, next) {
 res.status(err.status || 500);
 res.render('error', {
 message: err.message,
 error: err
 });
 });
 }
 
 // production error handler
 // no stacktraces leaked to user
 app.use(function(err, req, res, next) {
 res.status(err.status || 500);
 res.render('error', {
 message: err.message,
 error: {}
 });
 });
 
 
 module.exports = app;

Slide 40

Slide 40 text

// book-info-website/app.js … 
 var routes = require('./routes/index');
 var bookApi1 = require('./routes/book-api-v1');
 var bookApi2 = require('./routes/book-api-v2'); … app.use("/assets/modules", express.static(__dirname + '/bower_components'));
 
 app.use('/', routes);
 app.use('/api/v1', bookApi1);
 app.use('/api/v2', bookApi2); 


Slide 41

Slide 41 text

// book-info-website/routes/index.js
 var express = require('express');
 var router = express.Router();
 
 /* GET home page. */
 router.get('/', function (req, res, next) {
 res.render('index');
 });
 
 /* GET home page. */
 router.get('/partials/details.html', function (req, res, next) {
 res.render('partials/details');
 });
 
 /* GET home page. */
 router.get('/partials/list.html', function (req, res, next) {
 res.render('partials/list');
 });
 
 
 module.exports = router;

Slide 42

Slide 42 text

// book-info-website/routes/index.js /* GET home page. */
 router.get('/', function(req, res, next) {
 res.render('index');
 }); …

Slide 43

Slide 43 text

//- book-info-website/views/index.jade 
 doctype html
 html(lang="en" ng-app="booksApp")
 head
 title Book Info Website
 link(rel='stylesheet', href='/stylesheets/style.css')
 script(src='/assets/modules/angular/angular.js')
 script(src='/assets/modules/angular-resource/angular- resource.js')
 script(src='/assets/modules/angular-route/angular-route.js')
 script(src='/javascripts/angular/app.js')
 script(src='/javascripts/angular/services.js')
 script(src='/javascripts/angular/controllers.js')
 body
 div(ng-view)

Slide 44

Slide 44 text

// book-info-website/routes/book-api-v1.js
 var express = require('express');
 
 var booksApiClient = require('./../api-client/books');
 
 var router = express.Router();
 
 router.get('/', function (req, res, next) {
 booksApiClient.findAll(function (err, books) {
 res.json(books);
 });
 });
 
 router.get('/:isbn', function (req, res, next) {
 booksApiClient.getBook(req.params.isbn, function (err, bookDetail) {
 res.json(bookDetail);
 });
 });
 
 module.exports = router;

Slide 45

Slide 45 text

// book-info-website/routes/book-api-v1.js router.get('/', function (req, res, next) {
 booksApiClient.findAll(function (err, books) {
 res.json(books);
 });
 });

Slide 46

Slide 46 text

// book-info-website/api-client/books.js
 var makeHttpRequest = require('./client');
 
 var Books = function () {
 
 var endpoint = "http://book-jacket-api.herokuapp.com/";
 
 this.getBook = function (isbn, callback) {
 makeHttpRequest(endpoint.concat(isbn), callback)
 };
 
 this.findAll = function (callback) {
 makeHttpRequest(endpoint, callback)
 };
 };
 
 module.exports = new Books();

Slide 47

Slide 47 text

// book-info-website/api-client/client.js var http = require('http');
 
 module.exports = function (url, callback) {
 http.get(url, function (res) {
 var str = '';
 
 res.on('data', function (chunk) {
 str += chunk;
 });
 
 res.on('end', function () {
 var apiResponse = JSON.parse(str);
 
 callback(null, apiResponse);
 });
 }).on('error', function (e) {
 callback(e);
 });
 };

Slide 48

Slide 48 text

// book-info-website/routes/book-api-v2.js
 
 var express = require('express');
 var async = require('async');
 var _ = require('underscore');
 
 var booksApiClient = require('./../api-client/books');
 var ratingsApiClient = require('./../api-client/ratings');
 
 var router = express.Router();
 
 router.get('/', function (req, res, next) {
 booksApiClient.findAll(function (err, books) {
 res.json(books);
 });
 });
 
 router.get('/:isbn', function (req, res, next) {
 async.parallel({
 'book': function (callback) {
 booksApiClient.getBook(req.params.isbn, function (err, bookDetail) {
 callback(err, bookDetail);
 });
 },
 'rating': function (callback) {
 ratingsApiClient.getRating(req.params.isbn, function (err, ratings) {
 callback(err, ratings);
 });
 }
 }, function (err, results) {
 _.extend(results.book, {'rating': results.rating});
 res.json(results.book);
 });
 });
 
 module.exports = router;

Slide 49

Slide 49 text

// book-info-website/routes/book-api-v2.js router.get('/:isbn', function (req, res, next) {
 async.parallel({
 'book': function (callback) {
 booksApiClient.getBook(req.params.isbn, function (err, bookDetail) {
 callback(err, bookDetail);
 });
 }, 
 'rating': function (callback) {
 ratingsApiClient.getRating(req.params.isbn, function (err, ratings) {
 callback(err, ratings);
 });
 } 
 }, function (err, results) {
 _.extend(results.book, {'rating': results.rating});
 res.json(results.book);
 });
 });


Slide 50

Slide 50 text

Alternatives? • Akka/Storm/React • Queues + Any language • Go/Rust/Other language with parallelisation

Slide 51

Slide 51 text

Express • Non-blocking • Front-end Developer Friendly • Lightweight

Slide 52

Slide 52 text

In conclusion • Lower the barrier to entry to allow flexibility in you team • MVVM frameworks are going to get more popular • MVVM frameworks require APIs • Use middleware to hide none public services

Slide 53

Slide 53 text

http://www.happyjar.com/comic/pay-rise/

Slide 54

Slide 54 text

Any Questions? @PurpleBooth [email protected]