Slide 1

Slide 1 text

Microservices with Dart, Polymer and Hypermedia Sébastien Deleuze, Spring Framework Commiter and Dart GDE, Pivotal Inc. @sdeleuze

Slide 2

Slide 2 text

2 OpenSnap is a HTML5 clone of SnapChat https://github.com/sdeleuze/opensnap-polymer

Slide 3

Slide 3 text

Microservice Small Single
 Responsibility
 Principle Deployed
 Independently Inputs / Outputs

Slide 4

Slide 4 text

4 4 “Microservices architecture is essentially fine grained
 SOA done right without vendor bullshit”

Slide 5

Slide 5 text

5 Asynchronous non-blocking I/O is mandatory

Slide 6

Slide 6 text

6 The technology stack should be Fun Easy to use Scalable Future proof Efficient

Slide 7

Slide 7 text

7 The stack 7 RabbitMQ SockJS Dart Server Client MongoDB Polymer Redstone Stomp Dart Messaging Persistence

Slide 8

Slide 8 text

8 Dart microservices architecture 8 User service RabbitMQ STOMP broker Snap service Web client Stomp over Socket Async HTTP request Async driver connection SockJS plugin Stomp over WebSocket } JSON + Hypermedia

Slide 9

Slide 9 text

Dart introduction

Slide 10

Slide 10 text

10 Dart § Structured and flexible language created to improve maintainability and efficiency of web developments § Open Source § Official ECMA standard § Current version : Dart 1.7

Slide 11

Slide 11 text

11 Dart platform Language Tooling Virtual machine Documentation API Web frameworks dart2js compiler Package repository

Slide 12

Slide 12 text

12 Is Dart only for Chrome ? ?

Slide 13

Slide 13 text

13 Javascript is the Web's bytecode

Slide 14

Slide 14 text

14 Dart everywhere! Dart enabled Chrome
 (Dart VM) Server All major browsers
 (Javascript engine) Android Cloud ? Desktop ?

Slide 15

Slide 15 text

import  'dart:math';
 class  Point  {      final  num  _x,  _y;      num  get  x  =>  _x;      num  get  y  =>  _y;            Point(this._x,  this._y);          num  distanceTo(Point  other)  {          var  dx  =  _x  -­‐  other.x;          var  dy  =  _y  -­‐  other.y;          return  sqrt(dx  *  dx  +  dy  *  dy);      }   }       main()  {        var  a  =  new  Point(2,  3);      var  b  =  new  Point(3,  4);        print('distance  from  a  to  b  =  ${a.distanceTo(b)}');   } Classes and optional typing 15

Slide 16

Slide 16 text

16 Implicit interfaces class  Person  {      final  _name;      Person(this._name);      String  greet(who)  =>  'Hello,  $who.  I  am  $_name.';   }       class  Bro  implements  Person  {      String  greet(who)  =>  'Hi  $who.  What’s  up?';   }

Slide 17

Slide 17 text

17 Future API Future  authenticate()  {          return  userService.signout()                          .then((_)  =>  userService.signin(user))                          .then((_)  =>  router.go('photo',  new  Map()))                          .catchError((_)  =>  window.alert('Error  during  login'));   }

Slide 18

Slide 18 text

