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

Sébastien Deleuze

November 06, 2014
Tweet

More Decks by Sébastien Deleuze

Other Decks in Programming

Transcript

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

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

    Polymer Redstone Stomp Dart Messaging Persistence
  3. 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
  4. 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
  5. 11 Dart platform Language Tooling Virtual machine Documentation API Web

    frameworks dart2js compiler Package repository
  6. 14 Dart everywhere! Dart enabled Chrome
 (Dart VM) Server All

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

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

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

               //  ...   }       main()  {            enableFlags();          enableFlags(bold:  true);          enableFlags(bold:  true,  hidden:  false);   }  
  12. 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
  13. 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>  
  14. 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  
  15. 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>
  16. 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));                         }             }
  17. 30 Taking photos with WebRTC <video /> <canvas /> <img

    src ="data:image/jpg;base64,/9j/4AAQSkZJR" /> data:image/jpg;base64,/9j/4AAQSkZJR window.navigator.getUserMedia()
  18. 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
  19. 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);   }
  20. 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'};
  21. 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);      });   }
  22. 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()  {          //  …      }   }
  23. 40 User JSON serialization {      "_id":"543b80c33786c930f70e3961",    

     "username":"Seb",      "password":"qwerty",      "roles":["USER","ADMIN"]   }
  24. 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()  {          //  ...      }   }
  25. 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"  }  ]   }
  26. 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;          });      }   }  
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. IDE