$30 off During Our Annual Pro Sale. View Details »

Microservices with Dart, Polymer and Hypermedia

Microservices with Dart, Polymer and Hypermedia

This talk is about developing an application with the same language on client and server side : Dart. Based on a SnapChat clone sample app (source code available at https://github.com/sdeleuze/opensnap-polymer), the slides show a sample Microservices architecture based on Dart, Polymer, JSON REST webservices, STOMP and HAL (Hypermedia).

Sébastien Deleuze

November 06, 2014
Tweet

More Decks by Sébastien Deleuze

Other Decks in Programming

Transcript

  1. Microservices with Dart, Polymer and Hypermedia Sébastien Deleuze, Spring Framework

    Commiter and Dart GDE, Pivotal Inc. @sdeleuze
  2. 2 OpenSnap is a HTML5 clone of SnapChat https://github.com/sdeleuze/opensnap-polymer

  3. Microservice Small Single
 Responsibility
 Principle Deployed
 Independently Inputs / Outputs

  4. 4 4 “Microservices architecture is essentially fine grained
 SOA done

    right without vendor bullshit”
  5. 5 Asynchronous non-blocking I/O is mandatory

  6. 6 The technology stack should be Fun Easy to use

    Scalable Future proof Efficient
  7. 7 The stack 7 RabbitMQ SockJS Dart Server Client MongoDB

    Polymer Redstone Stomp Dart Messaging Persistence
  8. 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
  9. Dart introduction

  10. 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
  11. 11 Dart platform Language Tooling Virtual machine Documentation API Web

    frameworks dart2js compiler Package repository
  12. 12 Is Dart only for Chrome ? ?

  13. 13 Javascript is the Web's bytecode

  14. 14 Dart everywhere! Dart enabled Chrome
 (Dart VM) Server All

    major browsers
 (Javascript engine) Android Cloud ? Desktop ?
  15. 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
  16. 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?';   }
  17. 17 Future API Future  authenticate()  {        

     return  userService.signout()                          .then((_)  =>  userService.signin(user))                          .then((_)  =>  router.go('photo',  new  Map()))                          .catchError((_)  =>  window.alert('Error  during  login'));   }
  18. 18 Optional parameters String  say(String  from,  String  msg,  [String  channel='email'])

     {          //  ...   }       main()  {            say('Bob',  'Howdy');          say('Bob',  'Howdy',  'smoke  signal');   }
  19. 19 Named parameters enableFlags({bool  bold:  false,  bool  hidden:  false})  {

               //  ...   }       main()  {            enableFlags();          enableFlags(bold:  true);          enableFlags(bold:  true,  hidden:  false);   }  
  20. Dart language 20

  21. 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
  22. OpenSnap Client

  23. 23 OpenSnap
 Client Project structure 23

  24. 24 Web Components Custom elements HTML imports Templates Shadow DOM

  25. 25 Polymer: platform.js + a component oriented library <!-­‐-­‐  Polyfill

     Web  Components  support  for  older  browsers  -­‐-­‐>   <script  src="components/platform/platform.js"></script>       <!-­‐-­‐  Import  element  -­‐-­‐>   <link  rel="import"  href="google-­‐map.html">       <!-­‐-­‐  Use  element  -­‐-­‐>   <google-­‐map  lat="37.790"  long="-­‐122.390"></google-­‐map>  
  26. Paper element implements material design for the web

  27. 27 Index.html with <app-element /> <!DOCTYPE  html>   <html  lang="en">

         <head>          <meta  charset="utf-­‐8">          <meta  http-­‐equiv="X-­‐UA-­‐Compatible"  content="IE=edge,chrome=1">          <link  rel='stylesheet'  href='index.css'>          <link  rel="import"  href="packages/os_client/components/app_element.html">      </head>      <body  unresolved>
        <app-­‐element></app-­‐element>
        <script  type="application/dart">export  'package:polymer/init.dart';</script>      </body>   </html  
  28. 28 app_element.html file <link  rel="import"  href="packages/polymer/polymer.html">   <link  rel="import"  href="packages/core_elements/core_animated_pages.html">

      <link  rel="import"  href="photo_element.html">   <link  rel="import"  href="received_element.html">       <polymer-­‐element  name="app-­‐element">     <template>                 <core-­‐pages  selected="{{selected}}">                             <photo-­‐element  users={{users}}  ></photo-­‐element>                                       <received-­‐element  snaps={{snapReceived}}></received-­‐element>                                     </core-­‐pages>                         </template>               <script  type="application/dart"  src="app_element.dart"></script>             </polymer-­‐element>
  29. 29 app_element.dart file library  app_element;       import  'package:polymer/polymer.dart';

          @CustomTag('app-­‐element')   class  AppElement  extends  PolymerElement  {     @observable  final  ObservableList<User>  users  =  new  ObservableList<User>();               @observable  final  ObservableList<Snap>  snapReceived  =  new  ObservableList<Snap>();               @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));                         }             }
  30. 30 Taking photos with WebRTC <video /> <canvas /> <img

    src ="data:image/jpg;base64,/9j/4AAQSkZJR" /> data:image/jpg;base64,/9j/4AAQSkZJR window.navigator.getUserMedia()
  31. OpenSnap Server

  32. 32 OpenSnap server project structure 32

  33. 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
  34. 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);   }
  35. 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'};
  36. 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);      });   }
  37. OpenSnap Commons

  38. 38 OpenSnap
 Common Project Structure 38

  39. 39 User class class  User  {        

       String  id;      String  username;      String  password;      List<String>  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()  {          //  …      }   }
  40. 40 User JSON serialization {      "_id":"543b80c33786c930f70e3961",    

     "username":"Seb",      "password":"qwerty",      "roles":["USER","ADMIN"]   }
  41. 41 Snap class class  Snap  {        

     String  id;      User  author;      List<User>  recipients;      String  photo;      int  duration;          Snap([this.author,  this.recipients,  this.photo,  this.duration,  this.id  =  null]);          factory  Snap.fromJson(value)  {          //  ...      }          Map  toJson()  {          //  ...      }   }
  42. 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"  }  ]   }
  43. 43 On demand client or server users fetch class  SnapAssembler

     {            var  _client;            SnapAssembler(this._client);            Future<Snap>  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<User>  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;          });      }   }  
  44. 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
  45. 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
  46. Messaging

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

  49. 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
  50. 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
  51. IDE

  52. 52 Dart Editor

  53. 53 IDEA IntelliJ or Webstorm

  54. 54 Chrome Dev Editor

  55. Thanks! Code : https://github.com/sdeleuze/opensnap-polymer Dartisans : http://g.co/dartisans Dartlang [FR] -

    ʕ๏̮๏ʔ : http://gplus.to/dartlangfr Follow me on @sdeleuze …