18 Optional parameters String  say(String  from,  String  msg,  [String  channel='email'])  {          //  ...   }       main()  {            say('Bob',  'Howdy');          say('Bob',  'Howdy',  'smoke  signal');   }

Slide 19

Slide 19 text

19 Named parameters enableFlags({bool  bold:  false,  bool  hidden:  false})  {            //  ...   }       main()  {            enableFlags();          enableFlags(bold:  true);          enableFlags(bold:  true,  hidden:  false);   }  

Slide 20

Slide 20 text

Dart language 20

Slide 21

Slide 21 text

Dart (current) drawbacks 21 § Lack of quality server side library § Still waiting a big enabler (Chrome and/or Android support) § No good JSON automatic serialization mechanism § Javascript interoperability could be better

Slide 22

Slide 22 text

OpenSnap Client

Slide 23

Slide 23 text

23 OpenSnap
 Client Project structure 23

Slide 24

Slide 24 text

24 Web Components Custom elements HTML imports Templates Shadow DOM

Slide 25

Slide 25 text

25 Polymer: platform.js + a component oriented library                    

Slide 26

Slide 26 text

Paper element implements material design for the web

Slide 27

Slide 27 text

27 Index.html with                                                            
        
        export  'package:polymer/init.dart';        

Slide 28

Slide 28 text

28 app_element.html file                                                                                                                                                                                        

Slide 29

Slide 29 text

29 app_element.dart file library  app_element;       import  'package:polymer/polymer.dart';       @CustomTag('app-­‐element')   class  AppElement  extends  PolymerElement  {     @observable  final  ObservableList  users  =  new  ObservableList();               @observable  final  ObservableList  snapReceived  =  new  ObservableList();               @observable  int  selected  =  0;               StompListener  _stompListener  =  new  StompListener();               SnapSync  _snapSync  =  new  SnapSync();                   AppElement.created()  :  super.created()  {                 _snapSync.getReceived().then((snaps)  =>  snapReceived.addAll(snaps));                           _stompListener.subscribeSnapInbox((snap)  =>  snapReceived.add(snap));                         }             }

Slide 30

Slide 30 text

30 Taking photos with WebRTC data:image/jpg;base64,/9j/4AAQSkZJR window.navigator.getUserMedia()

Slide 31

Slide 31 text

OpenSnap Server

Slide 32

Slide 32 text

32 OpenSnap server project structure 32

Slide 33

Slide 33 text

33 Pub is Dart’s Maven name:  os_user   version:  0.1.0   dependencies:      mongo_dart:  '>=0.1.44  <0.2.0'      redstone:          git:  https://github.com/sdeleuze/redstone.dart.git      os_common:          path:  ../os-­‐common   dev_dependencies:      unittest:  any   pubspec.yaml

Slide 34

Slide 34 text

34 REST Webservices: user_service.dart import  'package:redstone/server.dart'  as  app;   import  'package:mongo_dart/mongo_dart.dart';   @app.Group('/user')   class  UserService  {          DbCollection  get  users  =>  app.request.attributes.db.collection("users");          @app.Route('/')      list()  =>  users.find().toList();          @app.Route('/',  methods:  const  [app.POST],  statusCode:  HttpStatus.CREATED)      @Encode()  create(@Decode()  User  u)  =>  users.insert(u.toJson()).then((_)  =>  u);                @app.Route('/:id')  =>  users.findOne(where.eq('_id',  id));          @app.Route('/:id',  methods:  const  [app.DELETE],  statusCode:  HttpStatus.NO_CONTENT)      delete(String  id)  =>  users.remove(where.eq('_id',  id)).then((_)  =>  true);   }

Slide 35

Slide 35 text

35 Interceptors: cors_interceptor.dart @app.Interceptor(r'/.*')   corsInterceptor()  {      if  (app.request.method  ==  'OPTIONS')  {          //overwrite  the  current  response  and  interrupt  the  chain.          app.response  =  new  shelf.Response.ok(null,  headers:  _createCorsHeader());          app.chain.interrupt();      }  else  {          //process  the  chain  and  wrap  the  response          app.chain.next(()  =>  app.response.change(headers:  _createCorsHeader()));      }   }       _createCorsHeader()  =>  {'Access-­‐Control-­‐Allow-­‐Origin':  '*',                                                  'Access-­‐Control-­‐Allow-­‐Headers':  'Content-­‐Type',                                                  'Access-­‐Control-­‐Allow-­‐Methods':  'POST,  GET,  DELETE,  OPTIONS',                                                  'Access-­‐Control-­‐Max-­‐Age':  '1728000'};

Slide 36

Slide 36 text

36 Running the server: user_server.dart import  'package:redstone/server.dart'  as  app;   import  'package:mongo_dart/mongo_dart.dart';   @app.Install()   import  'package:os_user/os_user.dart';   @app.Install()   import  'package:os_common/os_common_server.dart';       main()  {      app.setupConsoleLog();      Db  db  =  new  Db('mongodb://localhost/users');      db.open().then((_)  {          app.addModule(new  Module()..bind(Db,  toValue:  db));          app.addPlugin(ObjectMapper);          app.start(port:  8081);      });   }

Slide 37

Slide 37 text

OpenSnap Commons

Slide 38

Slide 38 text

38 OpenSnap
 Common Project Structure 38

Slide 39

Slide 39 text

39 User class class  User  {            String  id;      String  username;      String  password;      List  roles;          User([this.username  =  null,  this.password  =  null,  List  roles,  this.id  =  null])  {          this.roles  =  (roles  ==  null)  ?  new  List()  :  roles;      }          factory  User.fromJson(value)  {          //  …      }          Map  toJson()  {          //  …      }   }

Slide 40

Slide 40 text

40 User JSON serialization {      "_id":"543b80c33786c930f70e3961",      "username":"Seb",      "password":"qwerty",      "roles":["USER","ADMIN"]   }

Slide 41

Slide 41 text

41 Snap class class  Snap  {          String  id;      User  author;      List  recipients;      String  photo;      int  duration;          Snap([this.author,  this.recipients,  this.photo,  this.duration,  this.id  =  null]);          factory  Snap.fromJson(value)  {          //  ...      }          Map  toJson()  {          //  ...      }   }

Slide 42

Slide 42 text

42 Snap simple JSON serialization (not implemented) {      "_id":  "543f18e6a89233097207bce1",      "photo":  "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...",      "duration":  4,      "author":  {  "_id":  "543b80c33786c930f70e3961"  }      "recipients"  :  [  {  "_id":  "543b80c33786c930f70e39612"  },                                        {  "_id":  "543b80c33786c930f70e3963"  }  ]   }

Slide 43

Slide 43 text

43 On demand client or server users fetch class  SnapAssembler  {            var  _client;            SnapAssembler(this._client);            Future  fetch(Snap  snap)  {          List  userIds  =    snap.recipients.map((user)  =>  user.id).toList();          userIds.add(snap.author.id);          return  _client.get(Uri.parse(‘${User.BASE_URL}${userIds.join(',')}'),     headers:  {'Accept':  'application/json'}).then((response)  {                          List  users  =  User.fromJsonList(response.body);              snap.author  =  users.singleWhere((user)  =>  user.id  ==  snap.author.id);              snap.recipients  =  snap.recipients.map((recipient)  =>  users.singleWhere((user)  =>     user.id  ==  recipient.id)).toList();                          return  snap;          });      }   }  

Slide 44

Slide 44 text

44 Dart microservices architecture 44 Web client Snap service User service GET /snap/123 Snap with users IDs GET /user/545,676,542 Request users Snap with all users fields

Slide 45

Slide 45 text

45 Snap JSON serialization (Hypermedia version) {      "_id":  "543f18e6a89233097207bce1",      "photo":  "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...",      "duration":  4,      "_links":  {          "curies":  [              {                  "name":  "u",  "templated":  true,                  "href":  "http://localhost:8081/user/{rel}"              }          ],          "u:author":  {  "href":  "543b80c33786c930f70e3961"  },          "u:recipients":  [              {  "href":  "543b80c33786c930f70e3961"  }          ]      }   } http://stateless.co/hal_specification.html

Slide 46

Slide 46 text

Messaging

Slide 47

Slide 47 text

47 Why STOMP ? § Server need to publish events to the clients and other servers § STOMP = simple text based messaging over § TCP § Pure Websocket § SockJS (Websocket + fallbacks) § Using RabbitMQ § Handle routing and broacasting of messages

Slide 48

Slide 48 text

48 Stomp frame MESSAGE   destination:/queue/user.created   content-­‐type:application/json;charset=UTF-­‐8   {"username":"seb","password":null,"roles":["USER","ADMIN"]}

Slide 49

Slide 49 text

49 SockJS fallback options § Websocket is not always possible (proxy, IE) § RabbitMQ supports SockJS protocol § Client side: thin layer on Websocket API with several possible transports (Websocket, Server- Sent events, HTTP streaming, JSONP, Iframe …) § Must have for every large audience website

Slide 50

Slide 50 text

50 Messaging void  _subscribeSnapInbox(User  currentUser,  void  onCreated(Snap  snap))  {          _stompClient.subscribe(‘/queue/snap.inbox.${currentUser.id}’).listen((Stomp.Frame  frame)  =>                  _assembler.fetch(new  Snap.fromJson(frame.body)).then((snap)  =>    onCreated(snap)));   } @app.Route('/',  methods:  const  [app.POST])   create(@Decode()  Snap  snap)  {     if(snap.id  ==  null)  {                    snap.id  =  new  ObjectId().toHexString();                 }                 var  jsonSnap  =  snap.toJson();                 return  _snaps.insert(jsonSnap).then((_)  {                    snap.recipients.forEach((recipient)  =>                            _stomClient.send('/queue/snap.inbox.${recipient.id}',  body:  JSON.encode(jsonSnap)));                                return  jsonSnap;                 });               } On client side On server side

Slide 51

Slide 51 text

IDE

Slide 52

Slide 52 text

52 Dart Editor

Slide 53

Slide 53 text

53 IDEA IntelliJ or Webstorm

Slide 54

Slide 54 text

54 Chrome Dev Editor

Slide 55

Slide 55 text

Thanks! Code : https://github.com/sdeleuze/opensnap-polymer Dartisans : http://g.co/dartisans Dartlang [FR] - ʕ๏̮๏ʔ : http://gplus.to/dartlangfr Follow me on @sdeleuze …