Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

5778521f67d80de0ee3b213e4f159a59?s=128

Sébastien Deleuze

November 06, 2014
Tweet

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 …