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. Microservices with Dart, Polymer
    and Hypermedia
    Sébastien Deleuze, Spring Framework Commiter and Dart GDE, Pivotal Inc.
    @sdeleuze

    View Slide

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

    View Slide

  3. Microservice
    Small
    Single

    Responsibility

    Principle
    Deployed

    Independently
    Inputs /
    Outputs

    View Slide

  4. 4 4
    “Microservices architecture is essentially fine grained

    SOA done right without vendor bullshit”

    View Slide

  5. 5
    Asynchronous non-blocking I/O is mandatory

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  9. Dart
    introduction

    View Slide

  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

    View Slide

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

    View Slide

  12. 12
    Is Dart only for Chrome ?
    ?

    View Slide

  13. 13
    Javascript is the Web's bytecode

    View Slide

  14. 14
    Dart everywhere!
    Dart enabled Chrome

    (Dart VM)
    Server
    All major browsers

    (Javascript engine)
    Android
    Cloud
    ?
    Desktop
    ?

    View Slide

  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

    View Slide

  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?';  
    }

    View Slide

  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'));  
    }

    View Slide

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

    View Slide

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

    View Slide

  20. Dart language
    20

    View Slide

  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

    View Slide

  22. OpenSnap
    Client

    View Slide

  23. 23
    OpenSnap

    Client
    Project
    structure
    23

    View Slide

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

    View Slide

  25. 25
    Polymer: platform.js + a component oriented library
     
     
       
     
     
       
     
     

    View Slide

  26. Paper element implements material design for the web

    View Slide

  27. 27
    Index.html with
     
     
         
             
             
             
             
         
       

           

           export  'package:polymer/init.dart';  
         

    View Slide

  28. 28
    app_element.html file
     
     
     
     
       
     
       
             
         
                       
           
                                 
           
                                 
         
                       
       
             
       
             

    View Slide

  29. 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));  
                       
      }  
             
    }

    View Slide

  30. 30
    Taking photos with WebRTC



    data:image/jpg;base64,/9j/4AAQSkZJR
    window.navigator.getUserMedia()

    View Slide

  31. OpenSnap
    Server

    View Slide

  32. 32
    OpenSnap server project structure
    32

    View Slide

  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

    View Slide

  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);  
    }

    View Slide

  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'};

    View Slide

  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);  
       });  
    }

    View Slide

  37. OpenSnap
    Commons

    View Slide

  38. 38
    OpenSnap

    Common
    Project
    Structure
    38

    View Slide

  39. 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()  {  
           //  …  
       }  
    }

    View Slide

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

    View Slide

  41. 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()  {  
           //  ...  
       }  
    }

    View Slide

  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"  }  ]  
    }

    View Slide

  43. 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;  
           });  
       }  
    }  

    View Slide

  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

    View Slide

  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

    View Slide

  46. Messaging

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  51. IDE

    View Slide

  52. 52
    Dart Editor

    View Slide

  53. 53
    IDEA IntelliJ or Webstorm

    View Slide

  54. 54
    Chrome Dev Editor

    View Slide

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

    View Slide